The !RespondsWith annotation is used on a
controller to register one or more Views that should be used for
responding to a request. The controller does not ultimately decide which
view to use, rather the IPolicy does.
!RespondsWith is a simple way of hinting to an
IPolicy which view should be used to deliver a
response. Recess' default (and only) policy, aptly named
DefaultPolicy, looks at the views hinted and
finds the first which can handle the response with the desired
mime-type/format. Let's look at an exampe:
Example 10.17. The !RespondsWith Annotation
<?php /** * !RespondsWith Layouts, Json * !Prefix my/ **/ class MyController { /** !Route GET, hello/ */ function hello() { return $this->ok('hello'); } } ?>
Now suppose the following request is handled by in:
GET /my/hello.json. This will get routed to the
hello method which returns an
OkResponse with 'hello' as the
suggested template name. DefaultPolicy will first
ask LayoutsView if it can respond with the
OkResponse object by looking to see if the file
/app/views/my/hello.json.php exists, if so
LayoutsView will be chosen to handle the
response. If not, DefaultPolicy will then ask
JsonView if it can respond and, because it can
automatically respond to JSON request, it always will. If the annotation
on MyController gave priority to
JsonView over LayoutsView,
with: !RespondsWith Json, Layouts, then all JSON
requests would be handled by JsonView without
ever asking LayoutsView.
A View has only two operations: 1) to
decide whether it can or cannot render a Response, 2) to render a
Response. These are represented in a simple interface with the abstract
methods of AbstractView:
canRespondWith(Response
$response) and
respondWith(Response
$response). Some Views, like
JsonView, can automatically render a response so
developers never touch presentation code. For other Views, like
LayoutsView, their job is accomplished by
dispatching to user-defined scripts/templates from the
/apps/[app]/views/ directory where the user has
specified templates.
Templates are where developers spend most of their time designing
presentation. Views dispatch control to templates and pass in variables
from the response. By default, Recess ships with native PHP templates so
templates are plain-old-php files. Templates could also be Smarty
templates when paired with SmartyView. Templates
are placed in the /apps/[app]/views/ directory which
is somewhat of a misnomer because they're
templates, not views.
If you've programmed presentation logic in a PHP application
you've likely dealt with include a good amount.
When designing applications that have common UI it is a good practice to
extract common parts of the UI to their own script files. The problem
with simple includes is that they share scope. This makes includes
difficult to reason about, shown in the following example, even if you
know yourguess.php isn't going to halt, you don't
know whether "Hello World?" will get printed, or some other string, or
perhaps a notice error that temp is not defined:
Example 10.18. The Danger in Sharing include Scope
<?php $title = "Hello World?"; include('print-heading.php'); echo $title; ?> === <?php // print-heading.php echo "<h1>$title</h1>\n"; unset($title); ?> === Output: <h1>Hello World</h1> Notice: Undefined variable: title
Using includes can lead to spaghetti-code where you must
understand the entirety of every include before you can understand a
single script that uses includes. Included files which expect certain
variables to be available (perhaps $title is used to fill in the
<title> tag) require the includee to wade through the script and
determine which variables to have available in the context. For these
reasons Recess, as of version 0.20 encourages not using includes in your
templates and instead using Assertive Templates.
Assertive templates are a name given to a certain style of template: one that asserts the presence and type of every input variable at the top of the script. Here is an example:
Example 10.19. An Assertive Template
<?php // print-heading.part.php Part::input($title, 'string'); Part::input($level, 'int'); /////// Presentation Logic ////// echo "<h$level>$h1</h$level>\n"; unset($title); ?>
Layouts and Parts use Assertive Templates to address the
problem to make presentation logic more predictable. Here is a similar
snippet to the first example using the Part helper:
Example 10.20. Avoiding the Danger of Include
<?php
$title = "Hello world?";
Part::draw('print-heading', $title, 1);
echo $title;
?>
===
Output:
<h1>Hello world?</h1>
Hello world?Note that even though
print-heading.part.php unsets
$title, it has no effect on our top-level
template. Also note that, with the way drawing the
Part does not require knowing the names of
the variables within print-heading.part.php. We
simply just have to pass the right number of variables in. If this
feels more like a function than an include, you're right, it's a lot
like a function with typed inputs.
We'll go into more depth in the section describing the class
AssertiveTemplate, the common superclass of
Layouts & Parts.
Although there is additional effort in defining input, it simplifies
your reasoning about view logic and makes working with templates
(especially other people's!) much more predictable.
Helpers are classes, typically
abstract and static, that provide methods to simplify common tasks when
authoring presentation code. There are helpers, for example, for
composing URLs to controller methods, importing CSS from your app's
public/ assets directory, etc. For more info on the
helpers that ship with Recess see the section called “View Helpers”