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

Getting different memory references on beforeRenderView for views and partials; implementing a View Composer

I'm trying to implement something which Laravel refers to as a View composer. Summarily, when 'partials/top.volt' is loaded, the assigned composer class or closure should fire and attach variables to the view using setVar.


//services - Event manager for views
...
  $eventsManager = $di->getShared('eventsManager');
  $eventsManager->attach('view', new ViewComposerLoader());
...

//services - this will make sense after reading the others
$di->set('viewComposer', function() use($di) {
  //Load up composers
  $composer = new ViewComposer();
  $composer->compose('partials/top', $di->get('TopComposer'));

  return $composer;
}, true);

//This is the event handling class...
class ViewComposerLoader extends Plugin {
  public function beforeRenderView($event, $view, $viewPath) {
    $viewName = viewName($this->view->getViewsDir(), $viewPath);
    $composer = $this->getDI()->get('viewComposer');
    $composer->load($viewName, $view);
  }
}

//Which calls this:
class ViewComposer {
  protected $composers = array();

  public function compose($view, $composer) {
    array_push($this->composers, [$view, $composer]);
  }
  public function load($viewName, View $view) {
    foreach($this->composers as $arr) {
      if($viewName == $arr[0]) {
        if($arr[1] instanceof \Closure) {
          call_user_func($arr[1], $view);
        } else if($arr[1] instanceof \ComposerInterface) {
          $arr[1]->compose($view);
        }
      }
    }
  }
}

//Which finally calls this:
class TopComposer extends ComposerBase {
  ....
  public function compose(View $view) {
    $view->setVar('test', 1);
  }
}

//And a little helper
function viewName($viewDir, $viewPath) {
  $parts = explode('.', substr($viewPath, strlen($viewDir)));
  if(count($parts) > 1) array_pop($parts);
  return join('.', $parts);
}

However, this isn't working because when beforeRenderView is called, the memory reference held by the $view variable is different for the main view and the partial views. In other words, if you attach a variable to a partial in the beforeRenderView event, it is NOT going to be reflected when the final view is assembled. Any idea on how get around this or is this intrinsically insurmountable?

Edit: This is what I mean by 'different memory references'

Bump - I'd really love if I could get an answer to this.

edited Jun '14

I just overflew the post. Is your view service shared ?

Maybe it could help to solve

Yes, it is.


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

  $view = new View();

  $eventsManager = $di->getShared('eventsManager');
  $eventsManager->attach('view', new ViewComposerLoader());

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

  $view->registerEngines(array(
    '.volt' => function ($view, $di) use ($config) {

        $volt = new VoltEngine($view, $di);

        $volt->setOptions(array(
          'compiledPath' => $config->application->cacheDir,
          'compiledSeparator' => '_',
          'compileAlways' => true
        ));

        return $volt;
      },
    '.phtml' => 'Phalcon\Mvc\View\Engine\Php'
  ));
  $view->setEventsManager($eventsManager);

  return $view;
}, true);