diff --git a/system/View/View.php b/system/View/View.php index 935729393626..33efdaa21e13 100644 --- a/system/View/View.php +++ b/system/View/View.php @@ -109,6 +109,7 @@ class View implements RendererInterface */ protected $layout; + /** * Holds the sections and their data. * @@ -121,9 +122,18 @@ class View implements RendererInterface * if any. * * @var string|null + * @deprecated */ protected $currentSection; + /** + * The name of the current section being rendered, + * if any. + * + * @var array + */ + protected $sectionStack = []; + /** * Constructor * @@ -227,7 +237,7 @@ public function render(string $view, array $options = null, bool $saveData = nul // When using layouts, the data has already been stored // in $this->sections, and no other valid output // is allowed in $output so we'll overwrite it. - if (! is_null($this->layout) && empty($this->currentSection)) + if (! is_null($this->layout) && $this->sectionStack === []) { $layoutView = $this->layout; $this->layout = null; @@ -402,35 +412,44 @@ public function extend(string $layout) /** * Starts holds content for a section within the layout. * - * @param string $name + * @param string $name Section name + * + * @return void + * */ public function section(string $name) { + //Saved to prevent BC. $this->currentSection = $name; + $this->sectionStack[] = $name; ob_start(); } /** + * Captures the last section + * + * @return void * @throws RuntimeException */ public function endSection() { $contents = ob_get_clean(); - if (empty($this->currentSection)) + if ($this->sectionStack === []) { throw new RuntimeException('View themes, no current section.'); } + $section = array_pop($this->sectionStack); + // Ensure an array exists so we can store multiple entries for this. - if (! array_key_exists($this->currentSection, $this->sections)) + if (! array_key_exists($section, $this->sections)) { - $this->sections[$this->currentSection] = []; + $this->sections[$section] = []; } - $this->sections[$this->currentSection][] = $contents; - $this->currentSection = null; + $this->sections[$section][] = $contents; } /** diff --git a/tests/system/View/ViewTest.php b/tests/system/View/ViewTest.php index 8dd1d4431a38..7d2cf84becbb 100644 --- a/tests/system/View/ViewTest.php +++ b/tests/system/View/ViewTest.php @@ -367,7 +367,7 @@ public function testRenderSaveDataCover() $this->assertEquals(true, $this->getPrivateProperty($view, 'saveData')); } - public function testRenderSaveDataUseAflterSaveDataFalse() + public function testRenderSaveDataUseAfterSaveDataFalse() { $view = new View($this->config, $this->viewsDir, $this->loader); $view->setVar('testString', 'test'); @@ -387,4 +387,17 @@ public function testCachedAutoDiscoverAndRender() // this second renderings should go thru the cache $this->assertStringContainsString($expected, $view->render('Nested/simple', ['cache' => 10])); } + + public function testRenderNestedSections() + { + $view = new View($this->config, $this->viewsDir, $this->loader); + + $view->setVar('testString', 'Hello World'); + + $content = $view->render('nested_section'); + + $this->assertStringContainsString('

First

', $content); + $this->assertStringContainsString('

Second

', $content); + $this->assertStringContainsString('

Third

', $content); + } } diff --git a/tests/system/View/Views/nested_section.php b/tests/system/View/Views/nested_section.php new file mode 100644 index 000000000000..a89e6234ec5c --- /dev/null +++ b/tests/system/View/Views/nested_section.php @@ -0,0 +1,14 @@ +extend('layout'); ?> + +section('content'); ?> +

Second

+ + section('content'); ?> +

First

+ endSection(); ?> + +endSection(); ?> + +section('content'); ?> +

Third

+endSection(); ?> diff --git a/user_guide_src/source/changelogs/v4.1.2.rst b/user_guide_src/source/changelogs/v4.1.2.rst index 76bad454c54e..3de19b67a98e 100644 --- a/user_guide_src/source/changelogs/v4.1.2.rst +++ b/user_guide_src/source/changelogs/v4.1.2.rst @@ -16,6 +16,7 @@ Enhancements: Changes: +- Layouts in views now support nested sections. - ``Response::getCookie`` now returns a ``Cookie`` instance instead of an array of cookie attributes. - ``Response::getCookies`` now returns an array of ``Cookie`` instances instead of array of array of attributes. - To eliminate warnings from modern browsers' consoles, empty samesite values will be defaulted to ``Lax`` on cookie dispatch. @@ -30,6 +31,7 @@ Changes: Deprecations: +- Deprecated ``Codeigniter\View\View::$currentSection`` property. - Language strings and exceptions on invalid cookie samesite are deprecated for the ``CookieException``'s own exception message. - Deprecated `CodeIgniter\Entity` in favor of `CodeIgniter\Entity\Entity` - Deprecated cookie-related properties of ``Response`` in order to use the ``Cookie`` class. diff --git a/user_guide_src/source/outgoing/view_layouts.rst b/user_guide_src/source/outgoing/view_layouts.rst index e93ea9e17e7c..a0e47ab94499 100644 --- a/user_guide_src/source/outgoing/view_layouts.rst +++ b/user_guide_src/source/outgoing/view_layouts.rst @@ -60,6 +60,18 @@ matches the section name exists.:: The ``endSection()`` does not need the section name. It automatically knows which one to close. +Sections can contain nested sections:: + + extend('default') ?> + + section('content') ?> +

Hello World!

+ section('javascript') ?> + let a = 'a'; + endSection() ?> + endSection() ?> + + ****************** Rendering the View ******************