CollectionType Field

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $8.00

The form system does a pretty good job guessing the correct field types... but nobody is perfect. For example, the genusScientists field is not setup correctly. Click the clipboard icon to open the form profiler.

Yep, genusScientists is currently an EntityType with multiple set to true. Thanks to EasyAdminBundle, it renders this as a cool, tag-like, auto-complete box. Fancy!

But... that's not going to work here: the GenusScientist entity has an extra field called yearsStudied:

... lines 1 - 17
class GenusScientist
{
... lines 20 - 38
/**
* @ORM\Column(type="integer")
* @Assert\NotBlank()
*/
private $yearsStudied;
... lines 44 - 83
}

When you link a Genus and a User, we need to allow the admin to also fill in how many years the User has studied the Genus.

In the Symfony series, we did a lot of work to create a CollectionType field that used GenusScientistEmbeddedForm:

... lines 1 - 20
class GenusFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
... lines 26 - 48
->add('genusScientists', CollectionType::class, [
'entry_type' => GenusScientistEmbeddedForm::class,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
])
;
... lines 56 - 57
}
... lines 59 - 106
}

Thanks to that, in the admin, we just need to update the form to look like this.

Change genusScientists to use the expanded syntax. From here, you can guess what's next! Set type: collection and then add type_options with the 4 options you see here: entry_type: AppBundle\Form\GenusScientistEmbeddedForm, allow_delete: true, allow_add: true, by_reference: false:

... lines 1 - 80
easy_admin:
... lines 82 - 91
entities:
Genus:
... lines 94 - 114
form:
fields:
... lines 117 - 125
-
property: 'genusScientists'
type: 'collection'
type_options:
entry_type: AppBundle\Form\GenusScientistEmbeddedForm
allow_delete: true
allow_add: true
by_reference: false
... lines 134 - 145

Let's see what happens! Woh! Ignore how ugly it is for a minute. It does work! We can remove items and add new ones.

But it looks weird. When we created this form for our custom admin area - we hid the user field when editing... which looks really odd now. Open the GenusScientistEmbeddedForm. We used a form event to accomplish this: if the GenusScientist had an id, we unset the user field. Comment that out for now and refresh:

... lines 1 - 14
class GenusScientistEmbeddedForm extends AbstractType
{
... lines 17 - 34
public function onPostSetData(FormEvent $event)
{
if ($event->getData() && $event->getData()->getId()) {
$form = $event->getForm();
// unset($form['user']);
}
}
... lines 42 - 50
}

Cool: this section at least makes more sense now.

The CollectionType Problems

But... there are still some problems! First, it's ugly! I know this is just an admin area... but wow! If you want to use the CollectionType, you'll probably need to create a custom form theme for this one field and render things in a more intelligent way. We'll do something similar in a few minutes.

Second... this only works because we already did a lot of hard work setting up the relationships to play well with the CollectionType. Honestly, the CollectionType is both the best and worst form type: you can do some really complex stuff... but it requires some seriously tough setup. You need to worry about the owning and the inverse sides of the relationship, and things called orphanRemoval and cascading. There is some significant Doctrine magic going on behind the scenes to get it working.

So in a few minutes, we're going to look at a more custom alternative to using the collection type.

Virtual Form Field

But first, I want to show you one more thing. Go to the User section and edit a User. We haven't touched any of this config yet. In config.yml, under User, add form then fields. Let's include email and isScientist:

... lines 1 - 80
easy_admin:
... lines 82 - 91
entities:
... lines 93 - 145

Right now, the form has firstName and lastName fields... which makes sense: there are firstName and lastName properties in User. But just like we did earlier under the list view, instead of having firstName and lastName, we could actually have, just fullName. And nope... there is not a fullName property. But as long as we create a setFullName() method, we can totally add it to the form:

... lines 1 - 16
class User implements UserInterface
{
... lines 19 - 225
public function setFullName($fullName)
{
$names = explode(' ', $fullName);
$firstName = array_shift($names);
$lastName = implode(' ', $names);
$this->setFirstName($firstName);
$this->setLastName($lastName);
}
... lines 235 - 279
}

Actually, this isn't special to EasyAdminBundle, it's just how the form system works!

Now... this example is a little crazy. This code will take everything before the first space as the first name, and everything after as the last name. Totally imperfect, but you guys get the idea.

And now that we have getFullName() and setFullName(), add that as a field: property: fullName, type: text and a help message:

... lines 1 - 80
easy_admin:
... lines 82 - 91
entities:
... lines 93 - 145

Keep going to add avatarUri and universityName:

... lines 1 - 80
easy_admin:
... lines 82 - 91
entities:
... lines 93 - 145

Try it out! Yes! It looks great... and... it even submits! Next up, let's add a field that needs custom JavaScript to work.

Leave a comment!

This tutorial is built on an older version of Symfony & EasyAdminBundle. Many of the concepts are the same, but you can expect major differences in newer versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.7
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.17.0
        "sensio/distribution-bundle": "^5.0", // v5.0.25
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.4
        "knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "stof/doctrine-extensions-bundle": "^1.2", // v1.3.0
        "javiereguiluz/easyadmin-bundle": "^1.16" // v1.17.21
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.7
        "symfony/phpunit-bridge": "^3.0", // v3.4.40
        "nelmio/alice": "^2.1", // v2.3.5
        "doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
    }
}