Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions system/View/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n

$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];

if (str_contains($this->renderVars['view'], '\\')) {
$this->renderVars['file'] = $this->viewPath . ltrim(str_replace('\\', DIRECTORY_SEPARATOR, $this->renderVars['view']), DIRECTORY_SEPARATOR);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not accurate, we can add a directory to app/Views: _modules, share,.. to explicitly separate the overwritable templates. As app/Views/share/Example/Blog/blog_view.php.

There is unlikely to be an NS with such a shared\Example\Blog value, so it looks safer. If desired, this folder can be customized. The auto-selection of the search should remain.

} else {
$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];
}

if (! is_file($this->renderVars['file'])) {
$this->renderVars['file'] = $this->loader->locateFile(
$this->renderVars['view'],
Expand Down
53 changes: 53 additions & 0 deletions tests/system/View/ViewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,4 +413,57 @@ public function testViewExcerpt(): void
$this->assertSame('CodeIgniter is a PHP full-stack web framework...', $view->excerpt('CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure.', 48));
$this->assertSame('CodeIgniter - это полнофункциональный веб-фреймворк...', $view->excerpt('CodeIgniter - это полнофункциональный веб-фреймворк на PHP, который является легким, быстрым, гибким и безопасным.', 54));
}

public function testRenderNamespacedViewPriorityToAppViews(): void
{
$loader = $this->createMock(FileLocatorInterface::class);
$loader->expects($this->never())->method('locateFile');

$view = new View($this->config, $this->viewsDir, $loader);

$view->setVar('testString', 'Hello World');
$expected = '<h1>Hello World</h1>';

$output = $view->render('Nested\simple');

$this->assertStringContainsString($expected, $output);
}

public function testRenderNamespacedViewFallsBackToLoader(): void
{
$namespacedView = 'Some\Library\View';

$realFile = $this->viewsDir . '/simple.php';

$loader = $this->createMock(FileLocatorInterface::class);
$loader->expects($this->once())
->method('locateFile')
->with($namespacedView . '.php', 'Views', 'php')
->willReturn($realFile);

$view = new View($this->config, $this->viewsDir, $loader);

$view->setVar('testString', 'Hello World');
$output = $view->render($namespacedView);

$this->assertStringContainsString('<h1>Hello World</h1>', $output);
}

public function testRenderNamespacedViewWithExplicitExtension(): void
{
$namespacedView = 'Some\Library\View.html';

$realFile = $this->viewsDir . '/simple.php';

$loader = $this->createMock(FileLocatorInterface::class);
$loader->expects($this->once())
->method('locateFile')
->with($namespacedView, 'Views', 'html')
->willReturn($realFile);

$view = new View($this->config, $this->viewsDir, $loader);
$view->setVar('testString', 'Hello World');

$view->render($namespacedView);
}
}
2 changes: 2 additions & 0 deletions user_guide_src/source/changelogs/v4.7.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Libraries
- **Image:** Added ``ImageMagickHandler::clearMetadata()`` method to remove image metadata for privacy protection.
- **ResponseTrait:** Added ``paginate``` method to simplify paginated API responses. See :ref:`ResponseTrait::paginate() <api_response_trait_paginate>` for details.
- **Time:** added methods ``Time::addCalendarMonths()`` and ``Time::subCalendarMonths()``
- **View:** Added the ability to override namespaced views (e.g., from modules) by placing a matching file structure within the **app/Views** directory. See :ref:`Overriding Namespaced Views <views-overriding-namespaced-views>` for details.


Commands
========
Expand Down
32 changes: 32 additions & 0 deletions user_guide_src/source/outgoing/views.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,38 @@ example, you could load the **blog_view.php** file from **example/blog/Views** b

.. literalinclude:: views/005.php

.. _views-overriding-namespaced-views:

Overriding Namespaced Views
===========================

.. versionadded:: 4.7.0

You can override a namespaced view by creating a matching directory structure within your main **app/Views** directory.
This allows you to customize the output of modules or packages without modifying their source code.

For example, assume you have a module named Blog with the namespace ``Example\Blog``. The original view file is located at:

.. code-block:: text

/modules
└── Example
└── Blog
└── Views
└── blog_view.php

To override this view, create a file at the matching path within your application's Views directory:

.. code-block:: text

/app
└── Views
└── Example <-- Matches the first part of namespace
└── Blog <-- Matches the second part of namespace
└── blog_view.php <-- Your custom view

Now, when you call ``view('Example\Blog\blog_view')``, CodeIgniter will automatically load your custom view from **app/Views/Example/Blog/blog_view.php** instead of the original view file.

.. _caching-views:

Caching Views
Expand Down
Loading