With all this, you now probably know the most contrived part of the Yii conventions. Let's now talk about the custom renderers.
Yii uses renderers to process the view file you referenced in the render()
method, based on its file extension. If the View component of Yii is unable to find any renderer for the given view file, it treats this view file as a PHP script, and executes the renderPhpFile()
method:
/**
* @param string $_file_ the view file.
* @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return string the rendering result
*/
public function renderPhpFile($_file_, $_params_ = [])
{
ob_start();
ob_implicit_flush(false);
extract($_params_, EXTR_OVERWRITE);
require($_file_);
return ob_get_clean();
}
As you can see in the preceding code, Yii will just require the file for buffering the output. An important effect of custom renderers is that you can do anything inside your view files, which will be dangerous if you really do anything. You should ideally treat PHP view files as being in some template system's format, only allowing them to paste data passed to them as associative arrays or plain data structures.
Yii 2 does not ship along with any custom renderers. Let's think about how we can utilize this feature for our convenience.
The original design goal of the developers, obviously, was that you'll write the parser for some template system format, and then you'll be able to write view files not as raw PHP, but as these (supposedly, more restricted, and domain-specific) templates. By giving the templates a new format with a distinct extension, you'll force Yii to use your custom parser on them, which will supposedly convert the given template to, say, HTML, which you'll send to the client.
Let's employ a simple solution here, which is using Markdown (http://daringfireball.net/projects/markdown/syntax) to author the static pages. Let's say we want the set of user-level documentation pages, which will be handcrafted by a skilled tech writer. You probably neither want to force him or her to author pages in HTML nor allow him or her to author them in Word to painfully convert it to HTML yourself. Markdown will be a simple middle ground for this task.
Employing a custom renderer to render static Markdown files is pretty simple. We assume that you have the same CRM application we have been building in the previous two chapters.
First, let's declare what we want:
./cept generate:cept acceptance Documentation
Then, in the tests/acceptance/DocumentationCept.php
file just generated, use the following code:
$I = new AcceptanceTesterCRMUserSteps($scenario);
$I->wantTo('see whether user documentation is accessible'),
$I->amOnPage('/site/docs'),
$I->see('Documentation', 'h1'),
$I->seeLargeBodyOfText();
The seeLargeBodyOfText()
method will be defined in the CRMUserSteps
class as follows:
public function seeLargeBodyOfText() { $I = $this; $text = $I->grabTextFrom('p'), // naive selector $I->seeContentIsLong($text); }
We basically assert that we do see a documentation page if there's a heading called Documentation and a lengthy body of text below that. It's pretty naïve, of course, but we cannot afford to write an AI code capable of automatically checking whether the given text is indeed a documentation page or not.
We will place the seeContentIsLong()
method in the AcceptanceHelper
class inside tests/_support/AcceptanceHelper.php
:
public function seeContentIsLong($content, $trigger_length = 100) { $this->assertGreaterThen($trigger_length, strlen($content)); }
We have to do this because we don't have assertions in the AcceptanceTester
class itself. Don't forget to run ./cept build
afterwards, as we have modified the module of AcceptanceTester
(yup, AcceptanceHelper
is technically a Codeception module).
Now, run the test and watch it fail:
$ ./cept run tests/acceptance/DocumentationCept.php 1) Failed to see whether user documentation is accessible in DocumentationCept.php Sorry, I couldn't see "Documentation","h1": Failed asserting that any element by 'h1' on page /site/docs Elements: + <h1> Not Found (#404) contains text 'Documentation' Scenario Steps: 2. I see "Documentation","h1" 1. I am on page "/site/docs"
Of course, we don't have the /site/docs
route handler yet. Create the SiteController.actionDocs
method:
public function actionDocs() { return $this->render('docindex.md'), }
Note the absence of any Markdown-handling code, which is the whole point of using a custom renderer.
Now, the view file at views/site/docindex.md
should look as follows:
# Documentation Here we'll see some *Markdown* code. It's easier to write text documents with simple formatting this way. Imagine the user documentation here, describing: 1. [How to add Customers](/customers/add) 2. [How to find Customer by phone number](/customers/query) 3. [How to manage Services](/services)
The preceding code is a perfectly valid page in the Markdown format.
If you access the /site/docs
page now, you'll see that the text is rendered verbatim, because docindex.md
is being processed as the PHP script, and as it does not contain the <?php
processing instruction, it is parsed as HTML text. Here is the screenshot:
Finally, let's write the custom renderer itself. As this class is a part of the application infrastructure and has nothing to do with the domain model or the route handling, let's create a separate utilities
subdirectory and namespace for it. So, in a utilities/MarkdownRenderer.php
file, write the following:
<?php namespace apputilities; use yiihelpersMarkdown; use yiiaseViewRenderer; class MarkdownRenderer extends ViewRenderer { public function render($view, $file, $params) { // TODO } }
The apputilities
namespace is automatically mapped to the utilities
directory under the root of the code base, thanks to the Yii 2 autoloader we require in the index.php
entry script.
You may ask, how do we render the Markdown? We can render the Markdown as follows:
public function render($view, $file, $params) { return Markdown::process(file_get_contents($file)); }
Yii 2 includes the whole Markdown processor as one of its dependencies.
Be cautious, though, because just calling the raw file_get_contents()
method is pretty unsafe. We rely on $file
being safely constructed by the Yii internals here.
The MarkdownRenderer
class does not have the proper unit tests because arguably all we did was wrap two built-in and already extensively tested functions into one call. To simplify the discussion, we omitted the proper test-driving MarkdownRenderer
. Note, however, that in more complex cases, you must not do this. It is not all the time that you have a parser as simple as the one presented here.
With MarkdownRenderer
written, we have to wire it to the application. Add the description of our custom renderer to the components.view.renderers.md
section of the config at config/web.php
:
'components' => [ 'view' => [ 'renderers' => [ 'md' => [ 'class' => 'apputilitiesMarkdownRenderer' ] ] ] ]
The index in the renderers
array is the filename extension. In our case, it should be md
. Our renderer does not have any properties, so to properly reference it in the application configuration, we just need to provide the fully qualified name of its class.
Now run the tests. They will be executed successfully, as shown in the following screenshot:
At the /site/docs
location, you can see the properly formatted HTML page now:
18.225.72.245