We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

DI in Volt

Manual says I can use services by its names right in the Volt templates "If a service container (DI) is available for Volt". I believe it should be available if I pass it to Volt constructor. But any service name emits warning about not found variable.

Volt registration code:

    $this->set('volt', function ($view, $di) {
            $volt = new Volt($view, $di);
            print_r($di); // print is ok here
            $opt = [
                'compiledPath'      => APP_ROOT . '/cache/volt/',
                'compiledExtension' => '.compiled'
            ];

            if ($this->settings->dev) {
                $opt['stat'] = true;
                $opt['compileAlways'] = true;
            }

            $volt->setOptions($opt);
            return $volt;
        });

        $this->set('view', function() {
            $view = new View();
            $view->setViewsDir($this->settings->dir->view);
            $view->setDI($this); // added this line for debug – still doesn't work
            $view->registerEngines([
                '.volt' => 'volt'
            ]);
            return $view;
        });

Let's say I want to use service config in Volt:

        $this->set('config', function() {
            return $this->settings;
        });
    {{ dump(config) }}
    {{ dump(di.get("config")) }}

which compiles to

    <?php echo var_dump($config); ?>
    <?php echo var_dump($di->get('config')); ?>

But I got error Undefined variable: config in ... and Undefined variable: di in ... / Call to a member function get() on null in ... respectively.

So it looks like DI is not available for Volt. How to fix it?

Don't know if this related information, but I extended FactoryDefault DI container:

class DI extends FactoryDefault
{
    /** @var Config $settings */
    private $settings;

    function __construct(Config $config)
    {
        parent::__construct();

        $this->settings = $config;

        // all services declarations go here
    }
edited Aug '15

Services are registered on the DependencyInjector instance, not the lambda function of the "service builder". Also, DependencyInjector ($di) is not the same as Injectable ($controller, $task). You cannot use magic properties on a $di, you have to use the methods get set.

You should do something like this, if you really want a custom DI:

// DI.php

class DI extends FactoryDefault
{
    function __construct(Config $config)
    {
        parent::__construct();

        $this->set('config', function() use($config) {
            return $config;
        });

        $this->set('volt', function ($view, $di) {
            $volt = new Volt($view, $di);
            $opt = [
                'compiledPath'      => APP_ROOT . '/cache/volt/',
                'compiledExtension' => '.compiled'
            ];

            //if ($this->settings->dev) {  Context is lambda function, not Injectable nor DependencyInjector!
            if($di->get('config')->dev) {
                $opt['stat'] = true;
                $opt['compileAlways'] = true;
            }

            $volt->setOptions($opt);
            return $volt;
        });

        $this->set('view', function() use($config) {
            $view = new View();
            //$view->setViewsDir($this->settings->dir->view);  Context is lambda function!
            $view->setViewsDir($config->dir->view); // Variable $config comes from use($config), which comes from __construct($config)
            $view->registerEngines([
                '.volt' => 'volt'
            ]);
            return $view;
        });
    }
}

Well, thanks for your answer, but I changed logic to create instance of FactoryDefault instead of inherit it, and error remains the same. Here is simplified version:

class DI
{
    static function Create(Config $config)
    {
        $di = new FactoryDefault();

        $di->set('config', function() use ($config) {
            return $config;
        });

        // database, metadata, sessions and other services here, all of which works fine inside controllers.

        $di->set('volt', function ($view, $di) {
            $volt = new Volt($view, $di);

            $opt = [
                'compiledPath'      => APP_ROOT . '/cache/volt/',
                'compiledExtension' => '.compiled'
            ];

            if ($di->get('config')->dev) {
                $opt['stat'] = true;
                $opt['compileAlways'] = true;
            }

            $volt->setOptions($opt);
            return $volt;
        });

        $di->set('view', function() use ($config) {
            $view = new View();
            $view->setViewsDir($config->dir->view);
            $view->registerEngines([
                '.volt' => 'volt'
            ]);
            return $view;
        });

        return $di;
    }
}

I believe I missed something obvious, but have no clue what is it exactly.



77.7k
Accepted
answer
edited Aug '15

Services are strictly objects, so the Volt compiler expects a property accessor:

// Won't work, NULL
{{ dump(config) }}
// Works, bool
{{ dump(config.dev) }}
// Works, Phalcon\Config
{{ dump(config.dir) }}

Also, the $di isn't registered as a service by default. You'll have to explicitly register it as another service, but I don't think there's point to it.

$di->set('di', function() use($di) {
   return $di;
});

Thanks, access to property works indeed. dump(config) compiles to var_dump($config), while dump(config->dev) compiles to var_dump($this->config->dev).

About second part: no, I don't need to have di service, I just tried to access services through it as I found this construction in another thread about DI in Volt.