Question

This question is a case in hand.

Let's imagine a website page that displays a filtered products list :

  1. If I request /products?foo=bar in my browser, I get a full page wrapped in a layout, the layout contains a header with a form allowing to change search criterias.

  2. If I change foo value to baz and submit the form, it reloads dynamically the results (with a jQuery $.load(), for example), calling /products?foo=baz in Ajax.

  3. If I enter the same criteria into my iPhone app, the app requests /products?foo=bar to the server, which replies with a json object.

All 3 calls refer to the same resource ("the products list", resource is intended as in REST resource), but all need different rendering.

  1. In the first case, I need a full HTML page (with layout)
  2. In the second case, I need a partial HTML page (without layout)
  3. In the third case, I need a plain json response

This rendering switch can be triggered by the headers X-Requested-With (with or without layout) and Accept (html/json).

In Phalcon, the given example could be implemented as :

class ProductsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {
        // Build data
        $products = Products::find(array(
            'foo' => $this->request->get('foo')
        ));

        // JSON is identified
        if ($this->request->getBestAccept() == 'application/json')
        {
            // JSON rendering (case 3)
            $this->response->setContentType('application/json');
            $this->response->setJsonContent($products);
            $this->view->disable();
            return $this->response;
        }
        // Otherwise, defaults to HTML
        else
        {
            // Passing data to the view
            $this->view->products = $products;

            // Ajax call is identified
            if ($this->request->isAjax())
            {
                // Partial rendering (case 2)
                $view->disableLevel(array(
                    View::LEVEL_LAYOUT => true,
                    View::LEVEL_MAIN_LAYOUT => true
                ));
            }
            // Defaults to normal rendering
            else
            {
                // Layout rendering (case 1)
            }
        }
    }

}

How could I implement this mechanism application-wide, rather than duplicate this behavior in every single action ?

Was it helpful?

Solution

The easiest would be to get your data together in a unified format (if it isn't already) and pass it to a method in your base controller to handle that logic in pretty much the same way you showed in your example.

abstract class AbstractController extends \Phalcon\Mvc\Controller
{

    protected function prepareResponse($data)
    {

        // JSON is identified
        if ($this->request->getBestAccept() == 'application/json')
        {
            // JSON rendering (case 3)
            $this->response->setContentType('application/json');
            $this->response->setJsonContent($data);
            $this->view->disable();
            return $this->response;
        }

        // Otherwise, defaults to HTML

        // Passing data to the view
        $this->view->setVars($data);

        // Ajax call is identified
        if ($this->request->isAjax())
        {
            // Partial rendering (case 2)
            $view->disableLevel(array(
                View::LEVEL_LAYOUT => true,
                View::LEVEL_MAIN_LAYOUT => true
            ));
        }
        // Defaults to normal rendering
        else
        {
            // Layout rendering (case 1)
        }

        return null;
    }
}

class ProductsController extends AbstractController
{

    public function indexAction()
    {
        // Build data and prepare response
        return this->prepareResponse(array(
            'products' => Products::find(array(
               'foo' => $this->request->get('foo')
            ))
        ));
    }
}   

Otherwise, it might be a bit tricky. I see the following alternative scenarios.

Extend your base controller with, say, renderJson and renderPartial methods, to handle those bits of logic (default otherwise), but in your action you still would need to check which one to call. Duplication…

Do some crazy shit with the router to go to different action, e.g., indexJsonAction and indexPartialAction (default otherwise), depending on the conditions. But you'd end up with bloated controllers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top