Skip to content

Commit 1aa3c31

Browse files
authored
Merge pull request #1729 from codeigniter4/layouts
New View Layout functionality for simple template functionality.
2 parents b3d170d + ae73abd commit 1aa3c31

File tree

8 files changed

+232
-0
lines changed

8 files changed

+232
-0
lines changed

system/View/View.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,29 @@ class View implements RendererInterface
119119
*/
120120
protected $viewsCount = 0;
121121

122+
/**
123+
* The name of the layout being used, if any.
124+
* Set by the `extend` method used within views.
125+
*
126+
* @var string
127+
*/
128+
protected $layout;
129+
130+
/**
131+
* Holds the sections and their data.
132+
*
133+
* @var array
134+
*/
135+
protected $sections = [];
136+
137+
/**
138+
* The name of the current section being rendered,
139+
* if any.
140+
*
141+
* @var string
142+
*/
143+
protected $currentSection;
144+
122145
//--------------------------------------------------------------------
123146

124147
/**
@@ -211,6 +234,16 @@ public function render(string $view, array $options = null, $saveData = null): s
211234
$output = ob_get_contents();
212235
@ob_end_clean();
213236

237+
// When using layouts, the data has already been stored
238+
// in $this->sections, and no other valid output
239+
// is allowed in $output so we'll overwrite it.
240+
if (! is_null($this->layout))
241+
{
242+
$layoutView = $this->layout;
243+
$this->layout = null;
244+
$output = $this->render($layoutView, $options, $saveData);
245+
}
246+
214247
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
215248

216249
if (CI_DEBUG && (! isset($options['debug']) || $options['debug'] === true))
@@ -376,6 +409,82 @@ public function getData()
376409

377410
//--------------------------------------------------------------------
378411

412+
/**
413+
* Specifies that the current view should extend an existing layout.
414+
*
415+
* @param string $layout
416+
*
417+
* @return $this
418+
*/
419+
public function extend(string $layout)
420+
{
421+
$this->layout = $layout;
422+
}
423+
424+
//--------------------------------------------------------------------
425+
426+
/**
427+
* Starts holds content for a section within the layout.
428+
*
429+
* @param string $name
430+
*/
431+
public function section(string $name)
432+
{
433+
$this->currentSection = $name;
434+
435+
ob_start();
436+
}
437+
438+
//--------------------------------------------------------------------
439+
440+
/**
441+
*
442+
*
443+
* @throws \Zend\Escaper\Exception\RuntimeException
444+
*/
445+
public function endSection()
446+
{
447+
$contents = ob_get_clean();
448+
449+
if (empty($this->currentSection))
450+
{
451+
throw new \RuntimeException('View themes, no current section.');
452+
}
453+
454+
// Ensure an array exists so we can store multiple entries for this.
455+
if (! array_key_exists($this->currentSection, $this->sections))
456+
{
457+
$this->sections[$this->currentSection] = [];
458+
}
459+
$this->sections[$this->currentSection][] = $contents;
460+
461+
$this->currentSection = null;
462+
}
463+
464+
//--------------------------------------------------------------------
465+
466+
/**
467+
* Renders a section's contents.
468+
*
469+
* @param string $sectionName
470+
*/
471+
public function renderSection(string $sectionName)
472+
{
473+
if (! isset($this->sections[$sectionName]))
474+
{
475+
echo '';
476+
477+
return;
478+
}
479+
480+
foreach ($this->sections[$sectionName] as $contents)
481+
{
482+
echo $contents;
483+
}
484+
}
485+
486+
//--------------------------------------------------------------------
487+
379488
/**
380489
* Returns the performance data that might have been collected
381490
* during the execution. Used primarily in the Debug Toolbar.

tests/system/View/ViewTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,33 @@ public function testPerformanceNonLogging()
266266
$this->assertEquals(0, count($view->getPerformanceData()));
267267
}
268268

269+
public function testRenderLayoutExtendsCorrectly()
270+
{
271+
$view = new View($this->config, $this->viewsDir, $this->loader);
272+
273+
$view->setVar('testString', 'Hello World');
274+
$expected = "<p>Open</p>\n<h1>Hello World</h1>";
275+
276+
$this->assertContains($expected, $view->render('extend'));
277+
}
278+
279+
public function testRenderLayoutMakesDataAvailableToBoth()
280+
{
281+
$view = new View($this->config, $this->viewsDir, $this->loader);
282+
283+
$view->setVar('testString', 'Hello World');
284+
$expected = "<p>Open</p>\n<h1>Hello World</h1>\n<p>Hello World</p>";
285+
286+
$this->assertContains($expected, $view->render('extend'));
287+
}
288+
289+
public function testRenderLayoutSupportsMultipleOfSameSection()
290+
{
291+
$view = new View($this->config, $this->viewsDir, $this->loader);
292+
293+
$view->setVar('testString', 'Hello World');
294+
$expected = "<p>First</p>\n<p>Second</p>";
295+
296+
$this->assertContains($expected, $view->render('extend_two'));
297+
}
269298
}

tests/system/View/Views/extend.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?= $this->extend('layout') ?>
2+
3+
<?= $this->section('content') ?>
4+
<h1><?= $testString ?></h1>
5+
<?= $this->endSection() ?>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?= $this->extend('layout') ?>
2+
3+
<?= $this->section('content') ?>
4+
<p>First</p>
5+
<?= $this->endSection() ?>
6+
7+
8+
<?= $this->section('content') ?>
9+
<p>Second</p>
10+
<?= $this->endSection() ?>

tests/system/View/Views/layout.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<p>Open</p>
2+
<?= $this->renderSection('content') ?>
3+
<p><?= $testString ?></p>

user_guide_src/source/changelogs/next.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Version |version|
44
Release Date: Not released
55

66
Highlights:
7+
- New View Layouts provide simple way to create site site view templates.
78

89

910
The list of changed files follows, with PR numbers shown.

user_guide_src/source/outgoing/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ View components are used to build what is returned to the user.
1010
views
1111
view_cells
1212
view_renderer
13+
view_layouts
1314
view_parser
1415
response
1516
api_responses
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
############
2+
View Layouts
3+
############
4+
5+
.. contents::
6+
:local:
7+
:depth: 2
8+
9+
CodeIgniter supports a simple, yet very flexible, layout system that makes it simple to use one or more
10+
base page layouts across your application. Layouts support sections of content that can be inserted from
11+
any view being rendered. You could create different layouts to support one-column, two-column,
12+
blog archive pages, and more. Layouts are never directly rendered. Instead, you render a view, which
13+
specifies the layout that it wants to extend.
14+
15+
*****************
16+
Creating A Layout
17+
*****************
18+
19+
Layouts are views like any other. The only difference is their intended usage. Layouts are the only view
20+
files that would make use of the ``renderSection()`` method. This method acts as a placeholder for content.
21+
22+
::
23+
24+
<!doctype html>
25+
<html>
26+
<head>
27+
<title>My Layout</title>
28+
</head>
29+
<body>
30+
<?= $this->renderSection('content') ?>
31+
</body>
32+
</html>
33+
34+
The renderSection() method only has one argument - the name of the section. That way any child views know
35+
what to name the content section.
36+
37+
**********************
38+
Using Layouts in Views
39+
**********************
40+
41+
Whenveer a view wants to be inserted into a layout, it must use the ``extend()`` method at the top of the file::
42+
43+
<?= $this->extend('default') ?>
44+
45+
The extend method takes the name of any view file that you wish to use. Since they are standard views, they will
46+
be located just like a view. By default, it will look in the application's View directory, but will also scan
47+
other PSR-4 defined namespaces. You can include a namespace to locate the view in particular namespace View directory::
48+
49+
<?= $this->extend('Blog\Views\default') ?>
50+
51+
All content within a view that extends a layout must be included within ``section($name)`` and ``endSection()`` method calls.
52+
Any content between these calls will be inserted into the layout wherever the ``renderSection($name)`` call that
53+
matches the section name exists.::
54+
55+
<?= $this->extend('default') ?>
56+
57+
<?= $this->section('content') ?>
58+
<h1>Hello World!</h1>
59+
<?= $this->endSection() ?>
60+
61+
The ``endSection()`` does not need the section name. It automatically knows which one to close.
62+
63+
******************
64+
Rendering the View
65+
******************
66+
67+
Rendering the view and it's layout is done exactly as any other view would be displayed within a controller::
68+
69+
public function index()
70+
{
71+
echo view('some_view');
72+
}
73+
74+
The renderer is smart enough to detect whether the view should be rendered on its own, or if it needs a layout.

0 commit comments

Comments
 (0)