View blocks - markstory/cakephp GitHub Wiki

Blocks and slots are a concept found in other templating systems. I think CakePHP could benefit from them, as they solve a few common problems.

Problems

  • Views can only have one layout. Dynamic regions in the layout, like sidebars and navigation have to be cobbled together using elements, and passing element names around.
  • There is no existing way to 'extend' a view or a layout, and provide only the parts that are different. You must either make extensive use of elements, or repeat HTML + view code.
  • Using elements can be difficult in CMS style applications, as it requires having additional files on the filesystem.
  • $scripts_for_layout is a known problem. It doesn't give enough flexibility, and is hard to extend/divide.
  • $content_for_layout is crufty and annoying.

Possible solution

Implement a block/slot system, and view/layout extension. Having this will fix the issues illustrated above. Blocks/slots would solve the dynamic regions and scripts_for_layout issue. While view inheritance would be useful in keeping view code DRY.

Blocks/slots

Blocks/slots allow for the creation of inline sections of content, similar to elements, but without the overhead of opening a new file and creating a new context. Blocks could be implemented as methods on the view class, or via a helper:

<?php
// in the view
$this->start('sidebar');
echo 'This is the sidebar';
$this->end();

// In the parent view/layout
echo $this->fetch('sidebar');

Blocks could be fetched multiple times, if necessary. You could also modify/replace blocks:

<?php
// in the view
$this->start('sidebar');
echo 'This is the sidebar';
$this->end();

// append with a capturing block
$this->append('sidebar');
echo 'Also in the sidebar';
$this->end();

// append with a string 
$this->append('sidebar', $this->element('some_content'));

// replace/set (Note this method has overlay with existing View::set())
$this->assign('sidebar', $this->element('some_content'));

Blocks would also be used by CakePHP internally. Helper methods like HtmlHelper::meta|script|css would all use blocks instead of View:addScript(). $content_for_layout would be replaced with a content block. This makes extended views and layouts work the same.

Blocks would be implemented as a separate object, but have wrapper methods in View to hide the implementation.

<?php
// the following are the same
$this->Blocks->get('content');
$this->fetch('content');

Template inheritance

Template inheritance would work in tandem with blocks/slots to provide a different approach to extensible views. By defining only the sections that are unique (the blocks and content) you can keep the common code in a parent template. This allows for DRY'er view code:

<?php
$this->extend('view_page');

// create a capturing block
$this->start('sidebar');
echo 'the sidebar';
$this->end();

// set another block
$this->assign('sub_title', 'The sub title');

// The rest of the content not in blocks goes into the 'content' block
echo 'This is some content';
?>
<!-- In view_page.ctp -->
<h3><?php echo $this->fetch('sub_title'); ?></h3>
<?php echo $this->fetch('content'); ?>

Some implementation points:

  • Extended views can be nested to any depth.
  • Any view section - Elements, views, layouts can be extended.
  • Any un-captured content from the child template is available as the 'content' block.
  • Once the extension stack is done for a view, the layout will be rendered. The layout can also be extended as necessary.

Additional callbacks

In order for Helpers like CacheHelper to continue functioning as they have in the past, two new callbacks will be introduced. beforeRenderFile and afterRenderFile. These callbacks are similar to beforeRender, afterRender, beforeLayout and afterLayout. The key difference is that the new callbacks will be fired around every view fragment. Elements, extended views, views, and layouts. The existing callbacks could possibly be deprecated and/or replaced with a more consistent API when a backwards incompatible release is reached.

CacheHelper would also finally be able to handle nocache tags in elements, which is a nice win.

Callback listeners

Since there will be many more callbacks being fired during view rendering it might make sense to provide a way for helpers to opt-out of receiving callbacks, or require that the new callbacks be opt-in. One possible solution would be to have a $callbacks property on a helper. This would contain the list of callbacks a helper wants to receive:

<?php
// no callbacks.
protected $callbacks = array();
    
// only a few callbacks.
protected $callbacks = array('beforeRender', 'beforeLayout');

This could help with view layer performance overall as well. Helpers could be omitted from the callback cycle as desired. It may not be required, but benchmarking will tell.

Status

The changes described here have been implemented in 2.1

⚠️ **GitHub.com Fallback** ⚠️