Skip to content

Commit f929715

Browse files
committed
refactor: add appOverridesFolder for namespaced view overrides
1 parent 12167db commit f929715

File tree

5 files changed

+71
-13
lines changed

5 files changed

+71
-13
lines changed

app/Config/View.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,21 @@ class View extends BaseView
5959
* @var list<class-string<ViewDecoratorInterface>>
6060
*/
6161
public array $decorators = [];
62+
63+
/**
64+
* Subdirectory within app/Views for namespaced view overrides.
65+
*
66+
* Namespaced views will be searched in:
67+
*
68+
* app/Views/{$appOverridesFolder}/{Namespace}/{view_path}.{php|html...}
69+
*
70+
* This allows application-level overrides for package or module views
71+
* without modifying vendor source files.
72+
*
73+
* Examples:
74+
* 'overrides' -> app/Views/overrides/Example/Blog/post/card.php
75+
* 'vendor' -> app/Views/vendor/Example/Blog/post/card.php
76+
* '' -> app/Views/Example/Blog/post/card.php (direct mapping)
77+
*/
78+
public string $appOverridesFolder = 'overrides';
6279
}

system/View/View.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,13 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
202202
$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];
203203

204204
if (str_contains($this->renderVars['view'], '\\')) {
205-
$this->renderVars['file'] = $this->viewPath . ltrim(str_replace('\\', DIRECTORY_SEPARATOR, $this->renderVars['view']), DIRECTORY_SEPARATOR);
205+
$overrideFolder = $this->config->appOverridesFolder !== ''
206+
? trim($this->config->appOverridesFolder, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR
207+
: '';
208+
209+
$this->renderVars['file'] = $this->viewPath
210+
. $overrideFolder
211+
. ltrim(str_replace('\\', DIRECTORY_SEPARATOR, $this->renderVars['view']), DIRECTORY_SEPARATOR);
206212
} else {
207213
$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];
208214
}

tests/system/View/ViewTest.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ protected function setUp(): void
3434
{
3535
parent::setUp();
3636

37-
$this->loader = service('locator');
38-
$this->viewsDir = __DIR__ . '/Views';
39-
$this->config = new Config\View();
37+
$this->loader = service('locator');
38+
$this->viewsDir = __DIR__ . '/Views';
39+
$this->config = new Config\View();
40+
$this->config->appOverridesFolder = '';
4041
}
4142

4243
public function testSetVarStoresData(): void
@@ -466,4 +467,23 @@ public function testRenderNamespacedViewWithExplicitExtension(): void
466467

467468
$view->render($namespacedView);
468469
}
470+
471+
public function testOverrideWithCustomFolderChecksSubdirectory(): void
472+
{
473+
$this->config->appOverridesFolder = 'overrides';
474+
475+
$loader = $this->createMock(FileLocatorInterface::class);
476+
$loader->expects($this->once())
477+
->method('locateFile')
478+
->with('Nested\simple.php', 'Views', 'php')
479+
->willReturn($this->viewsDir . '/simple.php');
480+
481+
$view = new View($this->config, $this->viewsDir, $loader);
482+
483+
$view->setVar('testString', 'Fallback Content');
484+
485+
$output = $view->render('Nested\simple');
486+
487+
$this->assertStringContainsString('<h1>Fallback Content</h1>', $output);
488+
}
469489
}

user_guide_src/source/changelogs/v4.7.0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ Libraries
125125
- **Image:** Added ``ImageMagickHandler::clearMetadata()`` method to remove image metadata for privacy protection.
126126
- **ResponseTrait:** Added ``paginate``` method to simplify paginated API responses. See :ref:`ResponseTrait::paginate() <api_response_trait_paginate>` for details.
127127
- **Time:** added methods ``Time::addCalendarMonths()`` and ``Time::subCalendarMonths()``
128-
- **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.
128+
- **View:** Added the ability to override namespaced views (e.g., from modules/packages) by placing a matching file structure within the **app/Views/overrides** directory. See :ref:`Overriding Namespaced Views <views-overriding-namespaced-views>` for details.
129129

130130

131131
Commands

user_guide_src/source/outgoing/views.rst

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,24 @@ Overriding Namespaced Views
111111

112112
.. versionadded:: 4.7.0
113113

114-
You can override a namespaced view by creating a matching directory structure within your main **app/Views** directory.
115-
This allows you to customize the output of modules or packages without modifying their source code.
114+
You can override a namespaced view by creating a matching directory structure within your application's **app/Views** directory.
115+
This allows you to customize the output of modules or packages without modifying their core source code.
116116

117-
For example, assume you have a module named Blog with the namespace ``Example\Blog``. The original view file is located at:
117+
Configuration
118+
-------------
119+
120+
By default, overrides are looked for in the **app/Views/overrides** directory. You can configure this location via the ``$appOverridesFolder`` property in **app/Config/View.php**:
121+
122+
.. code-block:: php
123+
124+
public string $appOverridesFolder = 'overrides';
125+
126+
If you prefer to map namespaces directly to the root of **app/Views** (without a subdirectory), you can set this value to an empty string (``''``).
127+
128+
Example
129+
-------
130+
131+
Assume you have a module named **Blog** with the namespace ``Example\Blog``. The original view file is located at:
118132

119133
.. code-block:: text
120134
@@ -124,17 +138,18 @@ For example, assume you have a module named Blog with the namespace ``Example\Bl
124138
└── Views
125139
└── blog_view.php
126140
127-
To override this view, create a file at the matching path within your application's Views directory:
141+
To override this view (using the default configuration), create a file at the matching path within **app/Views/overrides**:
128142

129143
.. code-block:: text
130144
131145
/app
132146
└── Views
133-
└── Example <-- Matches the first part of namespace
134-
└── Blog <-- Matches the second part of namespace
135-
└── blog_view.php <-- Your custom view
147+
└── overrides <-- Configured $appOverridesFolder
148+
└── Example <-- Matches the first part of namespace
149+
└── Blog <-- Matches the second part of namespace
150+
└── blog_view.php <-- Your custom view
136151
137-
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.
152+
Now, when you call ``view('Example\Blog\blog_view')``, CodeIgniter will automatically load your custom view from **app/Views/overrides/Example/Blog/blog_view.php** instead of the original module view file.
138153

139154
.. _caching-views:
140155

0 commit comments

Comments
 (0)