We are moving our forum in GitHub Discussions. For questions about Phalcon v3/v4 you can visit here and for Phalcon v5 here.

Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

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.6k
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.