You may often need to include some HTML or PHP code in several pages. To avoid repeating that code, the PHP include()
statement will suffice most of the time.
For instance, if many of the templates of your application need to use the same fragment of code, save it in a file called myFragment.php
in the global template directory (myproject/apps/myapp/templates/
) and include it in your templates as follows:
<?php include(sfConfig::get('sf_app_template_dir').'/myFragment.php') ?>
But this is not a very clean way to package a fragment, mostly because you can have different variable names between the fragment and the various templates including it. In addition, the symfony cache system (described in Chapter 12) has no way to detect an include, so the fragment cannot be cached independently from the template. Symfony provides three alternative types of intelligent code fragments to replace include
s:
- If the logic is lightweight, you will just want to include a template file having access to some data you pass to it. For that, you will use a partial.
- If the logic is heavier (for instance, if you need to access the data model and/or modify the content according to the session), you will prefer to separate the presentation from the logic. For that, you will use a component.
- If the fragment is meant to replace a specific part of the layout, for which default content may already exist, you will use a slot.
Note Another code fragment type, called a component slot, is to be used when the nature of the fragment depends on the context (for instance, if the fragment needs to be different for the actions of a given module). Component slots are described later in this chapter.
The inclusion of these fragments is achieved by helpers of the Partial
group. These helpers are available from any symfony template, without initial declaration.
7.2.1. Partials
A partial is a reusable chunk of template code. For instance, in a publication application, the template code displaying an article is used in the article detail page, and also in the list of the best articles and the list of latest articles. This code is a perfect candidate for a partial, as illustrated in Figure 7-2.
Just like templates, partials are files located in the templates/
directory, and they contain HTML code with embedded PHP. A partial file name always starts with an underscore (_
), and that helps to distinguish partials from templates, since they are located in the same templates/
folders.
A template can include partials whether it is in the same module, in another module, or in the global templates/
directory. Include a partial by using the include_partial()
helper, and specify the module and partial name as a parameter (but omit the leading underscore and the trailing .php
), as described in Listing 7-7.
Listing 7-7 - Including a Partial in a Template of the mymodule
Module
// Include the myapp/modules/mymodule/templates/_mypartial1.php partial
// As the template and the partial are in the same module,
// you can omit the module name
<?php include_partial('mypartial1') ?>
// Include the myapp/modules/foobar/templates/_mypartial2.php partial
// The module name is compulsory in that case
<?php include_partial('foobar/mypartial2') ?>
// Include the myapp/templates/_mypartial3.php partial
// It is considered as part of the 'global' module
<?php include_partial('global/mypartial3') ?>
Partials have access to the usual symfony helpers and template shortcuts. But since partials can be called from anywhere in the application, they do not have automatic access to the variables defined in the action calling the templates that includes them, unless passed explicitly as an argument. For instance, if you want a partial to have access to a $total
variable, the action must hand it to the template, and then the template to the helper as a second argument of the include_partial()
call, as shown in Listings 7-8, 7-9, and 7-10.
Listing 7-8 - The Action Defines a Variable, in mymodule/actions/actions.class.php
class mymoduleActions extends sfActions
{
public function executeIndex()
{
$this->total = 100;
}
}
Listing 7-9 - The Template Passes the Variable to the Partial, in mymodule/templates/indexSuccess.php
<p>Hello, world!</p>
<?php include_partial('mypartial',
array('mytotal' => $total)
) ?>
Listing 7-10 - The Partial Can Now Use the Variable, in mymodule/templates/_mypartial.php
<p>Total: <?php echo $mytotal ?></p>
Tip All the helpers so far were called by <?php echo functionName() ?>
. The partial helper, however, is simply called by <?php include_partial() ?>
, without echo
, to make it behave similar to the regular PHP include()
statement. If you ever need a function that returns the content of a partial without actually displaying it, use get_partial()
instead. All the include_
helpers described in this chapter have a get_
counterpart that can be called together with an echo
statement.
7.2.2. Components
In Chapter 2, the first sample script was split into two parts to separate the logic from the presentation. Just like the MVC pattern applies to actions and templates, you may need to split a partial into a logic part and a presentation part. In such a case, you should use a component.
A component is like an action, except it's much faster. The logic of a component is kept in a class inheriting from sfComponents
, located in an action/components.class.php
file. Its presentation is kept in a partial. Methods of the sfComponents
class start with the word execute
, just like actions, and they can pass variables to their presentation counterpart in the same way that actions can pass variables. Partials that serve as presentation for components are named by the component (without the leading execute
, but with an underscore instead). Table 7-1 compares the naming conventions for actions and components.
Table 7-1 - Action and Component Naming Conventions
Convention | Actions | Components |
---|---|---|
Logic file | actions.class.php |
components.class.php |
Logic class extends | sfActions |
sfComponents |
Method naming | executeMyAction() |
executeMyComponent() |
Presentation file naming | myActionSuccess.php |
_myComponent.php |
Tip Just as you can separate actions files, the sfComponents
class has an sfComponent
counterpart that allows for single component files with the same type of syntax.
For instance, suppose you have a sidebar displaying the latest news headlines for a given subject, depending on the user's profile, which is reused in several pages. The queries necessary to get the news headlines are too complex to appear in a simple partial, so they need to be moved to an action-like file--a component. Figure 7-3 illustrates this example.
For this example, shown in Listings 7-11 and 7-12, the component will be kept in its own module (called news
), but you can mix components and actions in a single module if it makes sense from a functional point of view.
Listing 7-11 - The Components Class, in modules/news/actions/components.class.php
<?php
class newsComponents extends sfComponents
{
public function executeHeadlines()
{
$c = new Criteria();
$c->addDescendingOrderByColumn(NewsPeer::PUBLISHED_AT);
$c->setLimit(5);
$this->news = NewsPeer::doSelect($c);
}
}
Listing 7-12 - The Partial, in modules/news/templates/_headlines.php
<div>
<h1>Latest news</h1>
<ul>
<?php foreach($news as $headline): ?>
<li>
<?php echo $headline->getPublishedAt() ?>
<?php echo link_to($headline->getTitle(),'news/show?id='.$headline->getId()) ?>
</li>
<?php endforeach ?>
</ul>
</div>
Now, every time you need the component in a template, just call this:
<?php include_component('news', 'headlines') ?>
Just like the partials, components accept additional parameters in the shape of an associative array. The parameters are available to the partial under their name, and in the component via the $this
object. See Listing 7-13 for an example.
Listing 7-13 - Passing Parameters to a Component and Its Template
// Call to the component
<?php include_component('news', 'headlines', array('foo' => 'bar')) ?>
// In the component itself
echo $this->foo;
=> 'bar'
// In the _headlines.php partial
echo $foo;
=> 'bar'
You can include components in components, or in the global layout, as in any regular template. Like actions, components' execute
methods can pass variables to the related partial and have access to the same shortcuts. But the similarities stop there. A component doesn't handle security or validation, cannot be called from the Internet (only from the application itself), and doesn't have various return possibilities. That's why a component is faster to execute than an action.
7.2.3. Slots
Partials and components are great for reusability. But in many cases, code fragments are required to fill a layout with more than one dynamic zone. For instance, suppose that you want to add some custom tags in the <head>
section of the layout, depending on the content of the action. Or, suppose that the layout has one major dynamic zone, which is filled by the result of the action, plus a lot of other smaller ones, which have a default content defined in the layout but can be overridden at the template level.
For these situations, the solution is a slot. Basically, a slot is a placeholder that you can put in any of the view elements (in the layout, a template, or a partial). Filling this placeholder is just like setting a variable. The filling code is stored globally in the response, so you can define it anywhere (in the layout, a template, or a partial). Just make sure to define a slot before including it, and remember that the layout is executed after the template (this is the decoration process), and the partials are executed when they are called in a template. Does it sound too abstract? Let's see an example.
Imagine a layout with one zone for the template and two slots: one for the sidebar and the other for the footer. The slot values are defined in the templates. During the decoration process, the layout code wraps the template code, and the slots are filled with the previously defined values, as illustrated in Figure 7-4. The sidebar and the footer can then be contextual to the main action. This is like having a layout with more than one "hole."
Seeing some code will clarify things further. To include a slot, use the include_slot()
helper. The has_slot()
helper returns true
if the slot has been defined before, providing a fallback mechanism as a bonus. For instance, define a placeholder for a 'sidebar'
slot in the layout and its default content as shown in Listing 7-14.
Listing 7-14 - Including a 'sidebar'
Slot in the Layout
<div id="sidebar">
<?php if (has_slot('sidebar')): ?>
<?php include_slot('sidebar') ?>
<?php else: ?>
<!-- default sidebar code -->
<h1>Contextual zone</h1>
<p>This zone contains links and information
relative to the main content of the page.</p>
<?php endif; ?>
</div>
Each template has the ability to define the contents of a slot (actually, even partials can do it). As slots are meant to hold HTML code, symfony offers a convenient way to define them: just write the slot code between a call to the slot()
and end_slot()
helpers, as in Listing 7-15.
Listing 7-15 - Overriding the 'sidebar'
Slot Content in a Template
...
<?php slot('sidebar') ?>
<!-- custom sidebar code for the current template-->
<h1>User details</h1>
<p>name: <?php echo $user->getName() ?></p>
<p>email: <?php echo $user->getEmail() ?></p>
<?php end_slot() ?>
The code between the slot helpers is executed in the context of the template, so it has access to all the variables that were defined in the action. Symfony will automatically put the result of this code in the response object. It will not be displayed in the template, but made available for future include_slot()
calls, like the one in Listing 7-14.
Slots are very useful to define zones meant to display contextual content. They can also be used to add HTML code to the layout for certain actions only. For instance, a template displaying the list of the latest news might want to add a link to an RSS feed in the <head>
part of the layout. This is achieved simply by adding a 'feed'
slot in the layout and overriding it in the template of the list.