View Cells
View cells are small mini-controllers that can invoke view logic and render outtemplates. The idea of cells is borrowed from cells in Ruby, where they fulfill a similar role andpurpose.
When to use Cells
Cells are ideal for building reusable page components that require interactionwith models, view logic, and rendering logic. A simple example would be thecart in an online store, or a data-driven navigation menu in a CMS.
Creating a Cell
To create a cell, define a class in src/View/Cell and a template insrc/Template/Cell/. In this example, we’ll be making a cell to display thenumber of messages in a user’s notification inbox. First, create the class file.Its contents should look like:
- namespace App\View\Cell;
- use Cake\View\Cell;
- class InboxCell extends Cell
- {
- public function display()
- {
- }
- }
Save this file into src/View/Cell/InboxCell.php. As you can see, like otherclasses in CakePHP, Cells have a few conventions:
- Cells live in the
App\View\Cell
namespace. If you are making a cell ina plugin, the namespace would bePluginName\View\Cell
. - Class names should end in Cell.
- Classes should inherit from
Cake\View\Cell
.
We added an emptydisplay()
method to our cell; this is the conventionaldefault method when rendering a cell. We’ll cover how to use other methods laterin the docs. Now, create the file src/Template/Cell/Inbox/display.ctp. Thiswill be our template for our new cell.
You can generate this stub code quickly using bake
:
- bin/cake bake cell Inbox
Would generate the code we created above.
Implementing the Cell
Assume that we are working on an application that allows users to send messagesto each other. We have a Messages
model, and we want to show the count ofunread messages without having to pollute AppController. This is a perfect usecase for a cell. In the class we just made, add the following:
- namespace App\View\Cell;
- use Cake\View\Cell;
- class InboxCell extends Cell
- {
- public function display()
- {
- $this->loadModel('Messages');
- $unread = $this->Messages->find('unread');
- $this->set('unread_count', $unread->count());
- }
- }
Because Cells use the ModelAwareTrait
and ViewVarsTrait
, they behavevery much like a controller would. We can use the loadModel()
and set()
methods just like we would in a controller. In our template file, add thefollowing:
- <!-- src/Template/Cell/Inbox/display.ctp -->
- <div class="notification-icon">
- You have <?= $unread_count ?> unread messages.
- </div>
Note
Cell templates have an isolated scope that does not share the same Viewinstance as the one used to render template and layout for the currentcontroller action or other cells. Hence they are unaware of any helper callsmade or blocks set in the action’s template / layout and vice versa.
Loading Cells
Cells can be loaded from views using the cell()
method and works the same inboth contexts:
- // Load an application cell
- $cell = $this->cell('Inbox');
- // Load a plugin cell
- $cell = $this->cell('Messaging.Inbox');
The above will load the named cell class and execute the display()
method.You can execute other methods using the following:
- // Run the expanded() method on the Inbox cell
- $cell = $this->cell('Inbox::expanded');
If you need controller logic to decide which cells to load in a request, you canuse the CellTrait
in your controller to enable the cell()
method there:
- namespace App\Controller;
- use App\Controller\AppController;
- use Cake\View\CellTrait;
- class DashboardsController extends AppController
- {
- use CellTrait;
- // More code.
- }
Passing Arguments to a Cell
You will often want to parameterize cell methods to make cells more flexible.By using the second and third arguments of cell()
, you can pass actionparameters and additional options to your cell classes, as an indexed array:
- $cell = $this->cell('Inbox::recent', ['-3 days']);
The above would match the following function signature:
- public function recent($since)
- {
- }
Rendering a Cell
Once a cell has been loaded and executed, you’ll probably want to render it. Theeasiest way to render a cell is to echo it:
- <?= $cell ?>
This will render the template matching the lowercased and underscored version ofour action name, e.g. display.ctp.
Because cells use View
to render templates, you can load additional cellswithin a cell template if required.
Note
Echoing a cell uses the PHP __toString()
magic method which prevents PHPfrom showing the filename and line number for any fatal errors raised. Toobtain a meanful error message, it is recommended to use theCell::render()
method, for example <?= $cell->render() ?>
.
Rendering Alternate Templates
By convention cells render templates that match the action they are executing.If you need to render a different view template, you can specify the templateto use when rendering the cell:
- // Calling render() explicitly
- echo $this->cell('Inbox::recent', ['-3 days'])->render('messages');
- // Set template before echoing the cell.
- $cell = $this->cell('Inbox');
- $cell->viewBuilder()->setTemplate('messages');
- // Before 3.4
- $cell->viewBuilder()->template('messages');
- // Before 3.1
- $cell->template = 'messages';
- echo $cell;
Caching Cell Output
When rendering a cell you may want to cache the rendered output if the contentsdon’t change often or to help improve performance of your application. You candefine the cache
option when creating a cell to enable & configure caching:
- // Cache using the default config and a generated key
- $cell = $this->cell('Inbox', [], ['cache' => true]);
- // Cache to a specific cache config and a generated key
- $cell = $this->cell('Inbox', [], ['cache' => ['config' => 'cell_cache']]);
- // Specify the key and config to use.
- $cell = $this->cell('Inbox', [], [
- 'cache' => ['config' => 'cell_cache', 'key' => 'inbox_' . $user->id]
- ]);
If a key is generated the underscored version of the cell class and templatename will be used.
Note
A new View
instance is used to render each cell and these new objectsdo not share context with the main template / layout. Each cell isself-contained and only has access to variables passed as arguments to theView::cell()
call.
Paginating Data inside a Cell
Creating a cell that renders a paginated result set can be done by leveragingthe Paginator
class of the ORM. An example of paginating a user’s favoritemessages could look like:
- namespace App\View\Cell;
- use Cake\View\Cell;
- use Cake\Datasource\Paginator;
- class FavoritesCell extends Cell
- {
- public function display($user)
- {
- $this->loadModel('Messages');
- // Create a paginator
- $paginator = new Paginator();
- // Paginate the model
- $results = $paginator->paginate(
- $this->Messages,
- $this->request->getQueryParams(),
- [
- // Use a parameterized custom finder.
- 'finder' => ['favorites' => [$user]],
- // Use scoped query string parameters.
- 'scope' => 'favorites',
- ]
- );
- $paging = $paginator->getPagingParams() + (array)$request->getParam('paging');
- $this->request = $this->request->withParam('paging', $paging));
- $this->set('favorites', $results);
- }
- }
The above cell would paginate the Messages
model using scopedpagination parameters.
New in version 3.5.0: Cake\Datasource\Paginator
was added in 3.5.0.
Cell Options
Cells can declare constructor options that are converted into properties whencreating a cell object:
- namespace App\View\Cell;
- use Cake\View\Cell;
- use Cake\Datasource\Paginator;
- class FavoritesCell extends Cell
- {
- protected $_validCellOptions = ['limit'];
- protected $limit = 3;
- public function display($userId)
- {
- $this->loadModel('Users');
- $result = $this->Users->find('friends', ['for' => $userId]);
- $this->set('favorites', $result);
- }
- }
Here we have defined a $limit
property and add limit
as a cell option.This will allow us to define the option when creating the cell:
- $cell = $this->cell('Favorites', [$user->id], ['limit' => 10])
Cell options are handy when you want data available as properties allowing youto override default values.