Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The buildView() Method

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 $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The autocomplete setup works nicely on the edit page. But, if you click to create an article... it looks like it's working, but it's not! This is just the normal autocomplete from my browser.

There's no JavaScript error and we do have the class and the data- attribute. We expected this: we just... haven't added the JavaScript to this page!

In edit.html.twig, the javascripts and stylesheets blocks bring in the magic. Let's solve this in the simplest way possible. Copy both of these blocks. Open new.html.twig and paste! Oh, and I mentioned earlier, that we're going to eventually tweak things so that the author field is only filled in on create: we're going to disable it on edit.

... lines 1 - 2
{% block javascripts %}
{{ parent() }}
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.jquery.min.js"></script>
<script src="{{ asset('js/algolia-autocomplete.js') }}"></script>
{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/algolia-autocomplete.css') }}">
{% endblock %}
... lines 15 - 23

That means... we won't need any of this stuff on the edit page. Let's delete it now. But, if you did need some JavaScript and CSS on both templates and you did not want to duplicate the blocks, you could create a new template, like article_admin_base.html.twig. It would extend content_base.html.twig and include the javascripts and stylesheets blocks. Then, edit.html.twig and new.html.twig would extend this.

Anyways, now that the JavaScript and CSS live in the new template, when we refresh, we have autocomplete.

The buildView() Form Class Method

Before we move on, I have one more cool thing I want to show you! And, it solves a real problem... just not a problem we realize we had yet. Close a few files then go to UserSelectTextType. The whole autocomplete system works because we are setting the attr option with class and data-autocomplete-url keys. Now open ArticleFormType where we use this field type. One of the things that we're allowed to do here is override that attr option. But, if we did that, our custom attr option would completely replace the attr default from the type class! In other words, we would lose all of the special attributes that we need!

To fix this, at the bottom of UserSelectTextType, go to the Code -> Generate menu, or command+N on a Mac, select Override methods and choose buildView(). Oh, there's also a method called finishView() and its purpose is almost identical to buildView() - it's just called a bit later.

... lines 1 - 14
class UserSelectTextType extends AbstractType
... lines 17 - 48
public function buildView(FormView $view, FormInterface $form, array $options)
... lines 51 - 57

Here's what's going on: to render each field, Symfony creates a bunch of variables that are used in the form theme system. We already knew that: in register.html.twig we're overriding the attr variable. And in our form theme blocks, we use different variables to do our work.

And, of course, we know that, thanks to the profiler, we can see the exact view variables that exist for each field. But... where do these variables come from? For example, why does each field have a full_name variable? Who added that?

The answer is buildView(): Symfony calls this method on every field, and it is the place where these variables are created and can be changed.

We do that with this $view variable, which is kind of a strange object. Start with $attr = $view->vars['attr'];. This $view object has a public ->vars array property that holds all of the things that will eventually become the "variables". At this moment, the core form system has already set this variable up for us: it will either be equal to the attr option passed for this field, or an empty array.

... lines 1 - 48
public function buildView(FormView $view, FormInterface $form, array $options)
$attr = $view->vars['attr'];
... lines 52 - 57
... lines 59 - 60

Next: grab the class: if class is set on $attr, use it, but add a space on the end. If there is no class yet, set this to be blank. Now, here's the key: let's always append js-user-autocomplete: that's the class we're using above. Call $attr['class'] = to set the new class string back on.

... lines 1 - 48
public function buildView(FormView $view, FormInterface $form, array $options)
... line 51
$class = isset($attr['class']) ? $attr['class'].' ' : '';
$class .= 'js-user-autocomplete';
$attr['class'] = $class;
... lines 56 - 57
... lines 59 - 60

Oh, and we also need to add the data-autocomplete-url attribute. Copy that from above and say $attr['data-autocomplete-url'] equals the generated URL. Perfect! Finally, set all of this back onto the view object with $view->vars['attr'] = $attr.

... lines 1 - 48
public function buildView(FormView $view, FormInterface $form, array $options)
... lines 51 - 55
$attr['data-autocomplete-url'] = $this->router->generate('admin_utility_users');
$view->vars['attr'] = $attr;
... lines 59 - 60

Phew! We're done! Now that we're setting the attr variable directly, we don't need to set the option anymore. And the best part is that we know our attributes will be rendered no matter what the user passes to the attr option.

Let's try it! Move over, refresh and cool! Nice work team! The element still has the attributes we need.

Oh, and open the profiler for this form. Click on the author field and check out the View Variables. So cool! That's exactly what we set!

Next: the form component has a crazy powerful plugin system. Want to make some tweak to every form or even every field in your entire app? That's possible, and it's fun!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.6
        "symfony/console": "^4.0", // v4.1.6
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.1.6
        "symfony/framework-bundle": "^4.0", // v4.1.6
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.6
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.6
        "symfony/validator": "^4.0", // v4.1.6
        "symfony/web-server-bundle": "^4.0", // v4.1.6
        "symfony/yaml": "^4.0", // v4.1.6
        "twig/extensions": "^1.5" // v1.5.2
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
        "symfony/dotenv": "^4.0", // v4.1.6
        "symfony/maker-bundle": "^1.0", // v1.8.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.6