From 09057aff446c7d71fb0d79f87d594b3feca1f0d5 Mon Sep 17 00:00:00 2001 From: Antonio De Marco <antonio@nuvole.org> Date: Sun, 9 Apr 2017 14:56:22 +0200 Subject: [PATCH] Initial work on #91 --- .../UiPatterns/Pattern/LibraryPattern.php | 124 +++++++++++- src/UiPatternBase.php | 96 +++++++++- src/UiPatternInterface.php | 21 ++ src/UiPatternsManager.php | 181 ++---------------- src/UiPatternsManagerInterface.php | 27 +-- tests/features/overview.feature | 1 - tests/src/Kernel/UiPatternBaseTest.php | 39 +++- tests/src/Unit/UiPatternsManagerTest.php | 35 ---- ui_patterns.module | 30 ++- ui_patterns.services.yml | 2 +- 10 files changed, 318 insertions(+), 238 deletions(-) delete mode 100644 tests/src/Unit/UiPatternsManagerTest.php diff --git a/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php b/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php index 87aff7204..90bd12369 100644 --- a/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php +++ b/modules/ui_patterns_library/src/Plugin/UiPatterns/Pattern/LibraryPattern.php @@ -2,8 +2,11 @@ namespace Drupal\ui_patterns_library\Plugin\UiPatterns\Pattern; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Extension\ThemeHandlerInterface; +use Drupal\Core\TypedData\TypedDataManager; use Drupal\ui_patterns\UiPatternBase; -use Drupal\ui_patterns\UiPatternInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * The UI Pattern plugin. @@ -17,6 +20,123 @@ use Drupal\ui_patterns\UiPatternInterface; * deriver = "\Drupal\ui_patterns_library\Plugin\Deriver\LibraryDeriver" * ) */ -class LibraryPattern extends UiPatternBase implements UiPatternInterface { +class LibraryPattern extends UiPatternBase { + + /** + * The theme handler. + * + * @var \Drupal\Core\Extension\ThemeHandlerInterface + */ + protected $themeHandler; + + /** + * The theme handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * UiPatternsManager constructor. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, $root, TypedDataManager $typed_data_manager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $root, $typed_data_manager); + $this->moduleHandler = $module_handler; + $this->themeHandler = $theme_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('app.root'), + $container->get('typed_data_manager'), + $container->get('module_handler'), + $container->get('theme_handler') + ); + } + + /** + * {@inheritdoc} + */ + public function getThemeImplementation() { + $item = []; + $definition = $this->getPluginDefinition(); + + foreach ($definition['fields'] as $field) { + $item['variables'][$field['name']] = NULL; + } + $item['variables']['attributes'] = []; + $item['variables']['context'] = []; + $item['variables']['use'] = ''; + + $item += $this->processUseProperty($definition); + $item += $this->processCustomThemeHookProperty($definition); + $item += $this->processTemplateProperty($definition); + + return [ + $definition['theme hook'] => $item, + ]; + } + + /** + * Process 'custom hook theme' definition property. + * + * @param array $definition + * Pattern definition array. + * + * @return array + * Processed hook definition portion. + */ + protected function processCustomThemeHookProperty(array $definition) { + /** @var \Drupal\Core\Extension\Extension $module */ + $return = []; + if (!$definition['custom theme hook'] && $this->moduleHandler->moduleExists($definition['provider'])) { + $module = $this->moduleHandler->getModule($definition['provider']); + $return['path'] = $module->getPath() . '/templates'; + } + return $return; + } + + /** + * Process 'template' definition property. + * + * @param array $definition + * Pattern definition array. + * + * @return array + * Processed hook definition portion. + */ + protected function processTemplateProperty(array $definition) { + $return = []; + if (isset($definition['template'])) { + $return = ['template' => $definition['template']]; + } + return $return; + } + + /** + * Process 'use' definition property. + * + * @param array $definition + * Pattern definition array. + * + * @return array + * Processed hook definition portion. + */ + protected function processUseProperty(array $definition) { + $return = []; + if (!empty($definition['use'])) { + $return = [ + 'path' => $this->moduleHandler->getModule('ui_patterns')->getPath() . '/templates', + 'template' => 'patterns-use-wrapper', + ]; + } + return $return; + } } diff --git a/src/UiPatternBase.php b/src/UiPatternBase.php index c4e585910..f9dc9a36a 100644 --- a/src/UiPatternBase.php +++ b/src/UiPatternBase.php @@ -3,19 +3,58 @@ namespace Drupal\ui_patterns; use Drupal\Component\Plugin\PluginBase; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\TypedData\TypedDataManager; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Class UiPatternBase. * * @package Drupal\ui_patterns */ -abstract class UiPatternBase extends PluginBase implements UiPatternInterface { +abstract class UiPatternBase extends PluginBase implements UiPatternInterface, ContainerFactoryPluginInterface { /** * Prefix for locally defined libraries. */ const LIBRARY_PREFIX = 'ui_patterns'; + /** + * The app root. + * + * @var string + */ + protected $root; + + /** + * Typed data manager service. + * + * @var \Drupal\Core\TypedData\TypedDataManager + */ + protected $typedDataManager; + + /** + * UiPatternsManager constructor. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, $root, TypedDataManager $typed_data_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->root = $root; + $this->typedDataManager = $typed_data_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('app.root'), + $container->get('typed_data_manager') + ); + } + /** * {@inheritdoc} */ @@ -151,4 +190,59 @@ abstract class UiPatternBase extends PluginBase implements UiPatternInterface { return $value; } + /** + * {@inheritdoc} + */ + public function getLibraryDefinitions() { + // @codingStandardsIgnoreStart + $libraries = []; + $definition = $this->getPluginDefinition(); + + // Get only locally defined libraries. + $items = array_filter($definition['libraries'], function ($library) { + return is_array($library); + }); + + // Attach pattern base path to assets. + if (!empty($definition['base path'])) { + $base_path = str_replace($this->root, '', $definition['base path']); + $this->processLibraries($items, $base_path); + } + + // Produce final libraries array. + $id = $definition['id']; + array_walk($items, function ($value) use (&$libraries, $id) { + $libraries[$id . '.' . key($value)] = reset($value); + }); + + // @codingStandardsIgnoreEnd + return $libraries; + } + + /** + * Process libraries. + * + * @param array $libraries + * Libraries array. + * @param string $base_path + * Pattern base path. + * @param string $parent + * Item parent set in previous recursive iteration, if any. + */ + protected function processLibraries(array &$libraries, $base_path, $parent = '') { + $parents = ['js', 'base', 'layout', 'component', 'state', 'theme']; + $_libraries = $libraries; + foreach ($_libraries as $name => $values) { + $is_asset = in_array($parent, $parents, TRUE); + $is_external = isset($values['type']) && $values['type'] == 'external'; + if ($is_asset && !$is_external) { + $libraries[$base_path . DIRECTORY_SEPARATOR . $name] = $values; + unset($libraries[$name]); + } + elseif (!$is_asset) { + $this->processLibraries($libraries[$name], $base_path, $name); + } + } + } + } diff --git a/src/UiPatternInterface.php b/src/UiPatternInterface.php index 0839b8293..db4ea8c97 100644 --- a/src/UiPatternInterface.php +++ b/src/UiPatternInterface.php @@ -136,4 +136,25 @@ interface UiPatternInterface { */ public function getUse(); + /** + * Build and return Drupal theme implementation for current pattern. + * + * @return array + * Theme implementation. + * + * @see ui_patterns_theme() + */ + public function getThemeImplementation(); + + /** + * Get library definitions for current pattern. + * + * @return array + * List of library definitions. + * + * @see hook_library_info_build() + * @see ui_patterns_library_info_build() + */ + public function getLibraryDefinitions(); + } diff --git a/src/UiPatternsManager.php b/src/UiPatternsManager.php index 40b7168f9..593e9a82b 100644 --- a/src/UiPatternsManager.php +++ b/src/UiPatternsManager.php @@ -7,7 +7,6 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; -use Drupal\Core\TypedData\TypedDataManager; /** * Provides the default ui_patterns manager. @@ -16,13 +15,6 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage use StringTranslationTrait; - /** - * The app root. - * - * @var string - */ - protected $root; - /** * The theme handler. * @@ -30,36 +22,23 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage */ protected $themeHandler; - /** - * Typed data manager service. - * - * @var \Drupal\Core\TypedData\TypedDataManager - */ - protected $typedDataManager; - /** * UiPatternsManager constructor. * * @param \Traversable $namespaces * An object that implements \Traversable which contains the root paths * keyed by the corresponding namespace to look for plugin implementations. - * @param string $root - * Application root directory. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * Module handler service. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * Theme handler service. - * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager - * Typed data manager service. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend * Cache backend service. */ - public function __construct(\Traversable $namespaces, $root, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, TypedDataManager $typed_data_manager, CacheBackendInterface $cache_backend) { + public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache_backend) { parent::__construct('Plugin/UiPatterns/Pattern', $namespaces, $module_handler, 'Drupal\ui_patterns\UiPatternInterface', 'Drupal\ui_patterns\Annotation\UiPattern'); - $this->root = $root; $this->moduleHandler = $module_handler; $this->themeHandler = $theme_handler; - $this->typedDataManager = $typed_data_manager; $this->alterInfo('ui_patterns_info'); $this->setCacheBackend($cache_backend, 'ui_patterns', ['ui_patterns']); } @@ -68,10 +47,22 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage * {@inheritdoc} */ public function getPattern($id) { - // @todo should we cache pattern object instances? + // @todo should we statically cache this? return $this->getFactory()->createInstance($id); } + /** + * {@inheritdoc} + */ + public function getPatterns() { + // @todo should we statically cache this? + $patterns = []; + foreach ($this->getDefinitions() as $definition) { + $patterns[] = $this->getPattern($definition['id']); + } + return $patterns; + } + /** * {@inheritdoc} */ @@ -99,62 +90,6 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage }, $this->getDefinitions()); } - /** - * {@inheritdoc} - */ - public function hookTheme() { - $items = []; - - foreach ($this->getDefinitions() as $definition) { - $item = []; - $hook = $definition['theme hook']; - - foreach ($definition['fields'] as $field) { - $item['variables'][$field['name']] = NULL; - } - $item['variables']['attributes'] = []; - $item['variables']['context'] = []; - $item['variables']['use'] = ''; - - $item += $this->processUseProperty($definition); - $item += $this->processCustomThemeHookProperty($definition); - $item += $this->processTemplateProperty($definition); - $items[$hook] = $item; - } - - return $items; - } - - /** - * {@inheritdoc} - */ - public function hookLibraryInfoBuild() { - // @codingStandardsIgnoreStart - $libraries = []; - foreach ($this->getDefinitions() as $definition) { - - // Get only locally defined libraries. - $items = array_filter($definition['libraries'], function ($library) { - return is_array($library); - }); - - // Attach pattern base path to assets. - if (!empty($definition['base path'])) { - $base_path = str_replace($this->root, '', $definition['base path']); - $this->processLibraries($items, $base_path); - } - - // Produce final libraries array. - $id = $definition['id']; - array_walk($items, function ($value) use (&$libraries, $id) { - $libraries[$id . '.' . key($value)] = reset($value); - }); - } - - // @codingStandardsIgnoreEnd - return $libraries; - } - /** * {@inheritdoc} */ @@ -165,94 +100,6 @@ class UiPatternsManager extends DefaultPluginManager implements UiPatternsManage return !empty($definitions); } - /** - * Process libraries. - * - * @param array $libraries - * Libraries array. - * @param string $base_path - * Pattern base path. - * @param string $parent - * Item parent set in previous recursive iteration, if any. - */ - protected function processLibraries(array &$libraries, $base_path, $parent = '') { - $parents = ['js', 'base', 'layout', 'component', 'state', 'theme']; - $_libraries = $libraries; - foreach ($_libraries as $name => $values) { - $is_asset = in_array($parent, $parents, TRUE); - $is_external = isset($values['type']) && $values['type'] == 'external'; - if ($is_asset && !$is_external) { - $libraries[$base_path . DIRECTORY_SEPARATOR . $name] = $values; - unset($libraries[$name]); - } - elseif (!$is_asset) { - $this->processLibraries($libraries[$name], $base_path, $name); - } - } - } - - /** - * Process 'custom hook theme' definition property. - * - * @param array $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - * - * @see UiPatternsManager::hookTheme() - */ - protected function processCustomThemeHookProperty(array $definition) { - /** @var \Drupal\Core\Extension\Extension $module */ - $return = []; - if (!$definition['custom theme hook'] && $this->moduleHandler->moduleExists($definition['provider'])) { - $module = $this->moduleHandler->getModule($definition['provider']); - $return['path'] = $module->getPath() . '/templates'; - } - return $return; - } - - /** - * Process 'template' definition property. - * - * @param array $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - * - * @see UiPatternsManager::hookTheme() - */ - protected function processTemplateProperty(array $definition) { - $return = []; - if (isset($definition['template'])) { - $return = ['template' => $definition['template']]; - } - return $return; - } - - /** - * Process 'use' definition property. - * - * @param array $definition - * Pattern definition array. - * - * @return array - * Processed hook definition portion. - * - * @see UiPatternsManager::hookTheme() - */ - protected function processUseProperty(array $definition) { - $return = []; - if (!empty($definition['use'])) { - $return = [ - 'path' => $this->moduleHandler->getModule('ui_patterns')->getPath() . '/templates', - 'template' => 'patterns-use-wrapper', - ]; - } - return $return; - } - /** * {@inheritdoc} */ diff --git a/src/UiPatternsManagerInterface.php b/src/UiPatternsManagerInterface.php index c95ab5517..d23235608 100644 --- a/src/UiPatternsManagerInterface.php +++ b/src/UiPatternsManagerInterface.php @@ -21,33 +21,20 @@ interface UiPatternsManagerInterface extends PluginManagerInterface { public function getPattern($id); /** - * Return list of available patterns to be used as select options. - * - * @return array - * List of available patterns. - */ - public function getPatternsOptions(); - - /** - * Build and return pattern theme definitions. - * - * @return array - * Theme definitions. + * Get a fully instantiated list of pattern objects. * - * @see ui_patterns_theme() + * @return \Drupal\ui_patterns\UiPatternInterface[] + * List of pattern object instances. */ - public function hookTheme(); + public function getPatterns(); /** - * Get patterns library info. + * Return list of available patterns to be used as select options. * * @return array - * Array of libraries as expected by hook_library_info_build(). - * - * @see hook_library_info_build() - * @see ui_patterns_library_info_build() + * List of available patterns. */ - public function hookLibraryInfoBuild(); + public function getPatternsOptions(); /** * Check whereas the given theme hook is an actual pattern hook. diff --git a/tests/features/overview.feature b/tests/features/overview.feature index 57413d4a6..d3f803128 100644 --- a/tests/features/overview.feature +++ b/tests/features/overview.feature @@ -65,7 +65,6 @@ Feature: Overview Given I am logged in as a user with the "access patterns page" permission And I am on "/patterns/media" - Then the response should contain "/ui_patterns_test_theme/templates/patterns/media/css/media1.css" And the response should contain "/ui_patterns_test_theme/templates/patterns/media/css/media2.css" And the response should contain "/ui_patterns_test_theme/templates/patterns/media/js/media1.js" diff --git a/tests/src/Kernel/UiPatternBaseTest.php b/tests/src/Kernel/UiPatternBaseTest.php index 945aa009b..8091b3d2d 100644 --- a/tests/src/Kernel/UiPatternBaseTest.php +++ b/tests/src/Kernel/UiPatternBaseTest.php @@ -5,6 +5,7 @@ namespace Drupal\Tests\ui_patterns\Kernel; use function bovigo\assert\assert; use function bovigo\assert\predicate\isTrue; use function bovigo\assert\predicate\equals; +use Drupal\Component\Serialization\Yaml; use Drupal\ui_patterns\UiPatternBase; /** @@ -34,8 +35,7 @@ class UiPatternBaseTest extends AbstractUiPatternsTest { foreach ($definitions as $definition) { /** @var \Drupal\ui_patterns\UiPatternBase $pattern */ - $arguments = [[], 'plugin_id', $definition]; - $pattern = $this->getMockForAbstractClass(UiPatternBase::class, $arguments); + $pattern = $this->getUiPatternBaseMock($definition); assert($pattern->getId(), equals($definition['id'])); assert($pattern->getLabel(), equals($definition['label'])); @@ -55,4 +55,39 @@ class UiPatternBaseTest extends AbstractUiPatternsTest { } } + /** + * Test hookLibraryInfoBuild. + * + * @covers ::hookLibraryInfoBuild + */ + public function testHookLibraryInfoBuild() { + $items = Yaml::decode(file_get_contents(dirname(__FILE__) . '/../fixtures/libraries.yml')); + + foreach ($items as $item) { + $pattern = $this->getUiPatternBaseMock($item['actual']); + + /** @var \Drupal\ui_patterns\UiPatternBase $pattern */ + $libraries = $pattern->getLibraryDefinitions(); + assert($libraries, equals($item['expected'])); + } + } + + /** + * Get UiPatternBase mock. + * + * @param array $configuration + * Plugin configuration. + * @param array $methods + * List of methods to mock. + * + * @return \PHPUnit_Framework_MockObject_MockObject + * Mock object. + */ + protected function getUiPatternBaseMock(array $configuration = [], array $methods = []) { + $root = \Drupal::service('app.root'); + $typed_data_manager = \Drupal::service('typed_data_manager'); + $arguments = [[], 'plugin_id', $configuration, $root, $typed_data_manager]; + return $this->getMockForAbstractClass(UiPatternBase::class, $arguments, '', TRUE, TRUE, TRUE, $methods); + } + } diff --git a/tests/src/Unit/UiPatternsManagerTest.php b/tests/src/Unit/UiPatternsManagerTest.php deleted file mode 100644 index 086569360..000000000 --- a/tests/src/Unit/UiPatternsManagerTest.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -namespace Drupal\Tests\ui_patterns\Unit; - -use function bovigo\assert\assert; -use function bovigo\assert\predicate\equals; -use Drupal\Component\Serialization\Yaml; -use Drupal\ui_patterns\UiPatternsManager; - -/** - * @coversDefaultClass \Drupal\ui_patterns\UiPatternsManager - * - * @group ui_patterns - */ -class UiPatternsManagerTest extends AbstractUiPatternsTest { - - /** - * Test hookLibraryInfoBuild. - * - * @covers ::hookLibraryInfoBuild - */ - public function testHookLibraryInfoBuild() { - $items = Yaml::decode(file_get_contents(dirname(__FILE__) . '/../fixtures/libraries.yml')); - - foreach ($items as $item) { - $manager = $this->createPartialMock(UiPatternsManager::class, ['getDefinitions']); - $manager->method('getDefinitions')->willReturn([$item['actual']]); - - /** @var \Drupal\ui_patterns\UiPatternsManager $manager */ - $libraries = $manager->hookLibraryInfoBuild(); - assert($libraries, equals($item['expected'])); - } - } - -} diff --git a/ui_patterns.module b/ui_patterns.module index 7c04f1ae0..98e88e727 100644 --- a/ui_patterns.module +++ b/ui_patterns.module @@ -11,14 +11,33 @@ use Drupal\ui_patterns\UiPatterns; * Implements hook_theme(). */ function ui_patterns_theme() { - return [ + /** @var \Drupal\ui_patterns\UiPatternInterface $pattern */ + $items = [ 'patterns_destination' => [ 'variables' => ['sources' => NULL, 'context' => NULL], ], 'patterns_use_wrapper' => [ 'variables' => ['use' => NULL], ], - ] + UiPatterns::getManager()->hookTheme(); + ]; + + foreach (UiPatterns::getManager()->getPatterns() as $pattern) { + $items += $pattern->getThemeImplementation(); + } + return $items; +} + +/** + * Implements hook_library_info_build() + */ +function ui_patterns_library_info_build() { + /** @var \Drupal\ui_patterns\UiPatternInterface $pattern */ + + $definitions = []; + foreach (UiPatterns::getManager()->getPatterns() as $pattern) { + $definitions += $pattern->getLibraryDefinitions(); + } + return $definitions; } /** @@ -34,10 +53,3 @@ function ui_patterns_theme_suggestions_alter(array &$suggestions, array $variabl \Drupal::moduleHandler()->alter('ui_patterns_destination_suggestions', $suggestions, $variables, $variables['context']); } } - -/** - * Implements hook_library_info_build() - */ -function ui_patterns_library_info_build() { - return UiPatterns::getManager()->hookLibraryInfoBuild(); -} diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml index 1b73bbde4..eee75f4cd 100644 --- a/ui_patterns.services.yml +++ b/ui_patterns.services.yml @@ -1,7 +1,7 @@ services: plugin.manager.ui_patterns: class: Drupal\ui_patterns\UiPatternsManager - arguments: ['@container.namespaces', '@app.root', '@module_handler', '@theme_handler', '@typed_data_manager', '@cache.discovery'] + arguments: ['@container.namespaces', '@module_handler', '@theme_handler', '@cache.discovery'] plugin.manager.ui_patterns_source: class: Drupal\ui_patterns\Plugin\UiPatternsSourceManager parent: default_plugin_manager -- GitLab