3

I've inherited a website that uses Symfony 2.

The site was working fine until I ran composer to upgrade packages. It upgraded Symfony 2.6 to 2.7.1 and Twig 1.18 to 1.18.2.

I now get the following error:

Method "site" for object "Symfony\Bridge\Twig\AppVariable" does not exist in @Page/Page/homepage.html.twig at line 21

The twig file in question has calls like this:

{{ app.site.name }}

Now obviously site is not a member of the AppVariable type. I'm having trouble figuring out how the original developers managed to extend Twig's app global with a new member. I don't really know where to look. I'm not overly competent with Symfony.

Anthony
  • 12,177
  • 9
  • 69
  • 105
  • You could use a twig global variable instead: http://symfony.com/doc/current/cookbook/templating/global_variables.html I suspect Symfony\Bridge\Twig\AppVariable to be the app variable – DerStoffel Jun 19 '15 at 06:51
  • Yeah, I couldn't figure out what broke. I ended up replacing all of the `{{ app.site.whatever }}` stuff with a twig extension that provided functions for these things. – Anthony Jun 19 '15 at 06:55
  • actually it is http://api.symfony.com/2.7/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.html Try to extend it. – DerStoffel Jun 19 '15 at 07:00

2 Answers2

11

What probably was done by the old developers was overriding the app variable. Until Symfony @2.7 the class was called GlobalVariables and lives in the following namespace - Symfony\Bundle\FrameworkBundle\Templating. As of @2.7 it's called AppVariable and it lives in this namespace - Symfony\Bridge\Twig. What is done behind the scenes?

That class containing the global variables is simply added as twig global variable using the method addGlobal of Twig and for the value of app they inject the whole class as a service that's already defined. GlobalVariables or AppVariables accepts the service container as an argument and defines 5 methods by default that can be used in our templates. I will show you a simple way of extending that class, so you can use it to investigate.

Create simple class that we will define as a service:

<?php

namespace AppBundle\Service;

use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables,
    Symfony\Component\DependencyInjection\ContainerInterface;

class SuperGlobalsExtended extends GlobalVariables {

    /**
     * @var ContainerInterface
     **/
    protected $container = null;

    public function __construct(ContainerInterface $container) {
        $this->container = $container;

        parent::__construct($container);
    }

}

Then register it:

services:
    app.super_globals_extended:
        class: AppBundle\Service\SuperGlobalsExtended
        arguments:
            - @service_container

And last but not least, override the app variable of twig:

twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"
    globals:
        app: @app.super_globals_extended

Now you can define your own methods in your extended class as well as accessing the previous methods that were already here.

For symfony @2.7 is the same, just the class name is different. Here I extend the GlobalVariables, but for @2.7 you have to extend AppVariable.

Definition of the service for @2.7 located in TwigBundle Resources folder.

<service id="twig" class="%twig.class%">
        <argument type="service" id="twig.loader" />
        <argument /> <!-- Twig options -->
        <call method="addGlobal">
            <argument>app</argument>
            <argument type="service" id="twig.app_variable" />
        </call>
        <configurator service="twig.configurator.environment" method="configure" />
    </service>

Hope this helps.

Artamiel
  • 3,652
  • 2
  • 19
  • 24
3

Refer to Artamiels answer as a starting point.

Looks like with sf3, the AppVariable class does no longer need the container (and does not have a parent::__construct) but the interesting vars are being set directly.

So, I did set my class MyAppExtension extends AppVariable with this setup in services.yml:

extended_twig_app:
  class: AppBundle\Twig\MyAppExtension
  public: false
  calls:
    - [setEnvironment, ["%kernel.environment%"]]
    - [setDebug, ["%kernel.debug%"]]
    - [setTokenStorage, ['@security.token_storage']]
    - [setRequestStack, ['@request_stack']]

This is basically xml->yml converted of the coresponding code in the Twig Bundle: https://github.com/symfony/twig-bundle/blob/master/Resources/config/twig.xml

    <service id="twig.app_variable" class="Symfony\Bridge\Twig\AppVariable" public="false">
        <call method="setEnvironment"><argument>%kernel.environment%</argument></call>
        <call method="setDebug"><argument>%kernel.debug%</argument></call>
        <call method="setTokenStorage"><argument type="service" id="security.token_storage" on-invalid="ignore" /></call>
        <call method="setRequestStack"><argument type="service" id="request_stack" on-invalid="ignore" /></call>
    </service>
Community
  • 1
  • 1
Xosofox
  • 840
  • 1
  • 7
  • 21