diff --git a/libraries.api.php b/libraries.api.php index 6281a16b4bec68bcb59cf996b5eab0d250445929..2c832ebe80cf865a2ff9f3b9f0cfa5d904da0b5b 100644 --- a/libraries.api.php +++ b/libraries.api.php @@ -14,11 +14,15 @@ * Drupal request-response process in a generic way. * * @section sec_definitions Library definitions - * In order to do be useful to other modules Libraries API needs a list of - * known libraries and metadata about each of the libraries. Because multiple - * modules or themes may integrate with the same external library a key - * objective of Libraries API is to keep this information separate from any one - * module or theme. + * In order to be useful to other modules Libraries API needs a list of known + * libraries and metadata about each of the libraries. Because multiple modules + * themes may integrate with the same external library a key objective of + * Libraries API is to keep this information separate from any one module or + * theme. + * + * Definitions are accessed via a discovery that is responsible for checking + * whether a given definition exists and fetching it, if it is. See + * LibraryRegistryInterface and StreamDefinitionDiscovery for more information. * * @subsection sub_definitions_machine_name * A central part of a library's metadata is the library's machine name or ID. @@ -35,37 +39,11 @@ * race conditions between modules providing information for the same library. * Thus, in Drupal 8 there is no longer a hook making it necessary to properly * solve the problem of centrally maintaining and distributing library info - * files. This has yet to be done. - * - * @subsection sub_definitions_stream_wrapper - * In anticipation of a central repository of library information that will - * distribute library definitions as separate files Libraries API provides a - * 'library-definitions' stream wrapper. Due to that a library's definition can - * be accessed given only it's machine name, for example at - * 'library-definitions://example.yml' for a library with the machine name - * 'example'. YAML is chosen as the file format because it is used in many parts - * of Drupal 8 already. Using a stream wrapper has the benefit of being able to - * swap out the specific storage implementation without any other part of the - * code needing to change. For example, the specific directory which holds the - * library definitions on disk can be changed, multiple directories can be - * layered as though they were one, or the library definitions can even be - * read from a remote location without any part of the code other than the - * stream wrapper implementation itself needing to change. - * - * By default the library definitions stream wrapper reads from a single - * directory that is configurable and points to the 'library-definitions' - * directory within the public files directory by default. This makes library - * definitions writable by the webserver by default, which is in anticipation of - * a user interface that fetches definitions from a remote repository and stores - * them locally. For improved security the library definitions can be managed - * manually (or put under version control) and placed in a directory that is not - * writable by the webserver. The idea of using a stream wrapper for this as - * well as the default location is taken from the 'translations' stream wrapper - * provided by the Interface Translation module. + * files. This has yet to be done. See https://www.drupal.org/node/773508 for + * more information. * - * @see \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface - * @see \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistry - * @see \Drupal\libraries\StreamWrapper\LibraryDefinitionsStream + * @see \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface + * @see \Drupal\libraries\ExternalLibrary\Definition\StreamDefinitionDiscovery * * @} */ diff --git a/libraries.module b/libraries.module index 34e2913c89a54bccbe389344746c27475cbe1a16..77d3f455292e6c667599aad41b1ec4d0871b91b2 100644 --- a/libraries.module +++ b/libraries.module @@ -17,8 +17,9 @@ function libraries_library_info_build() { $library_manager = \Drupal::service('libraries.manager'); $core_libraries = []; - foreach ($library_manager->getRequiredLibraries() as $external_library) { - // @todo Consider using a library type listener instead. + foreach ($library_manager->getRequiredLibraryIds() as $external_library_id) { + $external_library = $library_manager->getLibrary($external_library_id); + // @todo Use a library type listener instead. if ($external_library instanceof AssetLibraryInterface) { $core_libraries += $external_library->getAttachableAssetLibraries(); } diff --git a/libraries.services.yml b/libraries.services.yml index 1ee8a6fd043d7366c9ff4e14a6239ae070ad3285..18d36cabf84bd236a5cd8b7b66a0e33a6b5c35e5 100644 --- a/libraries.services.yml +++ b/libraries.services.yml @@ -2,15 +2,15 @@ services: libraries.manager: class: Drupal\libraries\ExternalLibrary\LibraryManager arguments: - - '@libraries.registry' + - '@libraries.definitions.discovery' + - '@plugin.manager.libraries.library_type' - '@libraries.extension_handler' - - '@libraries.php_file_loader' - libraries.registry: - class: Drupal\libraries\ExternalLibrary\Registry\LibraryRegistry - arguments: ['@serialization.yaml', '@plugin.manager.libraries.library_type'] + libraries.definitions.discovery: + class: Drupal\libraries\ExternalLibrary\Definition\StreamDefinitionDiscovery + arguments: ['@serialization.yaml'] plugin.manager.libraries.library_type: - class: Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeManager + class: Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeFactory parent: default_plugin_manager plugin.manager.libraries.locator: class: Drupal\libraries\ExternalLibrary\Local\LocatorManager diff --git a/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php b/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2d7bebdf87d141bd901ea96bbbbf7605cac82aa1 --- /dev/null +++ b/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +/** + * Provides an interface for library definition discoveries. + * + * This is similar to the plugin system's DiscoveryInterface, except that this + * does not require knowing all definitions upfront, so there is no + * getDefinitions() method. + * + * @see \Drupal\Component\Plugin\Discovery\DiscoveryInterface + */ +interface DefinitionDiscoveryInterface { + + /** + * Checks whether a library definition exists. + * + * @param string $id + * The library ID. + * + * @return bool + * TRUE if a library definition with the given ID exists; FALSE otherwise. + */ + public function hasDefinition($id); + + /** + * Gets a library definition by its ID. + * + * @param string $id + * The library ID. + * + * @return array + * The library definition. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * + * @todo Consider returning a classed object instead of an array or at least + * document and validate the array structure. + */ + public function getDefinition($id); + +} diff --git a/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php b/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..328b0429babe0e745437e4056502cac608b27e1d --- /dev/null +++ b/src/ExternalLibrary/Definition/StreamDefinitionDiscovery.php @@ -0,0 +1,96 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +use Drupal\Component\Serialization\SerializationInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; + +/** + * Provides a stream-based implementation of a libraries definition discovery. + * + * Given a library ID of 'example', it reads the library definition from the URI + * 'library-definitions://example.yml'. See LibraryDefinitionsStream for more + * information. + * + * Using a stream wrapper has the benefit of being able to swap out the specific + * storage implementation without any other part of the code needing to change. + * For example, the specific directory which holds the library definitions on + * disk can be changed, multiple directories can be layered as though they were + * one, or the library definitions can even be read from a remote location + * without any part of the code other than the stream wrapper implementation + * itself needing to change. + * + * @see \Drupal\libraries\StreamWrapper\LibraryDefinitionsStream + */ +class StreamDefinitionDiscovery implements DefinitionDiscoveryInterface { + + /** + * The serializer for the library definition files. + * + * @var \Drupal\Component\Serialization\SerializationInterface + */ + protected $serializer; + + /** + * The scheme of the stream to use for library definitions. + * + * @var string + */ + protected $scheme = 'library-definitions'; + + /** + * Constructs a stream-based library definition discovery. + * + * @param \Drupal\Component\Serialization\SerializationInterface $serializer + * The serializer for the library definition files. + */ + public function __construct(SerializationInterface $serializer) { + $this->serializer = $serializer; + } + + /** + * Checks whether a library definition exists for the given ID. + * + * @param string $id + * The library ID to check for. + * + * @return bool + * TRUE if the library definition exists; FALSE otherwise. + */ + public function hasDefinition($id) { + return file_exists($this->getFileUri($id)); + } + + /** + * Returns the library definition for the given ID. + * + * @param string $id + * The library ID to retrieve the definition for. + * + * @return array + * The library definition array parsed from the definition JSON file. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + */ + public function getDefinition($id) { + if (!$this->hasDefinition($id)) { + throw new LibraryDefinitionNotFoundException($id); + } + return $this->serializer->decode(file_get_contents($this->getFileUri($id))); + } + + /** + * Returns the file URI of the library definition file for a given library ID. + * + * @param $id + * The ID of the external library. + * + * @return string + * The file URI of the file the library definition resides in. + */ + protected function getFileUri($id) { + $filename = $id . '.' . $this->serializer->getFileExtension(); + return "$this->scheme://$filename"; + } + +} diff --git a/src/ExternalLibrary/Exception/LibraryClassNotFoundException.php b/src/ExternalLibrary/Exception/LibraryClassNotFoundException.php deleted file mode 100644 index 0e64d8516c15c52446aa5fe8d58d3d20e7c788ec..0000000000000000000000000000000000000000 --- a/src/ExternalLibrary/Exception/LibraryClassNotFoundException.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException. - */ - -namespace Drupal\libraries\ExternalLibrary\Exception; - -use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorTrait; -use Exception; - -/** - * Provides an exception for a library definition without a class declaration.. - */ -class LibraryClassNotFoundException extends \RuntimeException { - - use LibraryIdAccessorTrait; - - /** - * Constructs a library exception. - * - * @param string $library_id - * The library ID. - * @param string $message - * (optional) The exception message. - * @param int $code - * (optional) The error code. - * @param \Exception $previous - * (optional) The previous exception. - */ - public function __construct( - $library_id, - $message = '', - $code = 0, - \Exception $previous = NULL - ) { - $this->libraryId = (string) $library_id; - $message = $message ?: "The library class for the library '{$this->libraryId}' could not be found."; - parent::__construct($message, $code, $previous); - } - -} diff --git a/src/ExternalLibrary/LibraryManager.php b/src/ExternalLibrary/LibraryManager.php index 49a642b73edebc8d5eeb8c8ce30dd2c936242ab3..6cfef1db04c624e14d12a9128bbf722bd95aee09 100644 --- a/src/ExternalLibrary/LibraryManager.php +++ b/src/ExternalLibrary/LibraryManager.php @@ -6,10 +6,12 @@ */ namespace Drupal\libraries\ExternalLibrary; +use Drupal\Component\Plugin\Factory\FactoryInterface; use Drupal\libraries\Extension\ExtensionHandlerInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException; +use Drupal\libraries\ExternalLibrary\LibraryType\LibraryCreationListenerInterface; use Drupal\libraries\ExternalLibrary\LibraryType\LibraryLoadingListenerInterface; -use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface; -use Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface; +use Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface; /** * Provides a manager for external libraries. @@ -17,11 +19,18 @@ use Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface; class LibraryManager implements LibraryManagerInterface { /** - * The library registry. + * The library definition discovery. * - * @var \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface + * @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface */ - protected $registry; + protected $definitionDiscovery; + + /** + * The library type factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $libraryTypeFactory; /** * The extension handler. @@ -33,48 +42,88 @@ class LibraryManager implements LibraryManagerInterface { /** * Constructs an external library manager. * - * @param \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface $registry + * @param \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $definition_disovery * The library registry. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $library_type_factory + * The library type factory. * @param \Drupal\libraries\Extension\ExtensionHandlerInterface $extension_handler * The extension handler. - * @param \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface $php_file_loader - * The PHP file loader. */ public function __construct( - LibraryRegistryInterface $registry, - ExtensionHandlerInterface $extension_handler, - PhpFileLoaderInterface $php_file_loader + DefinitionDiscoveryInterface $definition_disovery, + FactoryInterface $library_type_factory, + ExtensionHandlerInterface $extension_handler ) { - $this->registry = $registry; + $this->definitionDiscovery = $definition_disovery; + $this->libraryTypeFactory = $library_type_factory; $this->extensionHandler = $extension_handler; - $this->phpFileLoader = $php_file_loader; } /** * {@inheritdoc} */ - public function getRequiredLibraries() { - $libraries = []; + public function getLibrary($id) { + $definition = $this->definitionDiscovery->getDefinition($id); + return $this->getLibraryFromDefinition($id, $definition); + } + + /** + * {@inheritdoc} + */ + public function getRequiredLibraryIds() { + $library_ids = []; foreach ($this->extensionHandler->getExtensions() as $extension) { foreach ($extension->getLibraryDependencies() as $library_id) { - // Do not bother instantiating a library multiple times. - if (!isset($libraries[$library_id])) { - $libraries[$library_id] = $this->registry->getLibrary($library_id); - } + $library_ids[] = $library_id; } } - return $libraries; + return array_unique($library_ids); } /** * {@inheritdoc} */ public function load($id) { - $library_type = $this->registry->getLibraryType($id); + $definition = $this->definitionDiscovery->getDefinition($id); + $library_type = $this->getLibraryType($id, $definition); // @todo Throw an exception instead of silently failing. if ($library_type instanceof LibraryLoadingListenerInterface) { - $library_type->onLibraryLoad($this->registry->getLibrary($id)); - } + $library_type->onLibraryLoad($this->getLibraryFromDefinition($id, $definition)); + } + } + + /** + * @param $id + * @param $definition + * @return mixed + */ + protected function getLibraryFromDefinition($id, $definition) { + $library_type = $this->getLibraryType($id, $definition); + + // @todo Make this alter-able. + $class = $library_type->getLibraryClass(); + + // @todo Make sure that the library class implements the correct interface. + $library = $class::create($id, $definition); + + if ($library_type instanceof LibraryCreationListenerInterface) { + $library_type->onLibraryCreate($library); + return $library; + } + return $library; + } + + /** + * @param $id + * @param $definition + * @return object + */ + protected function getLibraryType($id, $definition) { + // @todo Validate that the type is a string. + if (!isset($definition['type'])) { + throw new LibraryTypeNotFoundException($id); + } + return $this->libraryTypeFactory->createInstance($definition['type']); } } diff --git a/src/ExternalLibrary/LibraryManagerInterface.php b/src/ExternalLibrary/LibraryManagerInterface.php index 58e7dc8ad485e3fdb31335c16bce886c96d11a2a..0cd7ef7832a65fac0f288dbac795fd922a615115 100644 --- a/src/ExternalLibrary/LibraryManagerInterface.php +++ b/src/ExternalLibrary/LibraryManagerInterface.php @@ -13,27 +13,42 @@ namespace Drupal\libraries\ExternalLibrary; */ interface LibraryManagerInterface { + /** + * Gets a library by its ID. + * + * @param string $id + * The library ID. + * + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The library object. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function getLibrary($id); + /** * Gets the list of libraries that are required by enabled extensions. * * Modules, themes, and installation profiles can declare library dependencies - * in their info files. + * by specifying a 'library_dependencies' key in their info files. * - * @return \Drupal\libraries\ExternalLibrary\LibraryInterface[]|\Generator - * An array of libraries keyed by their ID. - * - * @todo Expand the documentation. - * @todo Consider returning just library IDs. + * @return string[] + * An array of library IDs. */ - public function getRequiredLibraries(); + public function getRequiredLibraryIds(); /** * Loads library files for a library. * + * Note that not all library types support explicit loading. Asset libraries, + * in particular, are declared to Drupal core's library system and are then + * loaded using that. + * * @param string $id * The ID of the library. * - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException */ diff --git a/src/ExternalLibrary/LibraryType/LibraryTypeManager.php b/src/ExternalLibrary/LibraryType/LibraryTypeFactory.php similarity index 76% rename from src/ExternalLibrary/LibraryType/LibraryTypeManager.php rename to src/ExternalLibrary/LibraryType/LibraryTypeFactory.php index 6ca779cd6244882a8a5770d09ca5b941d47814e4..dff4668b9d31843401e1e04128f000692f45de83 100644 --- a/src/ExternalLibrary/LibraryType/LibraryTypeManager.php +++ b/src/ExternalLibrary/LibraryType/LibraryTypeFactory.php @@ -1,12 +1,8 @@ <?php -/** - * @file - * Contains \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeManager. - */ - namespace Drupal\libraries\ExternalLibrary\LibraryType; +use Drupal\Component\Plugin\Factory\FactoryInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; @@ -15,7 +11,7 @@ use Drupal\libraries\Annotation\LibraryType; /** * Provides a plugin manager for library type plugins. */ -class LibraryTypeManager extends DefaultPluginManager implements LibraryTypeManagerInterface { +class LibraryTypeFactory extends DefaultPluginManager { /** * Constructs a locator manager. @@ -35,13 +31,4 @@ class LibraryTypeManager extends DefaultPluginManager implements LibraryTypeMana $this->setCacheBackend($cache_backend, 'libraries_library_type_info'); } - /** - * {@inheritdoc} - */ - public function getLibraryClass(LibraryTypeInterface $library_type) { - // @todo Make this alter-able. - return $library_type->getLibraryClass(); - } - - } diff --git a/src/ExternalLibrary/LibraryType/LibraryTypeManagerInterface.php b/src/ExternalLibrary/LibraryType/LibraryTypeManagerInterface.php deleted file mode 100644 index d3b68a6acd6b556e5af4b8129142ed529e7a73f1..0000000000000000000000000000000000000000 --- a/src/ExternalLibrary/LibraryType/LibraryTypeManagerInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeManagerInterface. - */ - -namespace Drupal\libraries\ExternalLibrary\LibraryType; - -use Drupal\Component\Plugin\Factory\FactoryInterface; - -/** - * Provides an interface for library type managers. - */ -interface LibraryTypeManagerInterface extends FactoryInterface { - - /** - * Gets the library class to use for a given library type. - * - * @param \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface $library_type - * The library type to return the library class for. - * - * @return string - * The library class. - */ - public function getLibraryClass(LibraryTypeInterface $library_type); - -} - diff --git a/src/ExternalLibrary/Registry/LibraryRegistry.php b/src/ExternalLibrary/Registry/LibraryRegistry.php deleted file mode 100644 index 2555ffd93b05028167e7f9b49714a5ccea99d6f9..0000000000000000000000000000000000000000 --- a/src/ExternalLibrary/Registry/LibraryRegistry.php +++ /dev/null @@ -1,129 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistry. - */ - -namespace Drupal\libraries\ExternalLibrary\Registry; - -use Drupal\Component\Plugin\Factory\FactoryInterface; -use Drupal\Component\Serialization\SerializationInterface; -use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; -use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException; -use Drupal\libraries\ExternalLibrary\LibraryType\LibraryCreationListenerInterface; - -/** - * Provides an implementation of a registry of external libraries. - * - * @todo Consider moving parts of this logic into LibraryManager. - * @todo Allow for JavaScript CDN's, Packagist, etc. to act as library - * registries. - */ -class LibraryRegistry implements LibraryRegistryInterface { - - /** - * The serializer for the library definition files. - * - * @var \Drupal\Component\Serialization\SerializationInterface - */ - protected $serializer; - - /** - * The library type manager. - * - * @var \Drupal\Component\Plugin\Factory\FactoryInterface - */ - protected $libraryTypeFactory; - - /** - * Constructs a registry of external libraries. - * - * @param \Drupal\Component\Serialization\SerializationInterface $serializer - * The serializer for the library definition files. - * @param \Drupal\Component\Plugin\Factory\FactoryInterface $library_type_factory - * The library type manager. - */ - public function __construct( - SerializationInterface $serializer, - FactoryInterface $library_type_factory - ) { - $this->serializer = $serializer; - $this->libraryTypeFactory = $library_type_factory; - } - - /** - * {@inheritdoc} - */ - public function getLibrary($id) { - $library_type = $this->getLibraryType($id); - - $class = $this->libraryTypeFactory->getLibraryClass($library_type); - // @todo Make sure that the library class implements the correct interface. - $library = $class::create($id, $this->getDefinition($id)); - - if ($library_type instanceof LibraryCreationListenerInterface) { - $library_type->onLibraryCreate($library); - } - - return $library; - } - - /** - * Checks whether a library definition exists for the given ID. - * - * @param string $id - * The library ID to check for. - * - * @return bool - * TRUE if the library definition exists; FALSE otherwise. - */ - protected function hasDefinition($id) { - return file_exists($this->getFileUri($id)); - } - - /** - * Returns the library definition for the given ID. - * - * @param string $id - * The library ID to retrieve the definition for. - * - * @return array - * The library definition array parsed from the definition JSON file. - * - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException - */ - protected function getDefinition($id) { - if (!$this->hasDefinition($id)) { - throw new LibraryDefinitionNotFoundException($id); - } - return $this->serializer->decode(file_get_contents($this->getFileUri($id))); - } - - /** - * Returns the file URI of the library definition file for a given library ID. - * - * @param $id - * The ID of the external library. - * - * @return string - * The file URI of the file the library definition resides in. - */ - protected function getFileUri($id) { - $filename = $id . '.' . $this->serializer->getFileExtension(); - return "library-definitions://$filename"; - } - - /** - * {@inheritdoc} - */ - public function getLibraryType($id) { - $definition = $this->getDefinition($id); - // @todo Validate that the type is a string. - if (!isset($definition['type'])) { - throw new LibraryTypeNotFoundException($id); - } - return $this->libraryTypeFactory->createInstance($definition['type']); - } - -} diff --git a/src/ExternalLibrary/Registry/LibraryRegistryInterface.php b/src/ExternalLibrary/Registry/LibraryRegistryInterface.php deleted file mode 100644 index 146c28383a511be8725331b3d1ad2f3b41e15259..0000000000000000000000000000000000000000 --- a/src/ExternalLibrary/Registry/LibraryRegistryInterface.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface. - */ - -namespace Drupal\libraries\ExternalLibrary\Registry; - - -/** - * Provides an interface for library registries. - */ -interface LibraryRegistryInterface { - - /** - * Gets a library by its ID. - * - * @param string $id - * The library ID. - * - * @return \Drupal\libraries\ExternalLibrary\LibraryInterface - * The library. - * - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException - */ - public function getLibrary($id); - - /** - * Returns the library type for a library ID. - * - * Note that the passed ID is not the ID of the library type, but the library - * ID itself. Use the LibraryTypeManager to retrieve a library type given its - * ID. - * - * @param string $id - * The ID of the external library. - * - * @return string|\Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface - * The library type. - * - * @see \Drupal\libraries\ExternalLibrary\LibraryTypeManager - * - * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException - * @throws \Drupal\Component\Plugin\Exception\PluginException - * - * @todo Consider making this protected again, when this is moved to the - * LibraryManager. - */ - public function getLibraryType($id); - -} diff --git a/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php b/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php index 821bf6a3096a0a0b34674a3d028bf9ba25ca8049..0575e9c1386ef3f8c614a30e414db7d0cca06d0a 100644 --- a/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php +++ b/src/Plugin/libraries/LibraryType/PhpFileLibraryType.php @@ -106,10 +106,10 @@ class PhpFileLibraryType implements */ public function onLibraryLoad(LibraryInterface $library) { /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface $library */ + // @todo Prevent loading a library multiple times. foreach ($library->getPhpFiles() as $file) { $this->phpFileLoader->load($file); } - } } diff --git a/src/StreamWrapper/LibraryDefinitionsStream.php b/src/StreamWrapper/LibraryDefinitionsStream.php index 674866031759b802cc0039170d56891c2cc11c0f..c199fa0378600c85bb665d45be0f0f0e71821f45 100644 --- a/src/StreamWrapper/LibraryDefinitionsStream.php +++ b/src/StreamWrapper/LibraryDefinitionsStream.php @@ -13,9 +13,24 @@ use Drupal\Core\StreamWrapper\LocalStream; * Provides a stream wrapper for library definitions. * * Can be used with the 'library-definitions' scheme, for example - * 'library-definitions://example.yml'. + * 'library-definitions://example.yml' for a library ID of 'example'. + * + * By default this stream wrapper reads from a single directory that is + * configurable and points to the 'library-definitions' directory within the + * public files directory by default. This makes library definitions writable + * by the webserver by default, which is in anticipation of a user interface + * that fetches definitions from a remote repository and stores them locally. + * For improved security the library definitions can be managed manually (or put + * under version control) and placed in a directory that is not writable by the + * webserver. + * + * The idea of using a stream wrapper for this as well as the default location + * is taken from the 'translations' stream wrapper provided by the Interface + * Translation module. * * @see \Drupal\locale\StreamWrapper\TranslationsStream + * + * @todo Use a setting instead of configuration for the directory. */ class LibraryDefinitionsStream extends LocalStream { diff --git a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php index 9b840d4bf7873c92632ff28ffd844facbc31d7e3..e0e444cb9f56f07e8734b76edc53e2f2cc40d687 100644 --- a/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php +++ b/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php @@ -2,18 +2,15 @@ namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; -use Drupal\libraries\ExternalLibrary\Asset\AssetLibrary; -use Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException; -use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; -use Drupal\Tests\libraries\Kernel\LibraryKernelTestBase; +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; /** * Tests that external asset libraries are registered as core asset libraries. * * @group libraries */ -class AssetLibraryTest extends LibraryKernelTestBase { +class AssetLibraryTypeTest extends LibraryTypeKernelTestBase { /** * {@inheritdoc} @@ -28,41 +25,36 @@ class AssetLibraryTest extends LibraryKernelTestBase { * * @var \Drupal\Core\Asset\LibraryDiscoveryInterface */ - protected $libraryDiscovery; + protected $coreLibraryDiscovery; /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - - $this->libraryDiscovery = $this->container->get('library.discovery'); + $this->coreLibraryDiscovery = $this->container->get('library.discovery'); } /** - * Tests that library metadata is correctly gathered. + * {@inheritdoc} */ - public function testMetadata() { - try { - /** @var \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary $library */ - $library = $this->externalLibraryRegistry->getLibrary('test_asset_library'); - $this->assertInstanceOf(AssetLibrary::class, $library); + protected function getLibraryTypeId() { + return 'asset'; + } - $this->assertEquals('test_asset_library', $library->getId()); - $expected = ['test_asset_library' => [ - 'version' => '1.0.0', - 'css' => ['base' => ['http://example.com/example.css' => []]], - 'js' => ['http://example.com/example.js' => []], - 'dependencies' => [], - ]]; - $this->assertEquals($expected, $library->getAttachableAssetLibraries()); - } - catch (LibraryClassNotFoundException $exception) { - $this->fail(); - } - catch (LibraryDefinitionNotFoundException $exception) { - $this->fail(); - } + /** + * Tests that attachable asset library info is correctly gathered. + */ + public function testAttachableAssetInfo() { + /** @var \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary $library */ + $library = $this->getLibrary(); + $expected = ['test_asset_library' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.css' => []]], + 'js' => ['http://example.com/example.js' => []], + 'dependencies' => [], + ]]; + $this->assertEquals($expected, $library->getAttachableAssetLibraries()); } /** @@ -77,7 +69,7 @@ class AssetLibraryTest extends LibraryKernelTestBase { * @see \Drupal\libraries\ExternalLibrary\Registry\ExternalLibraryRegistry */ public function testAssetLibraryRemote() { - $library = $this->libraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); $expected = [ 'version' => '1.0.0', 'css' => [[ @@ -112,8 +104,8 @@ class AssetLibraryTest extends LibraryKernelTestBase { $this->container->get('string_translation'), 'assets/vendor' )); - $this->libraryDiscovery->clearCachedDefinitions(); - $library = $this->libraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); + $this->coreLibraryDiscovery->clearCachedDefinitions(); + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); $expected = [ 'version' => '1.0.0', 'css' => [[ diff --git a/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php b/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php index 9e5d1e86a6deaebc313076ae9dac42e5ae61060d..9209758913323dccff163832f417487d320562d1 100644 --- a/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php +++ b/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php @@ -2,39 +2,29 @@ namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile; -use Drupal\libraries\ExternalLibrary\Exception\LibraryClassNotFoundException; use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary; use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; -use Drupal\Tests\libraries\Kernel\LibraryKernelTestBase; +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; /** * Tests that the external library manager properly loads PHP file libraries. * * @group libraries */ -class PhpFileLibraryTest extends LibraryKernelTestBase { +class PhpFileLibraryTypeTest extends LibraryTypeKernelTestBase { /** * {@inheritdoc} */ public static $modules = ['libraries', 'libraries_test']; - /** - * The external library manager. - * - * @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface - */ - protected $externalLibraryManager; - /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $this->externalLibraryManager = $this->container->get('libraries.manager'); - $this->container->set('stream_wrapper.php_file_libraries', new TestLibraryFilesStream( $this->container->get('module_handler'), $this->container->get('string_translation'), @@ -43,24 +33,22 @@ class PhpFileLibraryTest extends LibraryKernelTestBase { } /** - * Tests that library metadata is correctly gathered. + * {@inheritdoc} */ - public function testMetadata() { - try { - /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary $library */ - $library = $this->externalLibraryRegistry->getLibrary('test_php_file_library'); - $this->assertInstanceOf(PhpFileLibrary::class, $library); + protected function getLibraryTypeId() { + return 'php_file'; + } - $this->assertEquals('test_php_file_library', $library->getId()); - $expected = [$this->modulePath . DIRECTORY_SEPARATOR . 'tests/libraries/test_php_file_library/test_php_file_library.php']; - $this->assertEquals($expected, $library->getPhpFiles()); - } - catch (LibraryClassNotFoundException $exception) { - $this->fail(); - } - catch (LibraryDefinitionNotFoundException $exception) { - $this->fail(); - } + /** + * Tests that the list of PHP files is correctly gathered. + */ + public function testPhpFileInfo() { + /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary $library */ + $library = $this->getLibrary(); + $this->assertTrue($library->isInstalled()); + $library_path = $this->modulePath . DIRECTORY_SEPARATOR . 'tests/libraries/test_php_file_library'; + $this->assertEquals($library_path, $library->getLocalPath()); + $this->assertEquals(["$library_path/test_php_file_library.php"], $library->getPhpFiles()); } /** @@ -78,7 +66,7 @@ class PhpFileLibraryTest extends LibraryKernelTestBase { } $this->assertFalse(function_exists($function_name)); - $this->externalLibraryManager->load('test_php_file_library'); + $this->libraryManager->load('test_php_file_library'); $this->assertTrue(function_exists($function_name)); } diff --git a/tests/src/Kernel/LibraryKernelTestBase.php b/tests/src/Kernel/LibraryKernelTestBase.php deleted file mode 100644 index 6b5cc968229ea119eb84cf27a7e9dcc8d1294908..0000000000000000000000000000000000000000 --- a/tests/src/Kernel/LibraryKernelTestBase.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php - -namespace Drupal\Tests\libraries\Kernel; - -use Drupal\KernelTests\KernelTestBase; - -/** - * Provides an improved version of the core kernel test base class. - */ -abstract class LibraryKernelTestBase extends KernelTestBase { - - /** - * The external library registry. - * - * @var \Drupal\libraries\ExternalLibrary\Registry\LibraryRegistryInterface - */ - protected $externalLibraryRegistry; - - /** - * The absolute path to the Libraries API module. - * - * @var string - */ - protected $modulePath; - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - - $this->externalLibraryRegistry = $this->container->get('libraries.registry'); - - /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ - $module_handler = $this->container->get('module_handler'); - $this->modulePath = $module_handler->getModule('libraries')->getPath(); - - $this->installConfig('libraries'); - /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ - $config_factory = $this->container->get('config.factory'); - $config_factory->getEditable('libraries.settings') - ->set('library_definitions.local.path', "{$this->modulePath}/tests/library_definitions") - ->save(); - } - -} diff --git a/tests/src/Kernel/LibraryTypeKernelTestBase.php b/tests/src/Kernel/LibraryTypeKernelTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..c0ccaf96f0d8d97186e810bf082aeb60f19b474b --- /dev/null +++ b/tests/src/Kernel/LibraryTypeKernelTestBase.php @@ -0,0 +1,143 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel; + +use Drupal\Component\Plugin\Exception\PluginException; +use Drupal\KernelTests\KernelTestBase; +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; +use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface; + +/** + * Provides an improved version of the core kernel test base class. + */ +abstract class LibraryTypeKernelTestBase extends KernelTestBase { + + /** + * The external library manager. + * + * @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface + */ + protected $libraryManager; + + /** + * The library type factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $libraryTypeFactory; + + /** + * The absolute path to the Libraries API module. + * + * @var string + */ + protected $modulePath; + + abstract protected function getLibraryTypeId(); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->libraryManager = $this->container->get('libraries.manager'); + $this->libraryTypeFactory = $this->container->get('plugin.manager.libraries.library_type'); + + /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ + $module_handler = $this->container->get('module_handler'); + $this->modulePath = $module_handler->getModule('libraries')->getPath(); + + $this->installConfig('libraries'); + /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ + $config_factory = $this->container->get('config.factory'); + $config_factory->getEditable('libraries.settings') + ->set('library_definitions.local.path', "{$this->modulePath}/tests/library_definitions") + ->save(); + } + + /** + * Tests that the library type can be instantiated. + */ + public function testLibraryType() { + $type_id = $this->getLibraryTypeId(); + try { + $this->libraryTypeFactory->createInstance($type_id); + $this->assertTrue(TRUE, "Library type '$type_id' can be instantiated."); + } + catch (PluginException $exception) { + $this->fail("Library type '$type_id' cannot be instantiated."); + } + } + + /** + * Tests that the test library can be instantiated. + */ + public function testLibrary() { + $type_id = $this->getLibraryTypeId(); + $id = $this->getLibraryId(); + try { + $library = $this->libraryManager->getLibrary($id); + $this->assertTrue(TRUE, "Test $type_id library can be instantiated."); + $this->assertInstanceOf($this->getLibraryType()->getLibraryClass(), $library); + $this->assertEquals($this->getLibraryId(), $library->getId()); + + } + catch (LibraryDefinitionNotFoundException $exception) { + $this->fail(); + $this->fail("Missing library definition for test $type_id library."); + } + catch (LibraryTypeNotFoundException $exception) { + $this->fail(); + $this->fail("Missing library type declaration for test $type_id library."); + } + } + + /** + * @return \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface + */ + protected function getLibraryType() { + try { + $library_type = $this->libraryTypeFactory->createInstance($this->getLibraryTypeId()); + } + catch (PluginException $exception) { + $library_type = $this->prophesize(LibraryTypeInterface::class)->reveal(); + } + finally { + return $library_type; + } + } + + /** + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + */ + protected function getLibrary() { + try { + $library = $this->libraryManager->getLibrary($this->getLibraryId()); + } + catch (LibraryDefinitionNotFoundException $exception) { + $library = $this->prophesize(LibraryInterface::class)->reveal(); + } + catch (LibraryTypeNotFoundException $exception) { + $library = $this->prophesize(LibraryInterface::class)->reveal(); + } + catch (PluginException $exception) { + $library = $this->prophesize(LibraryInterface::class)->reveal(); + } + finally { + return $library; + } + } + + /** + * @param $type_id + * @return string + */ + protected function getLibraryId() { + $type_id = $this->getLibraryTypeId(); + return "test_{$type_id}_library"; + } + +}