From a3ebd6c9a4e919b8bed1cecb8d5aff3a0bf233eb Mon Sep 17 00:00:00 2001 From: Yassine Doghri <yassine@doghri.fr> Date: Fri, 14 Oct 2022 15:29:01 +0000 Subject: [PATCH] refactor(view-components): use CI4's View Decorators to render components --- app/Common.php | 2 +- app/Config/Services.php | 21 --- app/Config/View.php | 3 +- app/Libraries/View.php | 142 ------------------ .../ViewComponents/ComponentRenderer.php | 17 +-- app/Libraries/ViewComponents/Decorator.php | 38 +++++ 6 files changed, 42 insertions(+), 181 deletions(-) delete mode 100644 app/Libraries/View.php create mode 100644 app/Libraries/ViewComponents/Decorator.php diff --git a/app/Common.php b/app/Common.php index 6ae40b6565..1f3b17c2ab 100644 --- a/app/Common.php +++ b/app/Common.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use App\Libraries\View; use Config\Services; +use Config\View; use ViewThemes\Theme; /** diff --git a/app/Config/Services.php b/app/Config/Services.php index 88da0c416a..3fa2e09245 100644 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -7,13 +7,10 @@ namespace Config; use App\Libraries\Breadcrumb; use App\Libraries\Negotiate; use App\Libraries\Router; -use App\Libraries\View; use CodeIgniter\Config\BaseService; use CodeIgniter\HTTP\Request; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\Router\RouteCollectionInterface; -use Config\Services as AppServices; -use Config\View as ViewConfig; /** * Services Configuration file. @@ -48,24 +45,6 @@ class Services extends BaseService 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 * language, encoding, charset, and more. diff --git a/app/Config/View.php b/app/Config/View.php index 212f28c917..104cdd8d86 100644 --- a/app/Config/View.php +++ b/app/Config/View.php @@ -6,6 +6,7 @@ namespace Config; use CodeIgniter\Config\View as BaseView; use CodeIgniter\View\ViewDecoratorInterface; +use ViewComponents\Decorator; class View extends BaseView { @@ -46,5 +47,5 @@ class View extends BaseView * * @var class-string<ViewDecoratorInterface>[] */ - public array $decorators = []; + public array $decorators = [Decorator::class]; } diff --git a/app/Libraries/View.php b/app/Libraries/View.php deleted file mode 100644 index 1fbb09ecec..0000000000 --- a/app/Libraries/View.php +++ /dev/null @@ -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; - } -} diff --git a/app/Libraries/ViewComponents/ComponentRenderer.php b/app/Libraries/ViewComponents/ComponentRenderer.php index 70a42a006e..097126154f 100644 --- a/app/Libraries/ViewComponents/ComponentRenderer.php +++ b/app/Libraries/ViewComponents/ComponentRenderer.php @@ -14,23 +14,11 @@ class ComponentRenderer { protected ViewComponents $config; - /** - * File name of the view source - */ - protected string $currentView; - public function __construct() { $this->config = config('ViewComponents'); } - public function setCurrentView(string $view): self - { - $this->currentView = $view; - - return $this; - } - public function render(string $output): string { // 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? $pathsToDiscover = []; $lookupPaths = $this->config->lookupPaths; - $pathsToDiscover = array_filter($lookupPaths, function ($path): bool { - return str_starts_with($this->currentView, $path); - }); - $pathsToDiscover = array_values($pathsToDiscover); + $pathsToDiscover = array_values($lookupPaths); $pathsToDiscover[] = $this->config->defaultLookupPath; $namePath = str_replace('.', '/', $name); diff --git a/app/Libraries/ViewComponents/Decorator.php b/app/Libraries/ViewComponents/Decorator.php new file mode 100644 index 0000000000..9ca40085f9 --- /dev/null +++ b/app/Libraries/ViewComponents/Decorator.php @@ -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; + } +} -- GitLab