Loading cl_components.services.yml +1 −3 Original line number Diff line number Diff line Loading @@ -3,12 +3,10 @@ services: Drupal\cl_components\Twig\TwigComponentLoader: arguments: - '@plugin.manager.cl_component' - '@module_handler' - '@theme_handler' - '@renderer' - '%twig.config%' tags: - { name: twig.loader, priority: 250 } - { name: twig.loader, priority: 5 } Drupal\cl_components\Twig\TwigExtension: arguments: Loading src/Component/ComponentMetadata.php +1 −1 Original line number Diff line number Diff line Loading @@ -212,7 +212,7 @@ final class ComponentMetadata { * Parse the schema information. * * @param array $metadata_info * The metadata information as decoded from "metadata.json". * The metadata information as decoded from the component definition file. * * @throws \Drupal\cl_components\Exception\InvalidComponentException */ Loading src/Twig/TwigComponentLoader.php +92 −101 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\cl_components\Twig; use Drupal\cl_components\ComponentPluginManager; use Drupal\cl_components\Exception\ComponentNotFoundException; use Drupal\cl_components\Exception\TemplateNotFoundException; use Drupal\Component\Discovery\YamlDirectoryDiscovery; use Drupal\Component\Serialization\Json; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; Loading @@ -12,12 +13,13 @@ use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Template\Loader\FilesystemLoader; use Twig\Error\LoaderError; use Twig\Loader\LoaderInterface; use Twig\Source; /** * Lets you load templates using the component ID. */ class TwigComponentLoader extends FilesystemLoader { class TwigComponentLoader implements LoaderInterface { /** * The plugin manager. Loading Loading @@ -52,25 +54,16 @@ class TwigComponentLoader extends FilesystemLoader { * * @param \Drupal\cl_components\ComponentPluginManager $plugin_manager * The plugin manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. * @param array $twig_config * @param array|null $twig_config * The twig configuration. */ public function __construct(ComponentPluginManager $plugin_manager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, RendererInterface $renderer, ?array $twig_config) { public function __construct(ComponentPluginManager $plugin_manager, RendererInterface $renderer, ?array $twig_config) { $this->pluginManager = $plugin_manager; $twig_config = $twig_config ?: []; $this->twigDebug = (bool) ($twig_config['debug'] ?? FALSE); $this->renderer = $renderer; parent::__construct( '.', $module_handler, $theme_handler ); } /** Loading @@ -80,14 +73,10 @@ class TwigComponentLoader extends FilesystemLoader { * Thrown if a template matching $name cannot be found. */ protected function findTemplate($name, $throw = TRUE) { [$id, $variant] = $this->parseIdAndVariant($name); $path = $name; try { [$id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($id); // Validate if the variant is valid. if ($variant && !in_array($variant, $component->getVariants(), TRUE)) { $message = sprintf('Invalid variant "%s" for component "%s".', $variant, $id); throw new LoaderError($message); } $template = $component->getTemplateName($variant ?? ''); $path = sprintf( '%s%s%s', Loading @@ -97,8 +86,10 @@ class TwigComponentLoader extends FilesystemLoader { ); } catch (ComponentNotFoundException | TemplateNotFoundException $e) { if ($throw) { throw new LoaderError($e->getMessage(), $e->getCode(), $e); } } if ($path || !$throw) { return $path; } Loading @@ -110,16 +101,6 @@ class TwigComponentLoader extends FilesystemLoader { * {@inheritdoc} */ public function exists($name): bool { // Check the filesystem for the template. try { if (!parent::exists($name)) { return FALSE; } } catch (LoaderError $e) { return FALSE; } try { [$id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($id); Loading @@ -133,17 +114,30 @@ class TwigComponentLoader extends FilesystemLoader { /** * {@inheritdoc} * * @throws \Drupal\cl_components\Exception\ComponentNotFoundException * @throws \Exception * @throws \Twig\Error\LoaderError */ public function getSourceContext($name): Source { try { [$component_id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($component_id); $source = parent::getSourceContext($name); $path = $component->getMetadata()->getPath() . DIRECTORY_SEPARATOR . $component->getTemplateName($variant); } catch (ComponentNotFoundException | TemplateNotFoundException $e) { return new Source('', $name, ''); } $code = file_get_contents($path); $lib_build = ['#attached' => ['library' => [$component->getLibraryName()]]]; try { // This is the main reason for the loader (other than finding the Twig // template). This will eventually add the auto-generated library into the // page. $this->renderer->render($lib_build); } catch (\Exception $e) { // Off-band library render failed. } $code = "{{ cl_components_additional_context('" . addcslashes($component_id, "'") . "', '" . addcslashes($variant ?: '', "'") . "', " . (int) ($this->twigDebug && $component->isDebugMode()) . ") }}" . PHP_EOL . $source->getCode(); . PHP_EOL . $code; if ($this->twigDebug) { $code = "{# start cl_component $name #}" . PHP_EOL . "<!-- start cl_component $name -->" . PHP_EOL Loading @@ -153,87 +147,85 @@ class TwigComponentLoader extends FilesystemLoader { } if ($component->isDebugMode()) { $debug_lib_build = ['#attached' => ['library' => ['cl_components/cl_debug']]]; try { $this->renderer->render($debug_lib_build); } return new Source($code, $source->getName(), $source->getPath()); catch (\Exception $e) { // Off-band library render failed. } } return new Source($code, $name, $path); } /** * {@inheritdoc} */ public function getCacheKey($name): string { return 'cl-component::' . parent::getCacheKey($name); try { [$component_id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($component_id); } catch (ComponentNotFoundException | TemplateNotFoundException $e) { throw new LoaderError('Unable to find component'); } return sprintf( 'cl-component--%s--%s--%s', $name, $variant, $component->getPluginDefinition()['provider'] ?? '' ); } /** * Parse ID and variant from the template key. * * @param string $name * The template name as provided in the include/embed. * * @return array [string, string] * The component ID and variant. * * @throws \Twig\Error\LoaderError * {@inheritdoc} */ public function parseIdAndVariant(string $name): array { if (isset($this->idVariantCache[$name])) { return $this->idVariantCache[$name]; public function isFresh($name, $time) { $file_is_fresh = static fn (string $path) => filemtime($path) < $time; try { [$component_id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($component_id); } $id_and_variant = $this->parseIdAndVariantByIdSyntax($name); if (!empty($id_and_variant)) { return $id_and_variant; catch (ComponentNotFoundException | TemplateNotFoundException $e) { throw new LoaderError('Unable to find component'); } return $this->parseIdAndVariantByPath($name); // If any of the templates, or the component definition, are fresh. Then the // component is fresh. $metadata_path = $component->getPluginDefinition()[YamlDirectoryDiscovery::FILE_KEY]; if ($file_is_fresh($metadata_path)) { return TRUE; } return array_reduce( array_map( static fn (string $name) => $component->getMetadata()->getPath() . DIRECTORY_SEPARATOR . $name, $component->getTemplates() ), static fn (bool $fresh, string $path) => $fresh || $file_is_fresh($path), FALSE ); } /** * Parses the ID and variant from the component path. * Parse ID and variant from the template key. * * @param string $name * The template name. * The template name as provided in the include/embed. * * @return array [string, string] * The component ID and variant. * * @throws \Twig\Error\LoaderError * @throws \Drupal\cl_components\Exception\ComponentNotFoundException */ private function parseIdAndVariantByPath(string $name): array { $path = parent::findTemplate($name); // Check if there is a metadata.json in the same directory. $dir = dirname($path); $metadata_path = sprintf( '%s%smetadata.json', rtrim($dir, '/'), DIRECTORY_SEPARATOR ); $error_message = sprintf('Unable to find a valid metadata.json for the component with template: "%s"', $name); if (!file_exists($metadata_path)) { throw new LoaderError($error_message); } $metadata_contents = file_get_contents($metadata_path); $metadata_parsed = Json::decode($metadata_contents ?: '{}'); $id = $metadata_parsed['machineName'] ?? ''; if (empty($id)) { throw new LoaderError($error_message); } // If requested, parse the variant. $variant = preg_replace( // Turn @name/my/path/to/component-id--variant.twig => variant. [ '@\.twig$@', '@^.*--@', ], '', $name ); $variant = preg_match('@.*/[^/]*--.*\.twig@', $name) ? $variant : ''; $available_variants = $metadata_parsed['variants'] ?? []; if (empty($variant) || in_array($variant, $available_variants) === FALSE) { $variant = ''; private function parseIdAndVariant(string $name): array { if (isset($this->idVariantCache[$name])) { return $this->idVariantCache[$name]; } $this->idVariantCache[$name] = [$id, $variant]; return [$id, $variant]; $id_and_variant = $this->parseIdAndVariantByIdSyntax($name); if (empty($id_and_variant)) { $message = sprintf('Unable to find %s by component ID.', $name); throw new LoaderError($message); } return $id_and_variant; } /** Loading @@ -244,6 +236,8 @@ class TwigComponentLoader extends FilesystemLoader { * * @return array|null * The ID and variant, or NULL if not found. * * @throws \Drupal\cl_components\Exception\ComponentNotFoundException */ private function parseIdAndVariantByIdSyntax(string $name): ?array { // First check if we can parse the prefix and variant from the name. Loading @@ -252,18 +246,15 @@ class TwigComponentLoader extends FilesystemLoader { if (str_contains($name, '--')) { [$id, $variant] = explode('--', $id); } if (empty($id)) { return NULL; } try { $component = $this->pluginManager->find($id); } catch (ComponentNotFoundException $e) { return NULL; } $available_variants = $component->getVariants(); if (!empty($variant) && !in_array($variant, $available_variants, TRUE)) { return NULL; $message = sprintf( 'Variant "%s" not found in component "%s".', $variant, $component->getMetadata()->getName() ); throw new ComponentNotFoundException($message); } $this->idVariantCache[$name] = [$id, $variant]; return [$id, $variant]; Loading Loading
cl_components.services.yml +1 −3 Original line number Diff line number Diff line Loading @@ -3,12 +3,10 @@ services: Drupal\cl_components\Twig\TwigComponentLoader: arguments: - '@plugin.manager.cl_component' - '@module_handler' - '@theme_handler' - '@renderer' - '%twig.config%' tags: - { name: twig.loader, priority: 250 } - { name: twig.loader, priority: 5 } Drupal\cl_components\Twig\TwigExtension: arguments: Loading
src/Component/ComponentMetadata.php +1 −1 Original line number Diff line number Diff line Loading @@ -212,7 +212,7 @@ final class ComponentMetadata { * Parse the schema information. * * @param array $metadata_info * The metadata information as decoded from "metadata.json". * The metadata information as decoded from the component definition file. * * @throws \Drupal\cl_components\Exception\InvalidComponentException */ Loading
src/Twig/TwigComponentLoader.php +92 −101 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\cl_components\Twig; use Drupal\cl_components\ComponentPluginManager; use Drupal\cl_components\Exception\ComponentNotFoundException; use Drupal\cl_components\Exception\TemplateNotFoundException; use Drupal\Component\Discovery\YamlDirectoryDiscovery; use Drupal\Component\Serialization\Json; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; Loading @@ -12,12 +13,13 @@ use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Template\Loader\FilesystemLoader; use Twig\Error\LoaderError; use Twig\Loader\LoaderInterface; use Twig\Source; /** * Lets you load templates using the component ID. */ class TwigComponentLoader extends FilesystemLoader { class TwigComponentLoader implements LoaderInterface { /** * The plugin manager. Loading Loading @@ -52,25 +54,16 @@ class TwigComponentLoader extends FilesystemLoader { * * @param \Drupal\cl_components\ComponentPluginManager $plugin_manager * The plugin manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. * @param array $twig_config * @param array|null $twig_config * The twig configuration. */ public function __construct(ComponentPluginManager $plugin_manager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, RendererInterface $renderer, ?array $twig_config) { public function __construct(ComponentPluginManager $plugin_manager, RendererInterface $renderer, ?array $twig_config) { $this->pluginManager = $plugin_manager; $twig_config = $twig_config ?: []; $this->twigDebug = (bool) ($twig_config['debug'] ?? FALSE); $this->renderer = $renderer; parent::__construct( '.', $module_handler, $theme_handler ); } /** Loading @@ -80,14 +73,10 @@ class TwigComponentLoader extends FilesystemLoader { * Thrown if a template matching $name cannot be found. */ protected function findTemplate($name, $throw = TRUE) { [$id, $variant] = $this->parseIdAndVariant($name); $path = $name; try { [$id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($id); // Validate if the variant is valid. if ($variant && !in_array($variant, $component->getVariants(), TRUE)) { $message = sprintf('Invalid variant "%s" for component "%s".', $variant, $id); throw new LoaderError($message); } $template = $component->getTemplateName($variant ?? ''); $path = sprintf( '%s%s%s', Loading @@ -97,8 +86,10 @@ class TwigComponentLoader extends FilesystemLoader { ); } catch (ComponentNotFoundException | TemplateNotFoundException $e) { if ($throw) { throw new LoaderError($e->getMessage(), $e->getCode(), $e); } } if ($path || !$throw) { return $path; } Loading @@ -110,16 +101,6 @@ class TwigComponentLoader extends FilesystemLoader { * {@inheritdoc} */ public function exists($name): bool { // Check the filesystem for the template. try { if (!parent::exists($name)) { return FALSE; } } catch (LoaderError $e) { return FALSE; } try { [$id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($id); Loading @@ -133,17 +114,30 @@ class TwigComponentLoader extends FilesystemLoader { /** * {@inheritdoc} * * @throws \Drupal\cl_components\Exception\ComponentNotFoundException * @throws \Exception * @throws \Twig\Error\LoaderError */ public function getSourceContext($name): Source { try { [$component_id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($component_id); $source = parent::getSourceContext($name); $path = $component->getMetadata()->getPath() . DIRECTORY_SEPARATOR . $component->getTemplateName($variant); } catch (ComponentNotFoundException | TemplateNotFoundException $e) { return new Source('', $name, ''); } $code = file_get_contents($path); $lib_build = ['#attached' => ['library' => [$component->getLibraryName()]]]; try { // This is the main reason for the loader (other than finding the Twig // template). This will eventually add the auto-generated library into the // page. $this->renderer->render($lib_build); } catch (\Exception $e) { // Off-band library render failed. } $code = "{{ cl_components_additional_context('" . addcslashes($component_id, "'") . "', '" . addcslashes($variant ?: '', "'") . "', " . (int) ($this->twigDebug && $component->isDebugMode()) . ") }}" . PHP_EOL . $source->getCode(); . PHP_EOL . $code; if ($this->twigDebug) { $code = "{# start cl_component $name #}" . PHP_EOL . "<!-- start cl_component $name -->" . PHP_EOL Loading @@ -153,87 +147,85 @@ class TwigComponentLoader extends FilesystemLoader { } if ($component->isDebugMode()) { $debug_lib_build = ['#attached' => ['library' => ['cl_components/cl_debug']]]; try { $this->renderer->render($debug_lib_build); } return new Source($code, $source->getName(), $source->getPath()); catch (\Exception $e) { // Off-band library render failed. } } return new Source($code, $name, $path); } /** * {@inheritdoc} */ public function getCacheKey($name): string { return 'cl-component::' . parent::getCacheKey($name); try { [$component_id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($component_id); } catch (ComponentNotFoundException | TemplateNotFoundException $e) { throw new LoaderError('Unable to find component'); } return sprintf( 'cl-component--%s--%s--%s', $name, $variant, $component->getPluginDefinition()['provider'] ?? '' ); } /** * Parse ID and variant from the template key. * * @param string $name * The template name as provided in the include/embed. * * @return array [string, string] * The component ID and variant. * * @throws \Twig\Error\LoaderError * {@inheritdoc} */ public function parseIdAndVariant(string $name): array { if (isset($this->idVariantCache[$name])) { return $this->idVariantCache[$name]; public function isFresh($name, $time) { $file_is_fresh = static fn (string $path) => filemtime($path) < $time; try { [$component_id, $variant] = $this->parseIdAndVariant($name); $component = $this->pluginManager->find($component_id); } $id_and_variant = $this->parseIdAndVariantByIdSyntax($name); if (!empty($id_and_variant)) { return $id_and_variant; catch (ComponentNotFoundException | TemplateNotFoundException $e) { throw new LoaderError('Unable to find component'); } return $this->parseIdAndVariantByPath($name); // If any of the templates, or the component definition, are fresh. Then the // component is fresh. $metadata_path = $component->getPluginDefinition()[YamlDirectoryDiscovery::FILE_KEY]; if ($file_is_fresh($metadata_path)) { return TRUE; } return array_reduce( array_map( static fn (string $name) => $component->getMetadata()->getPath() . DIRECTORY_SEPARATOR . $name, $component->getTemplates() ), static fn (bool $fresh, string $path) => $fresh || $file_is_fresh($path), FALSE ); } /** * Parses the ID and variant from the component path. * Parse ID and variant from the template key. * * @param string $name * The template name. * The template name as provided in the include/embed. * * @return array [string, string] * The component ID and variant. * * @throws \Twig\Error\LoaderError * @throws \Drupal\cl_components\Exception\ComponentNotFoundException */ private function parseIdAndVariantByPath(string $name): array { $path = parent::findTemplate($name); // Check if there is a metadata.json in the same directory. $dir = dirname($path); $metadata_path = sprintf( '%s%smetadata.json', rtrim($dir, '/'), DIRECTORY_SEPARATOR ); $error_message = sprintf('Unable to find a valid metadata.json for the component with template: "%s"', $name); if (!file_exists($metadata_path)) { throw new LoaderError($error_message); } $metadata_contents = file_get_contents($metadata_path); $metadata_parsed = Json::decode($metadata_contents ?: '{}'); $id = $metadata_parsed['machineName'] ?? ''; if (empty($id)) { throw new LoaderError($error_message); } // If requested, parse the variant. $variant = preg_replace( // Turn @name/my/path/to/component-id--variant.twig => variant. [ '@\.twig$@', '@^.*--@', ], '', $name ); $variant = preg_match('@.*/[^/]*--.*\.twig@', $name) ? $variant : ''; $available_variants = $metadata_parsed['variants'] ?? []; if (empty($variant) || in_array($variant, $available_variants) === FALSE) { $variant = ''; private function parseIdAndVariant(string $name): array { if (isset($this->idVariantCache[$name])) { return $this->idVariantCache[$name]; } $this->idVariantCache[$name] = [$id, $variant]; return [$id, $variant]; $id_and_variant = $this->parseIdAndVariantByIdSyntax($name); if (empty($id_and_variant)) { $message = sprintf('Unable to find %s by component ID.', $name); throw new LoaderError($message); } return $id_and_variant; } /** Loading @@ -244,6 +236,8 @@ class TwigComponentLoader extends FilesystemLoader { * * @return array|null * The ID and variant, or NULL if not found. * * @throws \Drupal\cl_components\Exception\ComponentNotFoundException */ private function parseIdAndVariantByIdSyntax(string $name): ?array { // First check if we can parse the prefix and variant from the name. Loading @@ -252,18 +246,15 @@ class TwigComponentLoader extends FilesystemLoader { if (str_contains($name, '--')) { [$id, $variant] = explode('--', $id); } if (empty($id)) { return NULL; } try { $component = $this->pluginManager->find($id); } catch (ComponentNotFoundException $e) { return NULL; } $available_variants = $component->getVariants(); if (!empty($variant) && !in_array($variant, $available_variants, TRUE)) { return NULL; $message = sprintf( 'Variant "%s" not found in component "%s".', $variant, $component->getMetadata()->getName() ); throw new ComponentNotFoundException($message); } $this->idVariantCache[$name] = [$id, $variant]; return [$id, $variant]; Loading