refactor(view-components): use CI4's View Decorators to render components
This commit is contained in:
parent
a182d96f18
commit
a3ebd6c9a4
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Libraries\View;
|
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
use Config\View;
|
||||||
use ViewThemes\Theme;
|
use ViewThemes\Theme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,13 +7,10 @@ namespace Config;
|
||||||
use App\Libraries\Breadcrumb;
|
use App\Libraries\Breadcrumb;
|
||||||
use App\Libraries\Negotiate;
|
use App\Libraries\Negotiate;
|
||||||
use App\Libraries\Router;
|
use App\Libraries\Router;
|
||||||
use App\Libraries\View;
|
|
||||||
use CodeIgniter\Config\BaseService;
|
use CodeIgniter\Config\BaseService;
|
||||||
use CodeIgniter\HTTP\Request;
|
use CodeIgniter\HTTP\Request;
|
||||||
use CodeIgniter\HTTP\RequestInterface;
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
use CodeIgniter\Router\RouteCollectionInterface;
|
use CodeIgniter\Router\RouteCollectionInterface;
|
||||||
use Config\Services as AppServices;
|
|
||||||
use Config\View as ViewConfig;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services Configuration file.
|
* Services Configuration file.
|
||||||
|
@ -48,24 +45,6 @@ class Services extends BaseService
|
||||||
return new Router($routes, $request);
|
return new Router($routes, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The Renderer class is the class that actually displays a file to the user. The default View class within
|
|
||||||
* CodeIgniter is intentionally simple, but this service could easily be replaced by a template engine if the user
|
|
||||||
* needed to.
|
|
||||||
*/
|
|
||||||
public static function renderer(?string $viewPath = null, ?ViewConfig $config = null, bool $getShared = true): View
|
|
||||||
{
|
|
||||||
if ($getShared) {
|
|
||||||
return static::getSharedInstance('renderer', $viewPath, $config);
|
|
||||||
}
|
|
||||||
|
|
||||||
$viewPath = $viewPath ?: config('Paths')
|
|
||||||
->viewDirectory;
|
|
||||||
$config = $config ?? config('View');
|
|
||||||
|
|
||||||
return new View($config, $viewPath, AppServices::locator(), CI_DEBUG, AppServices::logger());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Negotiate class provides the content negotiation features for working the request to determine correct
|
* The Negotiate class provides the content negotiation features for working the request to determine correct
|
||||||
* language, encoding, charset, and more.
|
* language, encoding, charset, and more.
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\View as BaseView;
|
use CodeIgniter\Config\View as BaseView;
|
||||||
use CodeIgniter\View\ViewDecoratorInterface;
|
use CodeIgniter\View\ViewDecoratorInterface;
|
||||||
|
use ViewComponents\Decorator;
|
||||||
|
|
||||||
class View extends BaseView
|
class View extends BaseView
|
||||||
{
|
{
|
||||||
|
@ -46,5 +47,5 @@ class View extends BaseView
|
||||||
*
|
*
|
||||||
* @var class-string<ViewDecoratorInterface>[]
|
* @var class-string<ViewDecoratorInterface>[]
|
||||||
*/
|
*/
|
||||||
public array $decorators = [];
|
public array $decorators = [Decorator::class];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Libraries;
|
|
||||||
|
|
||||||
use CodeIgniter\Debug\Toolbar\Collectors\Views;
|
|
||||||
use CodeIgniter\Filters\DebugToolbar;
|
|
||||||
use CodeIgniter\View\Exceptions\ViewException;
|
|
||||||
use CodeIgniter\View\View as CodeIgniterView;
|
|
||||||
use Config\Toolbar;
|
|
||||||
|
|
||||||
class View extends CodeIgniterView
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Builds the output based upon a file name and any
|
|
||||||
* data that has already been set.
|
|
||||||
*
|
|
||||||
* Valid $options:
|
|
||||||
* - cache Number of seconds to cache for
|
|
||||||
* - cache_name Name to use for cache
|
|
||||||
*
|
|
||||||
* @param string $view File name of the view source
|
|
||||||
* @param array<string, mixed>|null $options Reserved for 3rd-party uses since
|
|
||||||
* it might be needed to pass additional info
|
|
||||||
* to other template engines.
|
|
||||||
* @param bool|null $saveData If true, saves data for subsequent calls,
|
|
||||||
* if false, cleans the data after displaying,
|
|
||||||
* if null, uses the config setting.
|
|
||||||
*/
|
|
||||||
public function render(string $view, ?array $options = null, ?bool $saveData = null): string
|
|
||||||
{
|
|
||||||
$this->renderVars['start'] = microtime(true);
|
|
||||||
|
|
||||||
// Store the results here so even if
|
|
||||||
// multiple views are called in a view, it won't
|
|
||||||
// clean it unless we mean it to.
|
|
||||||
$saveData = $saveData ?? $this->saveData;
|
|
||||||
$fileExt = pathinfo($view, PATHINFO_EXTENSION);
|
|
||||||
$realPath = $fileExt === '' ? $view . '.php' : $view; // allow Views as .html, .tpl, etc (from CI3)
|
|
||||||
$this->renderVars['view'] = $realPath;
|
|
||||||
$this->renderVars['options'] = $options ?? [];
|
|
||||||
|
|
||||||
// Was it cached?
|
|
||||||
if (isset($this->renderVars['options']['cache'])) {
|
|
||||||
$cacheName = $this->renderVars['options']['cache_name'] ?? str_replace(
|
|
||||||
'.php',
|
|
||||||
'',
|
|
||||||
$this->renderVars['view']
|
|
||||||
);
|
|
||||||
$cacheName = str_replace(['\\', '/'], '', $cacheName);
|
|
||||||
|
|
||||||
$this->renderVars['cacheName'] = $cacheName;
|
|
||||||
|
|
||||||
if ($output = cache($this->renderVars['cacheName'])) {
|
|
||||||
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->renderVars['file'] = $this->viewPath . $this->renderVars['view'];
|
|
||||||
|
|
||||||
if (! is_file($this->renderVars['file'])) {
|
|
||||||
$this->renderVars['file'] = $this->loader->locateFile(
|
|
||||||
$this->renderVars['view'],
|
|
||||||
'Views',
|
|
||||||
$fileExt === '' ? 'php' : $fileExt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// locateFile will return an empty string if the file cannot be found.
|
|
||||||
if ($this->renderVars['file'] === '') {
|
|
||||||
throw ViewException::forInvalidFile($this->renderVars['view']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make our view data available to the view.
|
|
||||||
$this->tempData = $this->tempData ?? $this->data;
|
|
||||||
|
|
||||||
if ($saveData) {
|
|
||||||
$this->data = $this->tempData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save current vars
|
|
||||||
$renderVars = $this->renderVars;
|
|
||||||
|
|
||||||
$output = (function (): string {
|
|
||||||
extract($this->tempData);
|
|
||||||
ob_start();
|
|
||||||
include $this->renderVars['file'];
|
|
||||||
|
|
||||||
return ob_get_clean() ?: '';
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Get back current vars
|
|
||||||
$this->renderVars = $renderVars;
|
|
||||||
|
|
||||||
// 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 ($this->layout !== null && $this->sectionStack === []) {
|
|
||||||
$layoutView = $this->layout;
|
|
||||||
$this->layout = null;
|
|
||||||
// Save current vars
|
|
||||||
$renderVars = $this->renderVars;
|
|
||||||
$output = $this->render($layoutView, $options, $saveData);
|
|
||||||
// Get back current vars
|
|
||||||
$this->renderVars = $renderVars;
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = service('components')
|
|
||||||
->setCurrentView($this->renderVars['file'])
|
|
||||||
->render($output);
|
|
||||||
|
|
||||||
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
|
|
||||||
|
|
||||||
if (($this->debug && (! isset($options['debug']) || $options['debug'] === true))
|
|
||||||
&& in_array(DebugToolbar::class, service('filters')->getFiltersClass()['after'], true)
|
|
||||||
) {
|
|
||||||
$toolbarCollectors = config(Toolbar::class)->collectors;
|
|
||||||
|
|
||||||
if (in_array(Views::class, $toolbarCollectors, true)) {
|
|
||||||
// Clean up our path names to make them a little cleaner
|
|
||||||
$this->renderVars['file'] = clean_path($this->renderVars['file']);
|
|
||||||
$this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file'];
|
|
||||||
|
|
||||||
$output = '<!-- DEBUG-VIEW START ' . $this->renderVars['file'] . ' -->' . PHP_EOL
|
|
||||||
. $output . PHP_EOL
|
|
||||||
. '<!-- DEBUG-VIEW ENDED ' . $this->renderVars['file'] . ' -->' . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should we cache?
|
|
||||||
if (isset($this->renderVars['options']['cache'])) {
|
|
||||||
cache()->save($this->renderVars['cacheName'], $output, (int) $this->renderVars['options']['cache']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->tempData = null;
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,23 +14,11 @@ class ComponentRenderer
|
||||||
{
|
{
|
||||||
protected ViewComponents $config;
|
protected ViewComponents $config;
|
||||||
|
|
||||||
/**
|
|
||||||
* File name of the view source
|
|
||||||
*/
|
|
||||||
protected string $currentView;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->config = config('ViewComponents');
|
$this->config = config('ViewComponents');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCurrentView(string $view): self
|
|
||||||
{
|
|
||||||
$this->currentView = $view;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render(string $output): string
|
public function render(string $output): string
|
||||||
{
|
{
|
||||||
// Try to locate any custom tags, with PascalCase names like: Button, Label, etc.
|
// Try to locate any custom tags, with PascalCase names like: Button, Label, etc.
|
||||||
|
@ -139,10 +127,7 @@ class ComponentRenderer
|
||||||
// TODO: Is there a better way to locate components local to current module?
|
// TODO: Is there a better way to locate components local to current module?
|
||||||
$pathsToDiscover = [];
|
$pathsToDiscover = [];
|
||||||
$lookupPaths = $this->config->lookupPaths;
|
$lookupPaths = $this->config->lookupPaths;
|
||||||
$pathsToDiscover = array_filter($lookupPaths, function ($path): bool {
|
$pathsToDiscover = array_values($lookupPaths);
|
||||||
return str_starts_with($this->currentView, $path);
|
|
||||||
});
|
|
||||||
$pathsToDiscover = array_values($pathsToDiscover);
|
|
||||||
$pathsToDiscover[] = $this->config->defaultLookupPath;
|
$pathsToDiscover[] = $this->config->defaultLookupPath;
|
||||||
|
|
||||||
$namePath = str_replace('.', '/', $name);
|
$namePath = str_replace('.', '/', $name);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ViewComponents;
|
||||||
|
|
||||||
|
use CodeIgniter\View\ViewDecoratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Decorator
|
||||||
|
*
|
||||||
|
* Enables rendering of View Components into the views.
|
||||||
|
*
|
||||||
|
* Borrowed and adapted from https://github.com/lonnieezell/Bonfire2/
|
||||||
|
*/
|
||||||
|
class Decorator implements ViewDecoratorInterface
|
||||||
|
{
|
||||||
|
private static ?ComponentRenderer $components = null;
|
||||||
|
|
||||||
|
public static function decorate(string $html): string
|
||||||
|
{
|
||||||
|
$components = self::factory();
|
||||||
|
|
||||||
|
return $components->render($html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create a new instance of ComponentRenderer
|
||||||
|
*/
|
||||||
|
private static function factory(): ComponentRenderer
|
||||||
|
{
|
||||||
|
if (self::$components === null) {
|
||||||
|
self::$components = new ComponentRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$components;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue