1

I want to create a form for an entity, which holds two collections of the same entity, but each collection holds entitites with a specific value in one property.

My database tables look like this:

Table 1: base
| id | foo | bar |
|----|-----|-----|

Table 2: extended
| id | table_1_id | type | baz |
|----|------------|------|-----|

Table 1 and 2 are in a 1:n-relationship, so the "base" entity can hold any amount of "extended" entities.

I already defined the entities:

class Base {
    private $id;
    private $foo;
    private $baz;
    private $extendedCollection;

    /*...*/

    public function getTypeA() {
        $result = new ArrayCollection();
        foreach ($extendedCollection->toArray() as $item) {
            if ($item->getType() == "A") {
                $result->add($item);
            }
        }
        return $result;
   }
   /* respective for type "B" */

   public function addExtendedA(Extended $a) {
       if (!$this->extendedCollection->contains($a)) {
           $a
               ->setBase($this)
               ->setType("A");
           $this->extendedCollection->add($a);
       }
       return $this;
    }
    /* respective for type "B" */
}

class Extended {
    private $id;
    private $base;
    private $type;
    private $baz;

    /*...*/
}

Finally, I also created two form classes:

class BaseType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('foo')
            ->add('bar')
            ->add('typeA', CollectionType::class, array(
                'entry_type' => ExtendedType::class,
                'entry_options' => array(
                    'type' => "A"
                ),
                'allow_add' => true,
                'allow_delete' => true,
                'required' => false,
                'by_reference' => false
            )
            ->add('typeB', CollectionType::class, array(
                'entry_type' => ExtendedType::class,
                'entry_options' => array(
                    'type' => "B"
                ),
                'allow_add' => true,
                'allow_delete' => true,
                'required' => false,
                'by_reference' => false
            );

            /*...*/
    }
}

class ExtendedType extends AbstractType {

    private $type;

    public function buildForm(FormBuilderInterface $builder, array $options) {

        $this->type = $options['type'];`enter code here`
        $builder
            ->add('type', HiddenType::class, array(
                'data' => $this->type
            )
            ->add('baz');
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults(array(
            'data_class' => Extended::class,
            'type' => ""
        ));
    }
}

The desired outcome is the following: When a "Base" entity is requested, the database content is parsed as an entity, and the ArrayCollection of "Extended" entities is being sorted into two collections "A" and "B" by the type of the collection item, and both collections are rendered as separate form lists. This works.

When a "Base" entity is updated, both "Extended" collections should be fused to the new extendedCollection, and the entity should be persisted. This doesn't work.

If I manually add some extended rows to the database, I can render the template and everything is shown - however, when I try to apply some changes via the HTML form, a NoSuchPropertyException is thrown, where the message says Could not determine access type for "typeA". What exactly am I missing? Is there something I can do to optimize this model?

UPDATE 14.09.2017 I added a setter function to apply new "Extended" Collections:

class Base {
    /* see above for reference code */

    public function setTypeACollection(ArrayCollection $typeAExtended)
    {
        $this->setExtendedCollection($typeAExtended, "A");
        return $this;
    }
    /* Respective for type "B" */

    private function setExtendedCollection(ArrayCollection $newExtended, $type)
    {
        $newExtendedCollection = new ArrayCollection();
        $extendedArray = $this->extendedCollection->toArray();
        foreach ($extendedArray as $k => $v) {
            if ($v->getType() == $type) {
                unset($extendedArray[$k]);
            } else {
                $newExtendedCollection->add($v);
            }
        }
        foreach ($newExtended->toArray() as $newExt) {
            $newExt->setType($type);
            $newExtendedCollection->add($item);
        }
        $this->extendedCollection = $newExtendedCollection;
    }
}

Now, the NoSuchPropertyExceptionis gone, however a new problem persists: When I load the Base entity from the database, the Extended collections are applied clean, but updating the entity via HTML form results in the following errors depending on whether there were changes or not:

  • No Changes: The entity is updated in the wrong way, as all Type "B" are turned into Type "A", und thus are all displayed in the Type "A"-Collection.
  • Deleting a Type "B": The deleted Type "B" is turned into a Type "A" instead of being deleted.
  • Deleting a Type "A": The deleted Extended stays, and in addition all Type "B" are turned into Type "A".
  • Adding a Type "A": The added Extended is not persisted, and in addition all Type "B" are turned into Type "A".
  • Adding a Type "B": An ORMInvalidArgumentException is thrown, the message is A new entity was found through the relationship 'Acme\Bundle\Entity\Base#extendedCollection' that was not configured to cascade persist operations for entity: Acme\Bundle\Entity\Extended@0000000011153bff000000006bb39f24. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'Acme\Bundle\Entity\Extended#__toString()' to get a clue.

My Extended class contains the annotation @ORM\ManyToOne(targetEntity="Acme\Bundle\Entity\Base", inversedBy="extendedCollection", cascade={"persist"}) at the referencing $base property.

1 Answer 1

1

The form fields are mapped from the underlying object/class of the form, so Could not determine access type for "typeA" refer to missed property typeA in your Base class when the Form Component tried to map the submitted value to this property.

Try to solve it adding setTypeA(Collection $items) method in your Base class and update manually your extendedCollection property according to type.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.