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

How can I call the current controllers methods from within the beforeRender event?

I am trying to figure out how to use the event manager for this task.

Basically, all of my controllers extend from AppController. AppController has a method, isPjax that returns true or false based on if the request was made by my jQuery Pjax plugin. If the request is pjax, then the controller will by default set $render_for_pjax to true. Each individual controller is able to set this to false if it wishes.

Using the beforeRender event of the view, I would like to grab the current controller, check if $render_for_pjax is true or false, and call the prepareForPjax method if it is true.

In the code below, how can I access the controller instance within this event callback?

Thank you.

// Set up the view component.
$di->set('view', function() {

  $view = new \Phalcon\Mvc\View();

  $events_manager= new \Phalcon\Events\Manager();

  // Attach event listener to view.
  $events_manager->attach('view', function($event, $view) {
      if($event->getType() == 'beforeRender') {

          // I want to use the current view's controller right here.

      }
  });

  $view->setViewsDir('../app/views/');

  $view->setEventsManager($events_manager);

  // Registering Volt as template engine
  $view->registerEngines([
      ".phtml" => 'Phalcon\Mvc\View\Engine\Php'
  ]);

  return $view;
}, true);


6.9k

The \Phalcon\Mvc\View has a method getControllerName and getActionName that should be able to do what you want, I think.



12.6k

Hello, thanks for the response.

I saw that the view has those methods, however, they seem to simply return the name. I'm looking to call actual methods on the call, so I need some way to access the actual instance of the controller that is being used. Do you know what I mean?



6.9k
Accepted
answer

Ahh, well, in that case you're looking for $di->getShared('dispatcher')->getActiveController(), make sure to add $di through use.

  // Attach event listener to view.
  $events_manager->attach('view', function($event, $view) use ($di) {
      if($event->getType() == 'beforeRender') {

          $controller = $di->getShared('dispatcher')->getActiveController();

      }
   });

From an architectural level this is not the best way to do things though, keep that in mind. Controllers should be thin, not contain a lot of logic.



12.6k
edited Nov '14

Ahh, well, in that case you're looking for $di->getShared('dispatcher')->getActiveController(), make sure to add $di through use.

 // Attach event listener to view.
 $events_manager->attach('view', function($event, $view) use ($di) {
     if($event->getType() == 'beforeRender') {

         $controller = $di->getShared('dispatcher')->getActiveController();

     }
  });

From an architectural level this is not the best way to do things though, keep that in mind. Controllers should be thin, not contain a lot of logic.

Excellent, that sounds like it may be the ticket. I'm going to try this out momentarily.

Okay, I'm completely open to architectural advice - I'd appreciate it if you suggest a better way.

I explained my goal in my initial post, but I will summarize it:

I am using a jQuery plugin that allows links to simply swap out the content area of the page instead of navigate to a new page. When it does this, it attaches a header to the request so that the server knows to return only the content of the page as opposed to the entire HTML structure.

So I need to do this:

  • On the start of the request, if the header is present, set a isPjax = true flag and a respondPjax = true flag. These mean that the request was sent by the plugin and that my views should render accordingly.
  • Allow myself to set the respondPjax flag to false, meaning my views are rendered as if it's a regular request.
  • Before rendering begins, if isPjax and respondPjax are both true, disable the templates, layouts, etc, and render only the actions view.

This should all be handled without the need for a controller action to even know what kind of request it is by disabling layouts automatically when the header is present, but allowing the actions to cancel this if neccesary.

My thought was these could simply be a couple methods and variables on a BaseController that other controllers extend from.

Do you have a different idea?



6.9k
edited Nov '14

No, not at all. I think you're spot on, you're just over-engineering. In the follow example keep in mind that I've never used pJax and am unfamiliar with its workings so I may be off here or there.

Assuming:

BaseController extends \Phalcon\Mvc\Controller
{
    // default to true since we're also testing isAjax()
    protected $respondPjax = true;

    public function initialize()
    {
        // if it's an ajax call, respond with just the action template
        if ($this->request->isAjax() && $this->respondPjax) { 
            // potentially switch out the isAjax for a $request->getHeader('HTTP_YOUR_HEADER_HERE') check
            // to only catch specific calls based on the header.

            $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
        }
    }
}

PageController extends BaseController
{
    public function fooAction()
    {
       // regulary returns the "foo" template inside the "page" layout inside the main layout.
       // but when we're performing an ajax call, determined by isAjax, we're only returning the
       // the "foo" template.
    }

    public function barAction()
    {
        $this->respondPjax = false;
        // Always returns the entire layout structure
    }
}

That should do it, I think. You don't need the event manager in the view anymore, you only need the one variable. Unless I'm overlooking an essential part of your problem..



12.6k
edited Nov '14

I can see what you mean. Perhaps I can simplify things.

Here is my question about your implementation: Isn't initialize() called before the action executes? Which would mean even though barAction sets respondPjax to false, the render level will have already been changed?

The only reason I looked into events in the first place is I was looking for a way to default to true before the action, allow modification during the action, and then change the render level if respondPjax is true after the action.



6.9k

damnit, that's what I get for trying to be fast. My bad, sorry!

Instead perhaps try:

BaseController extends \Phalcon\Mvc\Controller
{
    // default to true since we're also testing isAjax()
    protected $respondPjax = true;

    public function __destruct()   // or if that doesn't work   public function afterExecuteRoute($dispatcher)
    {
        // if it's an ajax call, respond with just the action template
        if ($this->request->isAjax() && $this->respondPjax) { 
            // potentially switch out the isAjax for a $request->getHeader('HTTP_YOUR_HEADER_HERE') check
            // to only catch specific calls based on the header.

            $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
        }
    }
}

The PageController is unchanged in this. Only thing here is that I'm not sure if the view is rendered before the controllers teardown happens.



12.6k
edited Nov '14

That does it - nice and simple.

__destruct() isn't called until after the view is rendered, but afterExecuteRoute() works well. Thanks for bringing that up - I wasn't sure when exactly that executed.

Appreciate you answering my question and then showing me a better way.

I've updated this question also so anyone who stumbles into that question sees the solution I ended up going with.

Thanks again



6.9k

No problem, happy to be of service!