Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embed a Collection of Forms Error: Could not determine access type for property

I am trying to embed collection of Tag forms to Service form, according to this tutorial. Tag and Service entities have many-to-many relationship.

Form is rendering correctly. But when I submit form, I get

Could not determine access type for property "tagList"

error. I don't understand why new Tag object is not added to the Service class by calling the addTag() method.

ServiceType

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('title', TextType::class, array(
            'label' => 'Title'
        ))
    ;

    $builder->add('tagList', CollectionType::class, array(
        'entry_type' => TagType::class,
        'allow_add' => true,
        'allow_delete' => true,
        'by_reference' => false
    )));
}

Service class

{
....
 /**
 * @ORM\ManyToMany(targetEntity="Tag", mappedBy="serviceList",cascade={"persist"})
 */ 
private $tagList;

/**
 * @return ArrayCollection
 */
public function getTagList()
{
    return $this->tagList;
}

/**
 * @param Tag $tag
 * @return Service
 */
public function addTag(Tag $tag)
{
    if ($this->tagList->contains($tag) == false) {
        $this->tagList->add($tag);
        $tag->addService($this);
    }
}

/**
 * @param Tag $tag
 * @return Service
 */
public function removeTag(Tag $tag)
{
    if ($this->tagList->contains($tag)) {
        $this->tagList->removeElement($tag);
        $tag->removeService($this);
    }
    return $this;
}
}

Tag class

 {
  /**
 * @ORM\ManyToMany(targetEntity="Service", inversedBy="tagList")
 * @ORM\JoinTable(name="tags_services")
 */
private $serviceList;
 /**
 * @param Service $service
 * @return Tag
 */
public function addService(Service $service)
{
    if ($this->serviceList->contains($service) == false) {
        $this->serviceList->add($service);
        $service->addTag($this);
    }
    return $this;
}

/**
 * @param Service $service
 * @return Tag
 */
public function removeService(Service $service)
{
    if ($this->serviceList->contains($service)) {
        $this->serviceList->removeElement($service);
        $service->removeTag($this);
    }
    return $this;
}
 }

ServiceController

  public function newAction(Request $request)
{
    $service = new Service();
    $form = $this->createForm('AppBundle\Form\ServiceType', $service);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        $em = $this->getDoctrine()->getManager();
        $em->persist($service);
        $em->flush();

        return $this->redirectToRoute('service_show', array('id' => $service->getId()));
    }

    return $this->render('AppBundle:Service:new.html.twig', array(
        'service' => $service,
        'form' => $form->createView(),
    ));
}
like image 840
blahblah Avatar asked Feb 15 '17 10:02

blahblah


3 Answers

Could you please try to implement code from this URL?

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#owning-and-inverse-side-on-a-manytomany-association

First, please try to change mapped/inverse sides, and remove $service->addTag($this); from Tag::addService method.

like image 185
michail_w Avatar answered Oct 24 '22 04:10

michail_w


Short version:

I just ran into this problem and solved it by adding a setter for the affected property:

Could not determine access type for property "tagList"

public function setTagList(Array $tagList)
{
    $this->tagList = $tagList;
}

Long version:

The error message is signaling that Symfony is trying to modify the object's state, but cannot figure out how to actually make the change due to the way its class is set up.

Taking a look at Symfony's internals, we can see that Symfony gives you 5 chances to give it access and picks the best one in this order from top to bottom:

  1. A setter method named setProperty() with one argument:

This is the first thing Symfony checks for and is the most explicit way to achieve this. As far as I'm aware this is the best practice:

class Entity {

    protected $tagList;

    //...

    public function getTagList()
    {
        return $this->tagList;
    }

    //...
}
  1. A combined getter and setter in one method with one argument:

It's important to realize that this method will also be accessed by Symfony in order to get the object's state. Since those method calls don't include an argument, the argument in this method must be optional.

class Entity {

    protected $tagList;

    //...

    public function tagList($tags = null)
    {
        if($reps){
            $this->tagList = $tags;
        } else {
            return $this->tagList;
        }
    }

    //...
}
  1. The affected property being declared as public:

    class Entity {
    
        public $tagList;
        //... other properties here
    }
    
  2. A __set magic method:

This will affect all properties rather than just the one you intended.

class Entity {

    public $tagList;

    //...

    public function __set($name, $value){
        $this->$name = $value;
    }
    //...
}
  1. A __call magic method (in some cases):

I wasn't able to confirm this, but the internal code suggests this is possible when magic is enabled on PropertyAccessor's construction.


Only using one of the above strategies is required.

like image 3
HPierce Avatar answered Oct 24 '22 04:10

HPierce


Maybe the problem is that Symfony can't access that property?

If you look at where that exception is thrown (writeProperty method in the PropertyAccessor class) it says it can be thrown:

If the property does not exist or is not public.

In the tutorial you mentioned it has property $tags, and method addTag. I'm just guessing here, but maybe there's a convention where it tries to call a method names add($singularForm) and this is failing for you because the property is tagList and the method is addTag.

I'm not 100% sure, but you could try debugging by setting a stop point in that Symfony method to see why it's being thrown.

like image 2
mickadoo Avatar answered Oct 24 '22 05:10

mickadoo