Skip to end of metadata
Go to start of metadata

Zend Layout / View Enhancement Notes

This is a page for my Zend_Layout research and brainstorming.

Zend_View_Enhanced

Layouts

Don't like this implementation. Basically, it requires adding new functionality to Zend_View, which is already getting bloated, abstracts the implementation too much, and requires complex mechanics in the application views in order to aggregate content.

A better solution would be a view decorator that could aggregate content and assign it to a 'layout' view.

Partials

This is solid, and has a verifiable need. Jack Sleight also notes that it would be nice to have a way to pass a collection of data that could be looped over; our idea is a second partial helper, partialLoop(), with an API like:

Controllers

Needs some work:

  • Need to ensure ViewRenderer integration continues to work properly – that it uses the appropriate request object to make decisions
  • What happens if a _forward() or _redirect() is called in the dispatched controller? is this silently ignored?

Placeholders

Seems clunky. While I understand why it's done as a single class, the notation seems pretty clunky and not intuitive to how view helpers are typically done. However, that said, the actual implementation is solid, and accomplishes the goals.

Update: Ralph and I discussed this in depth. Basically, placeholders already exist, assuming the same view instance is used: view variables. The reason to have a placeholder implementation is for the following tasks:

  • Handling collections (allowing prepending, appending, and inserting into a collection of data, including sorting.
  • Allowing custom prefixes, postfixes, and separators for collections (for instance, prefixing the collection with "<ul><li>", postfixing with "</li></ul>", and separating with "</li><li>").
  • Capturing output to variables
  • Consistent API

The underlying datastore is an array of ArrayObjects.

The API would look something like this:

Capture content helper

At times, a particular application may want to create placeholder content on-the-fly without needing to forward to other actions or call specialized helpers. The easy way to do so is using output buffering to capture data. The placeholder helper would provide a mechanism for this:

captureStart() would allow an optional parameter to hint whether the data collected would overwrite or append the placeholder contents (i.e., if the result will be a scalar or part of a collection).

How to get values?

One obvious issue, if we allow either scalar or collection values in
placeholders, is how to get the value. Consider this:

$foo is now an ArrayObject – which might seem odd if you set it to a scalar value. However, to preserve the capabilities of the placeholder, it makes sense to still return as an ArrayObject – which allows for the __toString() implementation, etc. If the value is desired, I suggest adding a toValue() method:

If the placeholder consists of a single value, it would return that single value; if it is a collection, it would return the value as returned by ArrayObject::getArrayCopy().

Additional Helpers

General comments: Same as with placeholders: having the view helper return an object instead of directly placing the values seems counter to how most view helpers are created. I suggest that most of these should overload the helper method to allow the following:

  • no arguments: return the value
  • single empty argument: unset it
  • single non-empty argument: set the value

In the case of those that allow setting multiple values, passing an empty value for an index would unset it, passing just the index would retrieve it, and passing no arguments would return the object.

Doctype

This is basically for two purposes:

  • To output the appropriate doctype declaration in a layout script
  • To hint to other helpers (headScript()) how to format their contents (e.g. XHTML/XML doctypes need <script> to wrap JS in CDATA tags

Additional ones?

Paddy addresses several:

  • <link> generators
  • <script> generators
  • <meta> generators

These should extend the placeholder helper.

Zend_View_Factory

Not sure I see a clear need for this. However, it could make it possible to eliminate the need to specify the view suffix when calling things like partial() and render() within view scripts, and also allow normalization of paths and file names. The ViewRenderer would proxy to this instead of Zend_View_Abstract if used.

Zend_Layout

  • URL: Zend_Layout
    Relies on the response object for variable storage. This is handy, as you can then use the named response segment to target content to appropriate places. However, it ties Zend_Layout to the response object.

Another possible strategy is to use a postDispatch() plugin to pluck data out of the response object and place it in a registry in Zend_Layout, and then to create accessors in Zend_Layout to allow specifying content.

Don't necessarily like the singleton strategy. While it ensures that a single Zend_Layout instance exists, this may be possible by instantiating the plugin first, and having an accessor to retrieve it, or instantiating and passing to the plugin and various other helpers, perhaps via a factory.

Symfony

Theory

  • Views accept optional decorators
  • Layouts are decorators
  • Renderer class renders view, and decorates with any decorators

Operation

  • Layout selection
    • from YAML config
    • in the action
      • $this->setLayout($name)
      • setting to false disables layout
      • AJAX actions have no layout by default
  • Slots
    • Used by layouts, templates and partials
    • Basically a placeholder
    • Retrieval:
      • has_slot($name) - does the slot exist?
      • include_slot($name) - pull in the content
    • Creation:
      • slot($name) - start capture
      • end_slot() - ends capture
      • example:
  • Views
    • shortcut variables:
      • $sf_context (sfContext object) (what's this?)
      • $sf_request (request object)
      • $sf_params (parameters from request object)
      • $sf_user (current user session object)
    • extra methods
      • getRequestParameter()
  • Layout template
    • <head> population from response object:
      • include_http_metas()
      • include_metas()
      • include_title()
      • Response methods:
        • addHttpMeta()
        • addMeta()
        • setTitle()
        • addStyleSheet()
        • addJavaScript()
      • JS, CSS files are auto-included in layout via a filter
    • content population from view:
      • $sf_data->getRaw('sf_content')

Pros

Moving rendering to separate class means that you can have separate views with their own namespaces. Additionally, it means you can attach any number of decorators to a view, allowing an easy way to render something from the inside out.

The idea of functions or methods for retrieving content to inject in the layout is good; makes layout logic simpler and more configurable.

Also like the idea of auto-injecting various objects/vars into the views – good candidates are the request object and front controller.

Placeholders are a good concept, though I hate the 'slots' terminology.

Cons

  • Would likely mean either a complete rewrite of Zend_View to separate rendering details from configuation details, or adding logic to Zend_View_Abstract to allow attaching decorator templates.
  • Wouldn't allow for re-use of a single view object for multiple rendered views.
  • Don't like the idea that scripts/css are linked in via filtering; too much magic.
  • Too much coupling with both view and controller.

Cake

Operation

  • Handled jointly by Controller and View class
    • Controller provides layout info to View
  • Controller
    • $layoutPath
      • indicates path to layout files
    • $pageTitle
      • indicates text to use in <title> of layout script
    • $layout
      • indicates actual layout script to render; defaults to 'default'
    • $autoLayout
      • flag; render layouts automatically; default on
    • render()
      • second argument is layout to use
      • turns off autoRender
      • Calls view->render($action, $layout, $file)
  • View
    • render()
      • If autoLayout and layout both true, calls renderLayout()
    • renderLayout()
      • takes content and renders a view from the layout file
      • uses properties for title, scripts
      • assigns:
        • content_for_layout
        • title_for_layout
        • scripts_for_layout
        • cakeDebug

Pros

  • Predictable.
  • Configurable (can choose layout path, layout script, whether or not to use it).
  • Could theoretically be used even without MVC (rendering of layouts is done in View::render())

Cons

  • Cannot configure names of 'special' layout variables.
  • No solid placeholder implementation.

Solar

Operation

  • Handled in page controller
  • Properties define behaviour
    • $_layout is name of layout script
    • $_layout_var is placeholder in layout script to use
  • _render() handles it
    • if _layout not empty:
      • calls _setLayoutTemplates()
      • calls _renderLayout()
  • _setLayoutTemplates()
    • Loops through class stack to determine view script path for layout
  • _renderLayout()
    • assigns response content to view using _layout_var as key
    • appends '.php' suffix to _layout to determine view
    • renders layout view into response
  • _load()
    • takes spec in _action, which can include a 'layout' key

Pros

  • Simple and configurable. Easy to disable.
  • Can specify variable name for placeholder in layout.

Cons

  • Coupling with controller. Would not work with a dispatch loop.

Rails

Operation

  • Object property of controller
  • By default, looks in views/layouts
  • By default, looks for
    • layout of same name as controller
    • layout named application
  • Specify value for layout as part of class
    • nil means no layout
    • pass 'layout' key to render()
  • Inherits variables from controller view object
  • special variable, content_for_layout is rendered content to use

Pros

  • Simple to utilize.
  • Inherits view from application; variable re-use.
  • Easy to disable or enable.
  • Like the separate directory for layouts vs scripts.

Cons

  • Controller-based. Wouldn't work with a dispatch loop.

ZF Goals

  • Must work with a dispatch loop and named response segments. Since we can forward between actions, or create a loop of actions to dispatch, and because the response content can exist in multiple segments, we have special needs and capabilities other frameworks don't. Rendering must be done after the dispatch loop has finished, and should use all content segments.
  • Must be able to work without MVC. While the above should be true, Layouts should be available to those not wanting to use the MVC, or using an alternate MVC.
  • Must be simple to invoke. A controller should be able to specify a layout at will, and this should be easy to do.
  • Must be configurable
    • Must be able to specify which layout to use, but allow for a 'default' layout
    • Must be able to specify location of layouts
    • Must be able to specify names of layout variables
  • Must be able to pick and choose which placeholders to render
  • Must have a simple API for specifying items to use in the <head> section In discussion with Ralph, we've decided that view variables and the collection helper are the proper solution for this.
  • Must follow same naming conventions followed in ViewRenderer
    • naming
    • suffixes
  • Must inherit from view set in view renderer (if in use). This allows view scripts as well as action controllers to set variables to use in the layout.

Implementation

Inflector class

An inflector class that could be used by both the ViewRenderer and Zend_Layout
would need to be created. This would allow specifying:

  • view script suffix
  • rules for script autodiscovery
  • rules for normalization of names

Zend_Layout

  • Accessors
    • layout template; name of layout template, without suffix.
      • setLayout()
      • getLayout()
    • layout variable name prefix; prefix to use for all variables assigned to view from layout object
      • setLayoutVarPrefix()
      • getLayoutVarPrefix()
    • layout path
      • setLayoutPath()
      • addLayoutPath())
    • set view object
      • setView()
      • getView())
    • layout variable assignment
      • assign()
      • _get/_set()
  • render()
    • retrieves view object, according to:
      • internal
      • view renderer The plugin would inject from the ViewRenderer if found.
      • instantiate own
    • Clears all script paths
    • Sets script paths to internal layout path stack
    • Assigns layout variables to view
      • prepends all variable keys with layout variable name prefix prior to assigning
    • Determines layout template name (using inflector)
    • renders layout template
  • __construct($noMVC)
    • Registers controller plugin with front controller, unless $noMVC true
  • __construct()
    • Requires a path to layout scripts
    • Takes optional configuration data and uses to initialize
    • If a 'useMvc' flag is set (by default), then instantiates and registers controller plugin, and instantiates action helper and registers with it.
  • getInstance()
    • Used by view helper to retrieve instance of layout class. Constructor sets internal static variable to instance; getInstance() then checks for that; if none is found, throws exception, indicating that Zend_Layout was never initialized

Action helper that proxies to Class instance

  • use to proxy to accessors

View helper that proxies to Class instance

  • use to proxy to accessors

Controller plugin (dispatchLoopShutdown())

  • Checks to see if getLayout() is not empty
  • Assigns named segments of response object as variables to layout instance
  • Determines if ViewRenderer has been used, and, if so, passes its view object to the layout object
  • calls render()
  • assigns content back into response object

Usage Examples

From bootstrap:
Without MVC
Sample layout

Notes

  • Would need to be enabled manually, either in bootstrap, a controller, or a plugin.
  • Can be used without MVC if desired; simply instantiate, use accessors, and call render().
  • Some on-list have mentioned they'd like support for setting options via config, either to constructor or a setConfig() method

Class Skeletons

Zend_View_Helper_Placeholder_Exception

Zend_View_Helper_Placeholder_Container

Zend_View_Helper_Placeholder

Zend_Layout

Zend_Layout_Controller_Plugin

Zend_Layout_Action_Helper

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.