Skip to content

Instantly share code, notes, and snippets.

@weaverryan
Created January 3, 2011 14:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weaverryan/763507 to your computer and use it in GitHub Desktop.
Save weaverryan/763507 to your computer and use it in GitHub Desktop.
WIP Symfony2 View Documentation with notes

The View

For each request in Symfony2, the goal of the developer is always the same: to construct and return a Response object that represents the resource being requested. This is most obvious inside a controller, which almost always returns a Response object:

public function indexAction($name)
{
    // create a Response object directly
    return new Response('<html><body>Hello '.$name.'!</body></html');

    // create a Response object using the content from a template
    return new Response($this->renderView(
        'MyBundle:Mycontroller:index.twig',
        array('name' => $name)
    ));
}

Though optional, Symfony also provides a thin "view" layer: a centralized object that governs the preparation of the Response object by rendering templates and performing other actions. Specifically, the view allows the same logic to be used to create a Response whose content is HTML, JSON, XML or any other format.

public function indexAction($name)
{
    return $this->handle(array('name' => $name), 'MyBundle:MyController:index.twig');
}

At the surface, the handle() method simply renders the MyBundle:MyController:index.twig template and passes the $name variable to it. In reality, however, the process is much more powerful and is handled by the Symfony\\Bundle\\FrameworkBundle\\View\\DefaultView object.

The View with different Request Formats

The DefaultView object behaves differently based on the request format. By default, three request formats are supported

  • html: The given template is rendered and its content is used to create and return a the Response object;
  • json: The parameters are transformed into a json-encoded string and used to create and return the Response object. See Transforming Parameters to JSON;
  • xml: The parameters are transformed into an XML document and used to create and return the Response object. See Transforming Parameters and XML.

Support for any number of other formats can be added (see Custom Format Handler).

In our example, the three formats would be handled in the following ways:

  • html The MyBundle:MyController:index.twig is rendered normally;
  • json: The array('name' => $name)) is json-encoded and the resulting string is used to populate the Response object;
  • xml: The array('name' => $name)) is transformed into a simple XML document and used to populate the Response object.

This allows the same controller to return any number of different formats, without needing to modify the controller code. As a developer, it also gives you the power to choose how to process and handle specific formats on an application-wide (Using a Custom View) or controller-specific (Custom Format Handler) basis.

Global Parameters

The centralized view object also allows for any number of parameters to be passed into every template. These global parameters can be set in the view configuration and then accessed inside any template that uses the view layer. See Configuration for more information.

Configuration

// Notes on the configuration options (e.g. global parameters) when there // are such options

Custom Format Handler

By default, DefaultView handles three different formats: html, json, and xml. To override the default behavior for these formats, or to add support for new formats, custom format handlers can be registered with the view service. A custom handler is a PHP callback that will be invoked whenever the view attempts to handle a specific format:

public function indexAction($name)
{
    $this->get('view')->registerHandler('json', array($this, 'handleJson'));

    return $this->handle(array('name' => $name), 'MyBundle:MyController:index.twig');
}

When the request format is json, the method handleJson will be called on the controller object. Suppose that we'd like to render the MyBundle:MyController:index.twig template and use it to build a JSON array:

public function handleJson(DefaultView $view, Request $request, Response $response)
{
    $content = $this->renderView($view->getTemplate(), $view->getParameters());
    $json = json_encode(array('content' => $content, 'timestamp' => time()));
    $response->setContent($json);

    return $response;
}

The job of a custom handler is to prepare and return the Response object by creating and setting content in the appropriate format. Here, we populate the Response with a json-encoded string with the content from the template and a timestamp that might be used by client-side Javascript.

Tip

In order to type-hint the DefaultView, Request and Response objects in the handleJson method, the following would need to be registered at the top of the controller class:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\View\DefaultView;

Handling Redirects

In addition to creating content, the view is also responsible for processing redirects. Consider the following example:

public function updateAction($slug)
{
    // .. perform some update logic

    return $this->redirectToRoute('article_show', array('slug' => $slug));
}

In all formats, the default behavior is to create and return a Response object with a Location header and a 301 or 302 status code. This triggers the default redirect behavior and directs the client's browser to redirect to the given page.

This behavior can be controlled on a format-by-format basis. Let's revisit the custom handler handleJson from earlier and add some redirect logic to it:

public function handleJson(DefaultView $view, Request $request, Response $response) { if ($redirect = $view->getRedirect()) { $response->setRedirect($redirect['location'], $redirect['status_code']); return $response; }

// ... the remainder of the handling

}

If the original action method sets a redirect via redirectToRoute or redirectToUri, the information is stored in the $view variable. In the above code, we've implemented the default redirect behavior: the redirect is set on the Response object and returned.

Let's change the behavior and return a JSON-encoded array instead of redirecting. This may be more advantageous if the response is being returned to client-side Javascript code:

public function handleJson(DefaultView $view, Request $request, Response $response) { if ($redirect = $view->getRedirect()) { $json = json_encode(array('redirect' => $redirect['location'])); $response->setContent($json);

return $response;

}

// ... the remainder of the handling

}

In this case, if the request format is JSON, a JSON-encoded array will be returned with a status code of 200. Your client-side Javascript can handle the redirect however you choose.

Transforming Parameters to JSON

Transforming Parameters and XML

Creating your own View

Notes & Questions

Overall, I think this is a nice layer, but I'm afraid that it does too much out-of-the-box. See my notes below.

  • This seems to render the core.view event either useless or "second-class". That irks me for some reason.
  • Seems strange that I say I want to render a template, but then because my request format is json, my variables are just json'ed up and the template is never called (and no error). I don't like this.
  • I don't like that I can now, by default, only use HTML, XML or JSON. If I try something else, it's going to throw an Exception.
  • The reset() seems strange. Why do this? - what's wrong with me using my view layer multiple times (apart from probably not really making a ton of sense). It seems like if you want your object to behave like this, everything being reset should just be passed to the handle() method so that the whole process doesn't mutate the view.
  • In the controller, why make the handle() method have a different order of arguments than the renderView() method?
  • This really doesn't work well (at least for the purposes of documentation) if we keep automatically set the format in the template.format.twig. It just seems like we've got two totally different solutions for multiple formats. So, if this is merged, that's gotta go imho.
  • Unfortunate that I need several lines of code to show how you'd render a template through the view layer. That's one of the advantageous of the templating layer - I can show you how to use it directly in just one line (i.e. it documents really well).
  • Why would I pass a non-array as a parameter (e.g. a DOMDocument, or object)? Doesn't this make it impossible for other formats to work? For example, if I pass an object as the parameter, I can use the view for the JSON format, but not for XML or HTML (both transform functions can't handle an object). I'm having trouble seeing the use-case for this.
  • It seems inconsistent (though I empirically understand why) that the globalParameters are used only for the HTML format.
  • What about the DIC extension and tests?
  • I do really like the custom handlers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment