ContextSwitch and AjaxContext
The ContextSwitch action helper is intended for
facilitating returning different response formats on request.
The AjaxContext helper is a specialized version of
ContextSwitch that facilitates returning responses
to XmlHttpRequests.
To enable either one, you must provide hinting in your controller as to
what actions can respond to which contexts. If an incoming request
indicates a valid context for the given action, the helper will then:
Disable layouts, if enabled.
Set an alternate view suffix, effectively requiring a separate
view script for the context.
Send appropriate response headers for the context desired.
Optionally, call specified callbacks to setup the context and/or
perform post-processing.
As an example, let's consider the following controller:
_forward('list');
}
/**
* List news items
*/
public function listAction()
{
}
/**
* View a news item
*/
public function viewAction()
{
}
}
]]>
Let's say that we want the listAction() to also be
available in an XML format. Instead of creating a different action, we
can hint that it can return an XML response:
_helper->getHelper('contextSwitch');
$contextSwitch->addActionContext('list', 'xml')
->initContext();
}
// ...
}
]]>
What this will do is:
Set the 'Content-Type' response header to 'application/xml'.
Change the view suffix to 'xml.phtml' (or, if you use an
alternate view suffix, 'xml.[your suffix]').
Now, you'll need to create a new view script, 'news/list.xml.phtml',
which will create and render the XML.
To determine if a request should initiate a context switch, the helper
checks for a token in the request object. By default, it looks for the
'format' parameter, though this may be configured. This means that, in
most cases, to trigger a context switch, you can add a 'format'
parameter to your request:
Via URL parameter: /news/list/format/xml
(recall, the default routing schema allows for arbitrary key to value pairs
following the action)
Via GET parameter: /news/list?format=xml
ContextSwitch allows you to specify arbitrary contexts,
including what suffix change will occur (if any), any response headers
that should be sent, and arbitrary callbacks for initialization and post
processing.
Default Contexts Available
By default, two contexts are available to the
ContextSwitch helper: json and XML.
JSON. The JSON
context sets the 'Content-Type' response header to
'application/json', and the view script suffix to
'json.phtml'.
By default, however, no view script is required. It will
simply serialize all view variables, and emit the JSON
response immediately.
This behaviour can be disabled by turning off the automatic
JSON serialization:
_helper->contextSwitch()->setAutoJsonSerialization(false);
]]>
XML. The XML context
sets the 'Content-Type' response header to 'application/xml', and
the view script suffix to 'xml.phtml'. You will need to
create a new view script for the context.
Creating Custom Contexts
Sometimes, the default contexts are not enough. For instance, you may wish to return
YAML, or serialized PHP, an
RSS or ATOM feed, etc.
ContextSwitch allows you to do so.
The easiest way to add a new context is via the
addContext() method. This method takes two arguments,
the name of the context, and an array specification. The
specification should include one or more of the following:
suffix: the suffix to prepend to the
default view suffix as registered in the ViewRenderer.
headers: an array of header to value
pairs you wish sent as part of the response.
callbacks: an array containing one or
more of the keys 'init' or 'post', pointing to valid PHP
callbacks that can be used for context initialization and post
processing.
Initialization callbacks occur when the context is
detected by ContextSwitch. You can use it to
perform arbitrary logic that should occur. As an example,
the JSON context uses a callback to disable the ViewRenderer
when the automatic JSON serialization is on.
Post processing occurs during the action's
postDispatch() routine, and can be used to perform
arbitrary logic. As an example, the JSON context uses a
callback to determine if the automatic JSON serialization is
on; if so, it serializes the view variables to JSON and sends
the response, but if not, it re-enables the ViewRenderer.
There are a variety of methods for interacting with contexts:
addContext($context, array $spec): add a new
context. Throws an exception if the context already exists.
setContext($context, array $spec): add a new
context or overwrite an existing context. Uses the same
specification as addContext().
addContexts(array $contexts): add many contexts at
once. The $contexts array should be an array of
context to specification pairs. If any of the contexts already
exists, it will throw an exception.
setContexts(array $contexts): add new contexts and
overwrite existing ones. Uses the same specification as
addContexts().
hasContext($context): returns TRUE if
the context exists, FALSE otherwise.
getContext($context): retrieve a
single context by name. Returns an array following the
specification used in addContext().
getContexts(): retrieve all contexts. Returns an
array of context to specification pairs.
removeContext($context): remove a single context by
name. Returns TRUE if successful,
FALSE if the context was not found.
clearContexts(): remove all contexts.
Setting Contexts Per Action
There are two mechanisms for setting available contexts. You can
either manually create arrays in your controller, or use several
methods in ContextSwitch to assemble them.
The principle method for adding action to context relations is
addActionContext(). It expects two arguments, the
action to which the context is being added, and either the name of a
context or an array of contexts. As an example, consider the
following controller class:
Let's say we wanted to add an XML context to the 'list' action, and
XML and JSON contexts to the 'comments' action.
We could do so in the init() method:
_helper->contextSwitch()
->addActionContext('list', 'xml')
->addActionContext('comments', array('xml', 'json'))
->initContext();
}
}
]]>
Alternately, you could simply define the array property
$contexts:
array('xml'),
'comments' => array('xml', 'json')
);
public function init()
{
$this->_helper->contextSwitch()->initContext();
}
}
]]>
The above is less overhead, but also prone to potential errors.
The following methods can be used to build the context mappings:
addActionContext($action, $context): marks one
or more contexts as available to an action. If mappings
already exists, simply appends to those mappings.
$context may be a single context, or an array
of contexts.
A value of TRUE for the context will mark
all available contexts as available for the action.
An empty value for $context will disable all contexts for
the given action.
setActionContext($action, $context): marks one
or more contexts as available to an action. If mappings
already exists, it replaces them with those specified.
$context may be a single context, or an array
of contexts.
addActionContexts(array $contexts): add several
action to context pairings at once. $contexts
should be an associative array of action to context pairs. It
proxies to addActionContext(), meaning that if
pairings already exist, it appends to them.
setActionContexts(array $contexts): acts like
addActionContexts(), but overwrites existing
action to context pairs.
hasActionContext($action, $context): determine
if a particular action has a given context.
getActionContexts($action = null): returns
either all contexts for a given action, or all
action to context pairs.
removeActionContext($action, $context): remove
one or more contexts from a given action.
$context may be a single context or an array of
contexts.
clearActionContexts($action = null): remove all
contexts from a given action, or from all actions with
contexts.
Initializing Context Switching
To initialize context switching, you need to call
initContext() in your action controller:
_helper->contextSwitch()->initContext();
}
}
]]>
In some cases, you may want to force the context used; for instance,
you may only want to allow the XML context if context switching is
activated. You can do so by passing the context to
initContext():
initContext('xml');
]]>
Additional Functionality
A variety of methods can be used to alter the behaviour of the
ContextSwitch helper. These include:
setAutoJsonSerialization($flag): By default,
JSON contexts will serialize any view variables to
JSON notation and return this as a response. If you wish to
create your own response, you should turn this off; this
needs to be done prior to the call to
initContext().
setAutoJsonSerialization(false);
$contextSwitch->initContext();
]]>
You can retrieve the value of the flag with
getAutoJsonSerialization().
setSuffix($context, $suffix,
$prependViewRendererSuffix): With this method,
you can specify a different suffix to use for a given
context. The third argument is used to indicate whether or
not to prepend the current ViewRenderer suffix with the new
suffix; this flag is enabled by default.
Passing an empty value to the suffix will cause only the
ViewRenderer suffix to be used.
addHeader($context, $header, $content): Add a
response header for a given context. $header is
the header name, and $content is the value to
pass for that header.
Each context can have multiple headers;
addHeader() adds additional headers to the
context's header stack.
If the $header specified already exists for the
context, an exception will be thrown.
setHeader($context, $header, $content):
setHeader() acts just like
addHeader(), except it allows you to overwrite
existing context headers.
addHeaders($context, array $headers): Add
multiple headers at once to a given context. Proxies to
addHeader(), so if the header already exists,
an exception will be thrown. $headers is an
array of header to context pairs.
setHeaders($context, array $headers.): like
addHeaders(), except it proxies to
setHeader(), allowing you to overwrite existing
headers.
getHeader($context, $header): retrieve the
value of a header for a given context. Returns NULL if not
found.
removeHeader($context, $header): remove a
single header for a given context.
clearHeaders($context, $header): remove all
headers for a given context.
setCallback($context, $trigger, $callback): set
a callback at a given trigger for a given context. Triggers
may be either 'init' or 'post' (indicating callback will be
called at either context initialization or postDispatch).
$callback should be a valid PHP callback.
setCallbacks($context, array $callbacks): set
multiple callbacks for a given context. $callbacks
should be trigger to callback pairs. In actuality, the most callbacks
that can be registered are two, one for initialization and
one for post processing.
getCallback($context, $trigger): retrieve a
callback for a given trigger in a given context.
getCallbacks($context): retrieve all callbacks
for a given context. Returns an array of trigger to callback
pairs.
removeCallback($context, $trigger): remove a
callback for a given trigger and context.
clearCallbacks($context): remove all
callbacks for a given context.
setContextParam($name): set the request
parameter to check when determining if a context switch has
been requested. The value defaults to 'format', but this
accessor can be used to set an alternate value.
getContextParam() can be used to retrieve the
current value.
setAutoDisableLayout($flag): By default,
layouts are disabled when a context switch occurs; this is
because typically layouts will only be used for returning
normal responses, and have no meaning in alternate contexts.
However, if you wish to use layouts (perhaps you may have a
layout for the new context), you can change this behaviour
by passing a FALSE value to
setAutoDisableLayout(). You should do this
before calling
initContext().
To get the value of this flag, use the accessor
getAutoDisableLayout().
getCurrentContext() can be used to determine
what context was detected, if any. This returns NULL if no
context switch occurred, or if called before
initContext() has been invoked.
AjaxContext Functionality
The AjaxContext helper extends
ContextSwitch, so all of the functionality listed for
ContextSwitch is available to it. There are a few key
differences, however.
First, it uses a different action controller property for
determining contexts, $ajaxable. This is so you can
have different contexts used for AJAX versus normal
HTTP requests. The various
*ActionContext()* methods of
AjaxContext will write to this property.
Second, it will only trigger if an XmlHttpRequest has occurred, as
determined by the request object's isXmlHttpRequest()
method. Thus, if the context parameter ('format') is passed in the
request, but the request was not made as an XmlHttpRequest, no
context switch will trigger.
Third, AjaxContext adds an additional context,
HTML. In this context, it sets the suffix to
'ajax.phtml' in order to differentiate the context from a normal
request. No additional headers are returned.
Allowing Actions to Respond To Ajax Requests
In this following example, we're allowing requests to the
actions 'view', 'form', and 'process' to respond to AJAX
requests. In the first two cases, 'view' and 'form', we'll
return HTML snippets with which to update the page; in the
latter, we'll return JSON.
_helper->getHelper('AjaxContext');
$ajaxContext->addActionContext('view', 'html')
->addActionContext('form', 'html')
->addActionContext('process', 'json')
->initContext();
}
public function viewAction()
{
// Pull a single comment to view.
// When AjaxContext detected, uses the comment/view.ajax.phtml
// view script.
}
public function formAction()
{
// Render the "add new comment" form.
// When AjaxContext detected, uses the comment/form.ajax.phtml
// view script.
}
public function processAction()
{
// Process a new comment
// Return the results as JSON; simply assign the results as
// view variables, and JSON will be returned.
}
}
]]>
On the client end, your AJAX library will simply request the
endpoints '/comment/view',
'/comment/form', and
'/comment/process', and pass the 'format' parameter:
'/comment/view/format/html',
'/comment/form/format/html',
'/comment/process/format/json'. (Or you can pass the parameter
via query string: e.g., "?format=json".)
Assuming your library passes the 'X-Requested-With:
XmlHttpRequest' header, these actions will then return the
appropriate response format.