Table of Contents
Your controller has done all the heavy lifting and taken care of the "business logic", now it's time to get back to your client with a meaningful response. Where controllers are all about serving the client by doing and fetching, views are all about responding by presenting and rendering.
How you actually respond likely depends on: 1) what the HTTP client asks for "I want /person/1 in JSON", "Ok, the controller gave me the data for Person 1, here it is in JSON", and 2) the best way for you to get the job done: maybe it's using Smarty templates, maybe it's an automatic response, or maybe it's plain old PHP.
This brings us to the design goals of Recess' View system, which have been revamped in 0.20:
Flexible - The view system must simplify handling different response types: HTML, JSON, XML, PDF, etc.
Extensible - Users can create their own Views to accomodate preferences with template engine(s) or automating common responses.
Predictable - The logic for selecting a view should be straightforward. Assertive templates, covered later in this chapter, are also encouraged for improved predictability when authoring templates.
Before diving into specifics let's take a leading example of creating the view components for a hypothetical listing of blog posts. Suppose we have a Post model that has properties: title, author, and body. Our controller code will look like this:
Example 10.1. Controller Code to send a List of All Posts to the View Layer
/** * !RespondsWith Layouts * !Prefix post/ */ class PostsController { /** !Route GET, list */ function index() { $this->posts = Make::a('Post')->all(); } }
We'll get into the details of the relevant annotations later,
for now just take our word that the view class rendering the response will
be LayoutsView, and it will be looking for the view
template post/index.html.php found in the
apps/[appname]/views/ sub-directory. The variable
$postSet will be available to this template which
represents a set of Post models.
Our first-pass, naive template implementation of
post/index.html.php may look like this:
Example 10.2. A Naive View Template Implementation of post/index.html.php
<html> <head><title>A List of Posts</title></head> <body> <ul> <?php foreach($posts as $post): ?> <li class="post"> <span class="title"><?php echo $post->title ?></span> by <span class="author"><?php echo $post->author ?></span> </li> <?php endforeach ?> </ul> </body> </html>
This works, but as our application grows in complexity we will
want to remove redundant HTML to layouts shared by many views. Let's try
creating a layout that this simple view can extend to remove common
HTML.
Example 10.3. A Simple Layout: master.layout.php
<?php Layout::input($title, 'string'); Layout::input($body, 'string'); ?> <html> <head><title><?php echo $title ?></title></head> <body> <?php echo $body ?> </body> </html>
Example 10.4. Making post/index.html.php extend master.layout.php
<?php Layout::extend('master'); $title = 'A List of Posts'; $body = '<ul>'; foreach($posts as $post) { $body .= '<li class="post">' . '<span class="title">' . $post->title . '</span> by ' . '<span class="author">' . $post->author . '</span>' . '</li>'; } $body .= '</ul>'; ?>
As you can see, we've extracted the redundant HTML to a master
layout. You'll notice that the master layout specifies its inputs. The
child/parent templates do not share scope, the parent must define which
inputs it requires and the type the variable should be. This improves
predictability because you know exactly what
variables a parent layout is expecting. If your child template fails to
provide a variable required by the parent you will get a simple error
message notifying you of the problem instead of an obscure 'variable not
defined' message from somewhere deep in the parent template.
Unfortunately, our listing template has regressed. Storing the
output of the list of posts in a string is painful. This is where
Buffer'ing and Block save
the day! Buffer is a class that uses PHP's output buffering to fill
Blocks. The buffer fills a block. Let's take a look:
Example 10.5. Using Buffer::to($bodyBlock) in post/index.html.php
<?php Layout::extend('master'); $title = 'A List of Posts'; ?> <?php Buffer::to($body); ?> <ul> <?php foreach($posts as $post): ?> <li class="post"> <span class="title"><?php echo $post->title ?></span> by <span class="author"><?php echo $post->author ?></span> </li> <?php endforeach ?> </ul> <?php Buffer::end(); ?>
Now we don't have to worry about string concatenation, we let
Buffer fill our $body Block.
If we were to run this code we would get a type check error in the master
layout: $body is expected to be a string, but now it is an instance of
HtmlBlock which is a sub-class of
Block. Why not just have
Buffer fill strings? Shortly we'll see how
Block's type-hierarchy enables really powerful
features. The immediate benefit, though, is that it allows layouts to
specify they are expecting a block of HTML, not just a string. Let's
update our master layout.
Example 10.6. Updating the master.layout.php to assert $body is a Block
<?php Layout::input($title, 'string'); Layout::input($body, 'Block'); ?> <html> <head><title><?php echo $title ?></title></head> <body> <?php echo $body ?> </body> </html>
Easy enough! Notice all we needed to change was the expected
type on the input. We can echo a Block instance
just like we can a string. Imagine we had some other places in our
application where we'd really like to print out the list-item format of a
Post. Enter: Parts to save the day! Parts are kind
of like partial templates you may have used in other frameworks. Let's
take a look at how we would define a part for post.
Example 10.7. Defining post/li.part.php
<?php Part::input($aPost, 'Post'); ?> <li class="post"> <span class="title"><?php echo $aPost->title ?></span> by <span class="author"><?php echo $aPost->author ?></span> </li>
Notice the similarity with a layout: parts and layouts define
their inputs. The term we coined for this style of a template is an
Assertive Template. By being assertive about the
inputs a template expects we can make working with these templates much
more pleasant. Let's take a look at how we would use this part in
post/index.html.php.
Example 10.8. Using a Part in post/index.html.php
<?php Layout::extend('master'); $title = 'A List of Posts'; ?> <ul><?php foreach($posts as $post) { Part::draw('post/li', $post); } ?></ul>
We can 'draw' a Part by passing the path to the part in the
views directory, minus '.part.php', and the rest of the inputs in the
order specified by the part. You pass a part inputs just like you call a
function with arguments. Notice that the name of the variable passed to
draw and the name of the input do not have to match, again, like
functions. This is contrasted by most PHP partial libraries that would
require calling the template with an array like array('aPost' =>
$post).
You'll also notice we're no longer buffering $body. But the code
still works! What's going on? By default, if your child template does not
specify a $body block, any output will automatically create a
Block named $body that will be
passed to its parent layout.
Let's say we wanted to make it possible to append another class to
the post/li part. We can create an additional input
that is optional by passing a third argument to Part::input which is the
default value of an input.
Example 10.9. Specifying a Default Input Value in post/li.part.php
<?php Part::input($aPost, 'Post'); Part::input($class, 'string', ''); ?> <li class="post<?php if($class != '') echo " $class" ?>"> <span class="title"><?php echo $aPost->title ?></span> by <span class="author"><?php echo $aPost->author ?></span> </li>
Now, by default, the only class will be 'post', but others
could be appended by passing an additional input to the part. Let's go
back to our post/index.html.php template and make the additional class
name of 'odd' be appended to every other item in our list. This way we can
style every other post differently using CSS.
Example 10.10. Give odd posts an additional class of 'odd' in post/index.html.php
<?php Layout::extend('master'); $title = 'A List of Posts'; ?> <ul><?php $i = -1; foreach($posts as $post) { if((++$i % 2) == 0) { Part::draw('post/li', $post); } else { Part::draw('post/li', $post, 'odd'); } } ?></ul>
You can see by passing an additional input to post/li we are
now appending the 'odd' class to odd posts. Suppose that throughout our
project we use this even/odd classname technique in a number of places.
Could we do any better and eliminate this redundancy? Yes, we can! We'll
simply create a part for it.
Now, take a deep breath and hold on. Here comes the sexy stuff that
makes our parts different from everyone else's partials. Earlier we
alluded to there being some 'power' in passing around instances of
Block instead of strings. You saw an example of how
Buffer filled an HtmlBlock.
There is a special Block for Parts, too, called
PartBlock. Before we summon a
PartBlock, though, let's first create the
higher-order part that abstracts toggling between two blocks.
Example 10.11. Creating a Higher Order Part with each-toggle.part.php
<?php
Part::input($items, 'array');
Part::input($even, 'Block');
Part::input($odd, 'Block');
$i = -1;
foreach($items as $item) {
if((++$i % 2) == 0) {
$even->draw($item);
} else {
$odd->draw($item);
}
}
?>
There are two interesting things going on here. First, notice
that we're passing in two Block instances named
$even and $odd. Second, notice that
we're using the draw method of
Block. Every Block has two
methods: __tostring and
draw. For an HtmlBlock
created using Buffer, draw doesn't take any
arguments (but won't complain if you give them). For a
PartBlock, though, passing arguments to
draw will apply those arguments where you left
off in creating the PartBlock. Well, then, how do
you create a PartBlock?
Example 10.12. Creating PartBlocks for use in Higher-Order Parts
<?php // Assume $post is already defined as an instance of Post $partBlock = Part::block('post/li'); $partBlock->draw($post); $partBlock->draw($post, 'odd'); // Is the same as: $partBlock2 = Part::block('post/li', $post); $partBlock2->draw(); $partBlock2->draw('odd'); ?>
So, by using the block method instead of
draw, the method returns a
PartBlock with the parameters you've passed
stored. You can think of this like currying in
functional languages like Scala / OCaml. For the astute reader you'll
notice we have a problem on our hands, though. In order to use our
'each-toggle' part $post must be the final argument passed to draw. We can
solve this in one of two ways, first we could do the obvious and re-order
the inputs so that our 'post/li' is passed a class string first and a Post
second. That makes things ugly, though, because we really want class to be
an optional parameter. Recess has a mechanism for assigning optional
arguments to a PartBlock after it has been created. Let's take another
look.
Example 10.13. Specifying Out-of-order Inputs in PartBlocks for use in Higher-Order Parts
<?php // Assume $post is already defined as an instance of Post $partBlock = Part::block('post/li'); $partBlock->draw($post); $partBlock->class('odd')->draw($post); // Is the same as: $partBlock2 = Part::block('post/li', $post); $partBlock2->draw(); $partBlock2->class('odd')->draw(); ?>
This style of assignment is inspired by the jQuery library and uses method chaining to make it easier to specify multiple optional arguments. Notice that the 'class' method corresponds to the $class input of the 'post/li' part. Now we're finally equipt to use our higher-order 'each-toggle' template! Let's take a look:
Example 10.14. Refactoring post/index.html.php using PartBlocks and a Higher-order Part
<?php Layout::extend('master'); $title = 'A List of Posts'; ?> <ul> <?php $li = Part::block('post/li'); Part::draw('each-toggle', $posts, $li, $li->class('odd')); ?> </ul>
Voila! Simple and beautiful. If we wanted to be really
pedantic we could even make a part for a list of posts. In this case it's
a little excessive, but let's do it just to demonstrate how parts can be
composed. Let's start with the code we'd like to end up with in
post/index.html.php template and design the part from it.
Example 10.15. Our final version of post/index.html.php
<?php Layout::extend('master'); $title = 'A List of Posts'; $body = Part::block('post/ul', $posts); ?>
Example 10.16. A Part that uses Parts - post/ul.part.php
<?php Part::input($posts, 'array'); ?> <ul> <?php $li = Part::block('post/li'); Part::draw('each-toggle', $posts, $li, $li->class('odd')); ?> </ul>
This wraps up our leading tutorial of the new view system in
Recess 0.2! Blocks, Parts, and Layouts are the keywords.
Blocks are chunks of HTML that haven't been rendered
yet and can be static with HtmlBlock or wrap around
parts with PartBlock. Layouts are advanced includes
that safely transfer variables from a child template to a parent layout.
This is possible because parent layouts specify their inputs. Parts, kind
of like partials, also specify their inputs. Parts and Layouts are
considered to be Assertive Templates for this reason.
Parts can become blocks, kind of like functions can become lambdas, to
enable powerful higher-order part templating.