Problems of upgrading 3.2 to 4.0 or `volt` registering problem....

I'm trying to upgrade Phalcon 3.2.4 to 4.0, and got a problem: In my volt template :{{ flashSession.output() }}, it says:

PHP message: PHP Fatal error:  Uncaught Error: Call to a member function output() on null in /.../var/volt/_var_www_app_views_users_login.volt.php:49
Stack trace:
#0 [internal function]: unknown()
#1 [internal function]: Phalcon\Mvc\View\Engine\Volt->render('/var/www/....', Array, true)
#2 [internal function]: Phalcon\Mvc\View->engineRender(Array, 'users/login', true)
#3 [internal function]: Phalcon\Mvc\View->processRender('users', 'login', Array)
#4 [internal function]: Phalcon\Mvc\View->render('users', 'login')
#5 /var/www/..../public/index.php(38): Phalcon\Mvc\Application->handle('/users/login')
#6 {main}
  thrown in /var/www/.../var/volt/_var_www_app_views_users_login.volt.php on line 49" while reading response header from upstream, client: 10.0.10.171, server: default, request: "GET /users/login HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "10.0.10.116:83"

But in the Controllers, the code $this->flashSession->output() works well (but can't get the flash message)!

I think it is the problem of registering volt services? In Controllers, it can gets the $di object, but in the volt it can't gets the $diobjects. How to register the voltservices?

I'm using code style of Phalcon 3.x:

In public/index.php:

include APP_DIR . "/config/services.php";
$application = new Application($di);

In /config/services.php:

$di = new FactoryDefault();
....
$di->set('view', function() use ($config) {

    $view = new View();

    $view->setViewsDir($config->application->viewsDir);

    $view->registerEngines(array(
        '.volt' => function(View $view) use ($config) {
            $volt = new Volt($view);
            $volt->setOptions(array(
                'path' => $config->application->cacheDir . 'volt/',
                'separator' => '_'
            ));
            $volt->getCompiler()->addFunction('__', function ($resolvedArgs, $exprArgs){
                return '__(' . $resolvedArgs . ')';
            });
            return $volt;
        },
        '.phtml' => 'Phalcon\Mvc\View\Engine\Php' //// Generate Template files uses PHP itself as the template engine
    ));
    return $view;
});


26.7k

the code {{ this.flashSession.output() }}can work, but it can't get the flash message

The volt service is being registered just fine. Otherwise you wouldn't be getting this error - which is an error caused by Volt's rendering engine.

The error is saying that it Volt can't find flashSession when you call {{ flashSession.output() }}. Normally I would say check your services file to see if flashSession is registered. But if this is working with v3.2.4, I'm not sure why it's not with v4.0.5.

You said {{ this.flashSession.output() }} runs, but just doesn't output anything. What happens if you put this in your .volt file?

<pre>
<?php print_r($_SESSION); ?>
</pre>

That will at least show you if the message has been stored in $_SESSION



26.7k
edited Mar '20

Thanks for the reply!

the output of $_SESSION is

Array
(
    [$PHALCON/CSRF/KEY$] => N2lGTm5vZHZGanVDK0VzYjhlbmsyQT09
    [phrase-captcha] => v6rl7
    [$PHALCON/CSRF$] => bm9wMUpISk1Ub05wMW8wOSsrRUMyZz09
    [problem_source] => XOJ
)

It doesn't contain any flash message.

Some related code in /config/services.php are:

use Phalcon\Session\Adapter\Stream as SessionAdapter,
    Phalcon\Session\Manager as SessionManager,
    Phalcon\Flash\Direct as Flash,
    Phalcon\Flash\Session as FlashSession;
...

$di = new FactoryDefault();

$di->set('view', function() use ($config) {

    $view = new View();

    $view->setViewsDir($config->application->viewsDir);

    $view->registerEngines(array(
        '.volt' => function(View $view) use ($config) {
            $volt = new Volt($view);
            $volt->setOptions(array(
                'path' => $config->application->cacheDir . 'volt/',
                'separator' => '_'
            ));
            $volt->getCompiler()->addFunction('__', function ($resolvedArgs, $exprArgs){
                return '__(' . $resolvedArgs . ')';
            });
            return $volt;
        },
        '.phtml' => 'Phalcon\Mvc\View\Engine\Php' //// Generate Template files uses PHP itself as the template engine
    ));
    return $view;
});

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

// If the configuration specify the use of metadata adapter use it or use memory otherwise
$di->set('modelsMetadata', function() use ($config) {
    return new MetaDataAdapter(array(
        'metaDataDir' => $config->application->cacheDir . 'metaData/'
    ));
});

$di->setShared('security', function () {
    $security = new Security();
    // set Work factor (how many times we go through)
    $security->setWorkFactor(12); // can be a number from 1-12
    // set Default Hash
    $security->setDefaultHash(Security::CRYPT_BLOWFISH_Y); // choose default hash
    return $security;
});

/**
 * Crypt service
 */
$di->set('crypt', function () use ($config) {
    $crypt = new Crypt();
    $crypt->setKey($config->application->cryptSalt);
    return $crypt;
});

$di->set('cookies', function () {
    $cookies = new Cookies();
    $cookies->useEncryption(true);
    return $cookies;
}, true);

// Start the session the first time some component request the session service
$di->set('session', function () {
    $handler = new SessionAdapter();
    $session = new SessionManager();
    $session->setAdapter($handler);
    $session->start();
    return $session;
});

// flash css class

$di->set('flash', function() {
    $escaper = new Escaper();
    $flash = new Flash($escaper);
    $flash->setImplicitFlush(false);
    $flash->setcssClasses(array(
        'error' => 'alert alert-danger alert-dark',
        'success' => 'alert alert-success alert-dark',
        'notice' => 'alert alert-info alert-dark',
        'warning' => 'alert alert-dark',
    ));
    return $flash;
});

$di->set('flashSession', function() {
    $escaper   = new Escaper();
    $session   = new SessionManager();
    $files     = new SessionAdapter();
    $session->setAdapter($files);
    $flash = new FlashSession($escaper, $session);
    $flash->setImplicitFlush(false);
    $flash->setcssClasses(array(
        'error' => 'alert alert-danger alert-dark',
        'success' => 'alert alert-success alert-dark',
        'notice' => 'alert alert-info alert-dark',
        'warning' => 'alert alert-dark',
    ));
    return $flash;
});

/**
 * Dispatcher use a default namespace
 */
$di->set('dispatcher', function(){
    $dispatcher = new Dispatcher();
    $dispatcher->setDefaultNamespace('XOJ\Controllers');

    return $dispatcher;
});

BTW, the dispatcher service also can't work. {% if dispatcher.getControllerName()==.... %} returns:

PHP message: PHP Notice:  Undefined variable: dispatcher

I imagine other services also don't work? It seems your template doesn't have access to the Dependency Injector.

If you're geting that message displayed to the screen instead of a rendered template, then perhaps you were right in the first place and this is a Volt problem. If Volt were rendering the template, it would supress regular output (ie: notices).

Is your template file named [something].volt or [something].phtml? If the latter, then you're using raw PHP as the template engine, which would probably explain why the Dependency Injector isn't available. Try changing the extension to .volt.

If that's not the case, another thing I noticed is this line:

$volt = new Volt($view);

Are you including this line:

use \Phalcon\Mvc\View\Engine\Volt

at the top of the file? If not then the Volt engine isn't being loaded as you're not specifying the namespace. Either include that line or use the full namespaced class name when creating the new object.



26.7k
edited Mar '20

Yes my template file named *.volt, and the result of complied file named something like this _var_www_myname_app_views_users_login.volt.php automatically, and the code of *.volt template mixed with volt grammer and php code, somethings like this:

{% if page.items is defined %}
<div class="row">
    <div class="col-sm-12">
        <div class="panel colourable">
            <div class="panel-body text-center">
                <ul class="pagination">
                    <li><a href="{{ url(admin_uri~'/problems/list?page='~page.first) }}"><i class="fa fa-angle-double-left"></i></a></li>
                    <?php for ($i=1; $i<=$page->total_pages; $i++) {
                            echo "<li";
                        if ($i == $page->current) echo " class='active'";
                        echo "><a href='" , $this->url->get($admin_uri.'/problems/list?page=') , $i , "'>" , $i , "</a></li>";
                    }?>
                    <li><a href="{{ url(admin_uri~'/problems/list?page='~page.first) }}"><i class="fa fa-angle-double-right"></i></a></li>
                </ul>
            </div>
        </div>
    </div>
</div>
{% endif %}
{% include("partials/testdata") %}

The result of volt template {{ content() }} {{ flashSession.output() }} is <?= $this->getContent() ?> <?= $flashSession->output() ?>, The template seems to be rendered correctly.

Below is the full code of /config/services.php

<?php

use Phalcon\Di\FactoryDefault,
    Phalcon\Di,
    Phalcon\Mvc\View,
    Phalcon\Mvc\View\Engine\Volt,
    Phalcon\Mvc\Dispatcher,
    Phalcon\Url as UrlResolver,
    Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter,
    Phalcon\Mvc\Model\MetaData\Memory as MetaDataAdapter,
    Phalcon\Session\Adapter\Stream as SessionAdapter,
    Phalcon\Session\Manager as SessionManager,
    Phalcon\Cache\Frontend\Output as FrontendCache,
    Phalcon\Cache\Frontend\Data as FrontendData,
    Phalcon\Cache\Backend\File as BackendCache,
    Phalcon\Cache\Backend\Memcache as BackendMemcache,
    Phalcon\Crypt,
    Phalcon\Security,
    Phalcon\Events\Manager as EventsManager,
    Phalcon\Translate\Adapter\Gettext,
    Phalcon\Http\Response\Cookies,
    Phalcon\Escaper,
    Phalcon\Di\DiInterface,
    Phalcon\Di\ServiceProviderInterface,
    Phalcon\Flash\Direct as Flash,
    Phalcon\Flash\Session as FlashSession;

// The FactoryDefault Dependency Injector automatically register the right services providing a full stack framework
$di = new FactoryDefault();

$di->set('router', function(){
    return require __DIR__ . '/routes.php';
}, true);

// The URL component is used to generate all kind of urls in the application
$di->set('url', function() use ($config) {

    $url = new UrlResolver();

    $url->setBaseUri($config->application->baseUri);
    return $url;
}, true);

// Setting up the view component
$di->set('view', function() use ($config) {

    $view = new View();

    $view->setViewsDir($config->application->viewsDir);

    $view->registerEngines(array(
        '.volt' => function(View $view) use ($config) {
            $volt = new Volt($view);
            $volt->setOptions(array(
                'path' => $config->application->cacheDir . 'volt/',
                'separator' => '_'
            ));
            $volt->getCompiler()->addFunction('__', function ($resolvedArgs, $exprArgs){
                return '__(' . $resolvedArgs . ')';
            });
            return $volt;
        },
        '.phtml' => 'Phalcon\Mvc\View\Engine\Php' //// Generate Template files uses PHP itself as the template engine
    ));
    return $view;
});

// Database connection is created based in the parameters defined in the configuration file
$di->set('db', function() use ($config) {
    return new DbAdapter(array(
        'host' => $config->database->host,
        'username' => $config->database->username,
        'password' => $config->database->password,
        'dbname' => $config->database->dbname,
        'charset' => 'utf8'
    ));
});

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

// If the configuration specify the use of metadata adapter use it or use memory otherwise
$di->set('modelsMetadata', function() use ($config) {
    return new MetaDataAdapter(array(
        'metaDataDir' => $config->application->cacheDir . 'metaData/'
    ));
});

// Set the views cache service
/**
$di->set('viewCache', function() use ($config) {
// Cache data for one day by default
$fontCache = new FrontendCache(["lifetime" => 86400]);

// File backend settings
$cache = new BackendCache($fontCache, [
"cacheDir" => $config->application->cacheDir . 'cache/',
]);

return $cache;
});
 */

// Set the models cache service
/*
$di->set('modelsCache', function () {
    // Cache for one day
    $frontCache = new FrontendData(["lifetime" => 86400]);

    // Memcached connection settings
    $cache = new BackendMemcache(
        $frontCache,
        ["servers" => array(array(
            "host" => "localhost",
            "port" => "11211"
        ))]
    );
    return $cache;
});
*/

$di->setShared('security', function () {
    $security = new Security();
    // set Work factor (how many times we go through)
    $security->setWorkFactor(12); // can be a number from 1-12
    // set Default Hash
    $security->setDefaultHash(Security::CRYPT_BLOWFISH_Y); // choose default hash
    return $security;
});

/**
 * Crypt service
 */
$di->set('crypt', function () use ($config) {
    $crypt = new Crypt();
    $crypt->setKey($config->application->cryptSalt);
    return $crypt;
});

$di->set('cookies', function () {
    $cookies = new Cookies();
    $cookies->useEncryption(true);
    return $cookies;
}, true);

// Start the session the first time some component request the session service
$di->set('session', function () {
    $handler = new SessionAdapter();
    $session = new SessionManager();
    $session->setAdapter($handler);
    $session->start();
    return $session;
});

// flash css class

$di->set('flash', function() {
    $escaper = new Escaper();
    $flash = new Flash($escaper);
    $flash->setImplicitFlush(false);
    $flash->setcssClasses(array(
        'error' => 'alert alert-danger alert-dark',
        'success' => 'alert alert-success alert-dark',
        'notice' => 'alert alert-info alert-dark',
        'warning' => 'alert alert-dark',
    ));
    return $flash;
});

$di->set('flashSession', function() {
    $escaper   = new Escaper();
    $session   = new SessionManager();
    $files     = new SessionAdapter();
    $session->setAdapter($files);
    $flash = new FlashSession($escaper, $session);
    $flash->setImplicitFlush(false);
    $flash->setcssClasses(array(
        'error' => 'alert alert-danger alert-dark',
        'success' => 'alert alert-success alert-dark',
        'notice' => 'alert alert-info alert-dark',
        'warning' => 'alert alert-dark',
    ));
    return $flash;
});

/**
 * Dispatcher use a default namespace
 */
$di->set('dispatcher', function(){
    $dispatcher = new Dispatcher();
    $dispatcher->setDefaultNamespace('YZOI\Controllers');
/**
    $eventsManager = new EventsManager();
    $eventsManager->attach("dispatch", function ($event, $dispatcher, $exception) {
        //controller or action doesn't exist
        $object = $event->getData();
        if ($event->getType() == 'beforeException') {
            switch ($exception->getCode()) {
                case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                    $dispatcher->forward([
                        'controller' => 'errors',
                        'action'     => 'index'
                    ]);
                    return false;
                case Dispatcher::EXCEPTION_CYCLIC_ROUTING:
                    $dispatcher->forward([
                        'controller' => 'errors',
                        'action'     => 'show404'
                    ]);
                    return false;
            }
        }
    });
*/
    return $dispatcher;
});

/**
 * Custom authentication component
 */
$di->set('auth', function () {
    return new Auth();
});

$di->set('mailer', function () {
    return new Mailer();
});

$di->set('translation', function () use ($di) {
    $lang = $di->get('auth')->getIdentity();
    $lang = ($lang) ? $lang->display_lang : 'zh';
    $lang_dir = $di->get('config')->application->langDir;

    $controller = $di->get('router')->getControllerName();
    if (! $controller) $controller = 'index';

    require $lang_dir . $lang . '/main.php';
    $lang_addtional = $lang_dir . $lang . '/' . $controller . '.php';
    if (file_exists($lang_addtional))
        require $lang_addtional;

    $translation = new Phalcon\Translate\Adapter\NativeArray(array(
        "content" => $_messages
    ));
    //var_dump($translation);
    return $translation;

}, true);

/*
$di->set('swiftmailer', function() use ($config) {
    include $config->application->libraryDir . 'swift_required.php';
    $transport = Swift_SmtpTransport::newInstance($config->smtp_host)
        ->setUsername($config->smtp_username)
        ->setPassword($config->smtp_password);
    $mailer = Swift_Mailer::newInstance($transport);
    return $mailer;
});*/

/**
 * Translation function call anywhere
 *
 * @param $string
 *
 * @return mixed
 */
if (! function_exists('__')) {
    function __($string, array $placeholder = null)
    {
        $translation = \Phalcon\Di::getDefault()->get('translation');
        return $translation->_($string, $placeholder);
    }
}

Without modify and before the upgrading, all the code works well.

And now, the Db services can work( with CURD works well), the dispatcher and flashSession services can't work.

I'm not sure where is the problem.

edited Mar '20

I'm not sure what the problem is either. It looks like {{ flashSession.output() }} should be rendered to <?php $this->flashSession->output(); ?> but it's not.

Maybe mention this in the Discord channel: https://discordapp.com/channels/310910488152375297/314381544888336386 ? You might get a few interested people taking a look.

I'm using v3.4.5 and created a working, simplified test case with:

$DI = new \Phalcon\DI\FactoryDefault();
// Volt setup omitted for brevity

$DI->set('flash', function() {
    return new \Phalcon\Flash\Direct();
});

$DI->set('flashSession', function() {
    return new \Phalcon\Flash\Session();
});

if(isset($_GET['log'])){
    $DI->get('flashSession')->notice($_GET['log']);
}

I then went to /my/page?log=foobar, then /my/page/, and saw "foobar" outputed.

If that doesn't work for you in v4, then this is likely a Phalcon bug. If it does work, then the problem's with your code somewhere and we can keep digging.



26.7k

Hi Dylan, Thank you very much! According to your code I change to the simplest registion of the services:

$di->set('session', function () {
    $handler = new SessionAdapter();
    $session = new SessionManager();
    $session
        ->setAdapter($handler)
        ->start();
    return $session;
});

// flash css class
$di->set('flash', function() {
    $flash = new Flash();
    $flash->setImplicitFlush(false);
    $flash->setcssClasses(array(
        'error' => 'alert alert-danger alert-dark',
        'success' => 'alert alert-success alert-dark',
        'notice' => 'alert alert-info alert-dark',
        'warning' => 'alert alert-dark',
    ));
    return $flash;
});

$di->set('flashSession', function() {
    $flash = new FlashSession();
    //$flash->setImplicitFlush(false);
    $flash->setcssClasses(array(
        'error' => 'alert alert-danger alert-dark',
        'success' => 'alert alert-success alert-dark',
        'notice' => 'alert alert-info alert-dark',
        'warning' => 'alert alert-dark',
    ));
    return $flash;
});

and this time it works! and the dispatcher service also worked! Although I don't what's the problem

Well, I'd start adding back functionality until it breaks.

This should have no affect on the dispatcher, so I wonder if it's unrelated.