From b875c3a6bb0c15974d9b56be32751293ea2007f3 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Mon, 3 Dec 2018 17:30:58 -0600 Subject: [PATCH 1/8] Initial standalone Hydrator plugin manager implementation This patch provides an initial standalone plugin manager implementation, for users who do not want to use zend-servicemanager, but still want to use the `DelegatingHydrator`, or to compose the plugin manager in classes that will retrieve hydrators. To achieve this, it first adds the interface `Zend\Hydrator\HydratorPluginManagerInterface`, which extends the PSR-11 `ContainerInterface`; this allows us to typehint on hydrator plugin managers specifically, as well as ensure they comply with PSR-11 (which zend-servicemanager v3 variants do not currently). The `DelegatingHydratorFactory` now tests against that interface instead of the `HydratorPluginManager` when attempting to retrieve the hydrator plugin manager to use. Next, the new `StandaloneHydratorPluginManager` is a non-extensible, hard-coded container for retrieving the shipped hydrators only. If users want something that can be configured, they should: - Use the `HydratorPluginManager` - Configure their application container to load hydrators - Write their own implementation The patch also adds a factory for the `StandaloneHydratorPluginManager`; it simply instantiates and returns it. Finally, `HydratorPluginManagerFactory` now raises an exception if zend-servicemanager is not detected, and guides users to use the `StandaloneHydratorPluginManager` or to install zend-servicemanager, based on their configuration needs. --- src/DelegatingHydratorFactory.php | 4 +- .../MissingHydratorServiceException.php | 25 ++++ src/HydratorPluginManager.php | 2 +- src/HydratorPluginManagerFactory.php | 15 ++ src/HydratorPluginManagerInterface.php | 16 ++ src/StandaloneHydratorPluginManager.php | 138 ++++++++++++++++++ ...StandaloneHydratorPluginManagerFactory.php | 20 +++ ...daloneHydratorPluginManagerFactoryTest.php | 79 ++++++++++ test/StandaloneHydratorPluginManagerTest.php | 122 ++++++++++++++++ 9 files changed, 418 insertions(+), 3 deletions(-) create mode 100644 src/Exception/MissingHydratorServiceException.php create mode 100644 src/HydratorPluginManagerInterface.php create mode 100644 src/StandaloneHydratorPluginManager.php create mode 100644 src/StandaloneHydratorPluginManagerFactory.php create mode 100644 test/StandaloneHydratorPluginManagerFactoryTest.php create mode 100644 test/StandaloneHydratorPluginManagerTest.php diff --git a/src/DelegatingHydratorFactory.php b/src/DelegatingHydratorFactory.php index d81e416..67e9fc1 100644 --- a/src/DelegatingHydratorFactory.php +++ b/src/DelegatingHydratorFactory.php @@ -25,10 +25,10 @@ public function __invoke(ContainerInterface $container) : DelegatingHydrator /** * Locate and return a HydratorPluginManager instance. */ - private function marshalHydratorPluginManager(ContainerInterface $container) : HydratorPluginManager + private function marshalHydratorPluginManager(ContainerInterface $container) : ContainerInterface { // Already one? Return it. - if ($container instanceof HydratorPluginManager) { + if ($container instanceof HydratorPluginManagerInterface) { return $container; } diff --git a/src/Exception/MissingHydratorServiceException.php b/src/Exception/MissingHydratorServiceException.php new file mode 100644 index 0000000..2ac28cd --- /dev/null +++ b/src/Exception/MissingHydratorServiceException.php @@ -0,0 +1,25 @@ + + */ + private $aliases = [ + 'arrayserializable' => ArraySerializableHydrator::class, + ArraySerializable::class => ArraySerializableHydrator::class, + 'arrayserializablehydrator' => ArraySerializableHydrator::class, + ClassMethods::class => ClassMethodsHydrator::class, + 'classmethods' => ClassMethodsHydrator::class, + 'classmethodshydrator' => ClassMethodsHydrator::class, + 'delegatinghydrator' => DelegatingHydrator::class, + ObjectProperty::class => ObjectPropertyHydrator::class, + 'objectpropertyhydrator' => ObjectPropertyHydrator::class, + 'objectproperty' => ObjectPropertyHydrator::class, + Reflection::class => ReflectionHydrator::class, + 'reflectionhydrator' => ReflectionHydrator::class, + 'reflection' => ReflectionHydrator::class, + ]; + + /** + * @var array + */ + private $factories = []; + + /** + * @var callable Invokable factory for hydrators without dedicated factories. + */ + private $invokableFactory; + + public function __construct() + { + $this->invokableFactory = function (ContainerInterface $container, string $class) { + return new $class(); + }; + + $this->factories = [ + ArraySerializableHydrator::class => $this->invokableFactory, + ClassMethodsHydrator::class => $this->invokableFactory, + DelegatingHydrator::class => new DelegatingHydratorFactory(), + ObjectPropertyHydrator::class => $this->invokableFactory, + ReflectionHydrator::class => $this->invokableFactory, + ]; + } + + /** + * {@inheritDoc} + */ + public function get($id) + { + $class = $this->resolveName($id); + if (! $class) { + throw Exception\MissingHydratorServiceException::forService($id); + } + + $instance = ($this->factories[$class])($this, $class); + + return $instance; + } + + /** + * {@inheritDoc} + */ + public function has($id) + { + return null !== $this->resolveName($id); + } + + /** + * Resolve a service name from an identifier. + * + * If $name is registered in $factories, the method returns it verbatim. + * + * Next it checks if the $name is registered verbatim in $aliases; if so, + * it returns the target of the alias. + * + * finally, it does a strtolower() on it and looks to see if it exists + * in the $aliases array; if so, it returns the target of the alias, + * otherwise it returns null indicating inability to resolve. + */ + private function resolveName(string $name) : ?string + { + if (isset($this->factories[$name])) { + return $name; + } + + if (isset($this->aliases[$name])) { + return $this->aliases[$name]; + } + + $alias = strtolower($name); + return $this->aliases[$alias] ?? null; + } +} diff --git a/src/StandaloneHydratorPluginManagerFactory.php b/src/StandaloneHydratorPluginManagerFactory.php new file mode 100644 index 0000000..a1e7f42 --- /dev/null +++ b/src/StandaloneHydratorPluginManagerFactory.php @@ -0,0 +1,20 @@ +factory = new StandaloneHydratorPluginManagerFactory(); + $this->container = $this->prophesize(ContainerInterface::class); + } + + public function assertDefaultServices( + StandaloneHydratorPluginManager $manager, + string $message = self::MESSAGE_DEFAULT_SERVICES + ) { + $this->assertTrue($manager->has('ArraySerializable'), sprintf($message, 'ArraySerializable')); + $this->assertTrue($manager->has('ArraySerializableHydrator'), sprintf($message, 'ArraySerializableHydrator')); + $this->assertTrue($manager->has(ArraySerializable::class), sprintf($message, ArraySerializable::class)); + $this->assertTrue( + $manager->has(ArraySerializableHydrator::class), + sprintf($message, ArraySerializableHydrator::class) + ); + + $this->assertTrue($manager->has('ClassMethods'), sprintf($message, 'ClassMethods')); + $this->assertTrue($manager->has('ClassMethodsHydrator'), sprintf($message, 'ClassMethodsHydrator')); + $this->assertTrue($manager->has(ClassMethods::class), sprintf($message, ClassMethods::class)); + $this->assertTrue($manager->has(ClassMethodsHydrator::class), sprintf($message, ClassMethodsHydrator::class)); + + $this->assertTrue($manager->has('DelegatingHydrator'), sprintf($message, 'DelegatingHydrator')); + $this->assertTrue($manager->has(DelegatingHydrator::class), sprintf($message, DelegatingHydrator::class)); + + $this->assertTrue($manager->has('ObjectProperty'), sprintf($message, 'ObjectProperty')); + $this->assertTrue($manager->has('ObjectPropertyHydrator'), sprintf($message, 'ObjectPropertyHydrator')); + $this->assertTrue($manager->has(ObjectProperty::class), sprintf($message, ObjectProperty::class)); + $this->assertTrue( + $manager->has(ObjectPropertyHydrator::class), + sprintf($message, ObjectPropertyHydrator::class) + ); + + $this->assertTrue($manager->has('Reflection'), sprintf($message, 'Reflection')); + $this->assertTrue($manager->has('ReflectionHydrator'), sprintf($message, 'ReflectionHydrator')); + $this->assertTrue($manager->has(Reflection::class), sprintf($message, Reflection::class)); + $this->assertTrue($manager->has(ReflectionHydrator::class), sprintf($message, ReflectionHydrator::class)); + } + + public function testCreatesPluginManagerWithDefaultServices() + { + $manager = ($this->factory)($this->container->reveal()); + $this->assertDefaultServices($manager); + } +} diff --git a/test/StandaloneHydratorPluginManagerTest.php b/test/StandaloneHydratorPluginManagerTest.php new file mode 100644 index 0000000..0ea1d4c --- /dev/null +++ b/test/StandaloneHydratorPluginManagerTest.php @@ -0,0 +1,122 @@ +manager = new StandaloneHydratorPluginManager(); + } + + /** + * @return mixed + */ + public function reflectProperty(object $class, string $property) + { + $r = new ReflectionProperty($class, $property); + $r->setAccessible(true); + return $r->getValue($class); + } + + public function testInstantiationInitializesInvokableFactoryProperty() + { + $this->assertAttributeInstanceOf(Closure::class, 'invokableFactory', $this->manager); + } + + public function hydratorsWithoutConstructors() : iterable + { + yield 'ArraySerializable' => [Hydrator\ArraySerializableHydrator::class]; + yield 'ArraySerializableHydrator' => [Hydrator\ArraySerializableHydrator::class]; + yield 'ClassMethods' => [Hydrator\ClassMethodsHydrator::class]; + yield 'ClassMethodsHydrator' => [Hydrator\ClassMethodsHydrator::class]; + yield Hydrator\ArraySerializable::class => [Hydrator\ArraySerializableHydrator::class]; + yield Hydrator\ClassMethods::class => [Hydrator\ClassMethodsHydrator::class]; + yield Hydrator\ObjectProperty::class => [Hydrator\ObjectPropertyHydrator::class]; + yield Hydrator\Reflection::class => [Hydrator\ReflectionHydrator::class]; + yield 'ObjectPropertyHydrator' => [Hydrator\ObjectPropertyHydrator::class]; + yield 'ObjectProperty' => [Hydrator\ObjectPropertyHydrator::class]; + yield 'ReflectionHydrator' => [Hydrator\ReflectionHydrator::class]; + yield 'Reflection' => [Hydrator\ReflectionHydrator::class]; + } + + /** + * @dataProvider hydratorsWithoutConstructors + */ + public function testInstantiationInitializesFactoriesForHydratorsWithoutConstructorArguments(string $class) + { + $invokableFactory = $this->reflectProperty($this->manager, 'invokableFactory'); + $factories = $this->reflectProperty($this->manager, 'factories'); + + $this->assertArrayHasKey($class, $factories); + $this->assertSame($invokableFactory, $factories[$class]); + } + + public function testDelegatingHydratorFactoryIsInitialized() + { + $factories = $this->reflectProperty($this->manager, 'factories'); + $this->assertInstanceOf( + Hydrator\DelegatingHydratorFactory::class, + $factories[Hydrator\DelegatingHydrator::class] + ); + } + + public function testHasReturnsFalseForUnknownNames() + { + $this->assertFalse($this->manager->has('unknown-service-name')); + } + + public function knownServices() : iterable + { + foreach ($this->hydratorsWithoutConstructors() as $key => $data) { + $class = array_pop($data); + $alias = sprintf('%s alias', $key); + $fqcn = sprintf('%s class', $key); + + yield $alias => [$key, $class]; + yield $fqcn => [$class, $class]; + } + + yield 'DelegatingHydrator alias' => ['DelegatingHydrator', Hydrator\DelegatingHydrator::class]; + yield 'DelegatingHydrator class' => [Hydrator\DelegatingHydrator::class, Hydrator\DelegatingHydrator::class]; + } + + /** + * @dataProvider knownServices + */ + public function testHasReturnsTrueForKnownServices(string $service) + { + $this->assertTrue($this->manager->has($service)); + } + + public function testGetRaisesExceptionForUnknownService() + { + $this->expectException(Hydrator\Exception\MissingHydratorServiceException::class); + $this->manager->get('unknown-service-name'); + } + + /** + * @dataProvider knownServices + */ + public function testGetReturnsExpectedTypesForKnownServices(string $service, string $expectedType) + { + $instance = $this->manager->get($service); + $this->assertInstanceOf($expectedType, $instance); + } +} From 320f8c9cf533a911895a04afdd0573580e1ba056 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 4 Dec 2018 09:23:21 -0600 Subject: [PATCH 2/8] Register the StandaloneHydratorPluginManager service with the container Registers the StandaloneHydratorPluginManager via factory configuration. If zend-servicemanager is installed, it aliases the `HydratorManager` service to the `HydratorPluginManager`, but otherwise uses the `StandaloneHydratorPluginManager`. --- src/ConfigProvider.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index c626356..eff8167 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -9,6 +9,8 @@ namespace Zend\Hydrator; +use Zend\ServiceManager\ServiceManager; + class ConfigProvider { /** @@ -26,16 +28,25 @@ public function __invoke() : array /** * Return dependency mappings for this component. * + * If zend-servicemanager is installed, this will alias the HydratorPluginManager + * to the `HydratorManager` service; otherwise, it aliases the + * StandaloneHydratorPluginManager. + * * @return string[][] */ public function getDependencyConfig() : array { + $hydratorManagerTarget = class_exists(ServiceManager::class) + ? HydratorPluginManager::class + : StandaloneHydratorPluginManager::class; + return [ 'aliases' => [ - 'HydratorManager' => HydratorPluginManager::class, + 'HydratorManager' => $hydratorManagerTarget, ], 'factories' => [ - HydratorPluginManager::class => HydratorPluginManagerFactory::class, + HydratorPluginManager::class => HydratorPluginManagerFactory::class, + StandaloneHydratorPluginManager::class => StandaloneHydratorPluginManagerFactory::class, ], ]; } From 8d781cdc21e37c75e1245c5d6be7d4c0514f32e7 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 4 Dec 2018 11:51:37 -0600 Subject: [PATCH 3/8] Documents hydrator plugin managers Documents what they are, why they exist, and how to configure each. It also provides an example of a custom hydrator plugin manager. --- docs/book/v3/migration.md | 9 +- docs/book/v3/plugin-managers.md | 197 ++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 docs/book/v3/plugin-managers.md diff --git a/docs/book/v3/migration.md b/docs/book/v3/migration.md index 2f86d45..aba3fd5 100644 --- a/docs/book/v3/migration.md +++ b/docs/book/v3/migration.md @@ -222,4 +222,11 @@ is now marked `private`. This version removes support for zend-servicemanager v2 service names. Under zend-servicemanager v2, most special characters were removed, and the name normalized to all lowercase. Now, only fully qualified class names are mapped to -factories, and short names (names omitting the namespace) are mapped as aliases. +factories, and short names (names omitting the namespace and/or "Hydrator" +suffix) are mapped as aliases. + +Additionally, version 3 ships a standalone, PSR-11 compliant version, +`Zend\Hydrator\StandaloneHydratorPluginManager`. By default, the `HydratorManager` +service alias will point to the `StandaloneHydratorPluginManager` if +zend-servicemanager is not installed, and the `HydratorPluginManager` otherwise. +See the [plugin managers chapter](plugin-managers.md) for more details. diff --git a/docs/book/v3/plugin-managers.md b/docs/book/v3/plugin-managers.md new file mode 100644 index 0000000..4c68f8d --- /dev/null +++ b/docs/book/v3/plugin-managers.md @@ -0,0 +1,197 @@ +# Plugin Managers + +It can be useful to compose a plugin manager from which you can retrieve +hydrators; in fact, `Zend\Hydrator\DelegatingHydrator` does exactly that! +With such a manager, you can retrieve instances using short names, or instances +that have dependencies on other services, without needing to know the details of +how that works. + +Examples of Hydrator plugin managers in real-world scenarios include: + +- [hydrating database result sets](https://docs.zendframework.com/zend-db/result-set/#zend92db92resultset92hydratingresultset) +- [preparing API payloads](https://docs.zendframework.com/zend-expressive-hal/resource-generator/#resourcegenerator) + +## HydratorPluginManagerInterface + +We provide two plugin manager implementations. Essentially, they only need to +implement the [PSR-11 ContainerInterface](https://www.php-fig.org/psr/psr-11/), +but plugin managers in current versions of [zend-servicemanager](https://docs.zendframework.com/zend-servicemanager/) +only implement it indirectly via the container-interop project. + +As such, we ship `Zend\Hydrator\HydratorPluginManagerInterface`, which simply +extends the PSR-11 `Psr\Container\ContainerInterface`. Each of our +implementations implement it. + +## HydratorPluginManager + +If you have used zend-hydrator prior to version 3, you are likely already +familiar with this class, as it has been the implementation we have shipped from +initial versions. The `HydratorPluginManager` extends the zend-servicemanager +`AbstractPluginManager`, and has the following behaviors: + +- It will only return `Zend\Hydrator\HydratorInterface` instances. +- It defines short-name aliases for all shipped hydrators (the class name minus + the namespace), in a variety of casing combinations. +- All but the `DelegatingHydrator` are defined as invokable services (meaning + they can be instantiated without any constructor arguments). +- The `DelegatingHydrator` is configured as a factory-based service, mapping to + the `Zend\Hydrator\DelegatingHydratorFactory`. +- No services are shared; a new instance is created each time you call `get()`. + +### HydratorPluginManagerFactory + +`Zend\Hydrator\HydratorPluginManager` is mapped to the factory +`Zend\Hydrator\HydratorPluginManagerFactory` when wired to the dependency +injection container. + +The factory will look for the `config` service, and use the `hydrators` +configuration key to seed it with additional services. This configuration key +should map to an array that follows [standard zend-servicemanager configuration](https://docs.zendframework.com/zend-servicemanager/configuring-the-service-manager/). + +## StandaloneHydratorPluginManager + +`Zend\Hydrator\StandaloneHydratorPluginManager` provides an implementation that +has no dependencies on other libraries. **It can only load the hydrators shipped +with zend-hydrator**. + +### StandardHydratorPluginManagerFactory + +`Zend\Hydrator\StandardHydratorPluginManager` is mapped to the factory +`Zend\Hydrator\StandardHydratorPluginManagerFactory` when wired to the dependency +injection container. + +## HydratorManager alias + +`Zend\Hydrator\ConfigManager` defines an alias service, `HydratorManager`. That +service will point to `Zend\Hydrator\HydratorPluginManager` if +zend-servicemanager is installed, and `Zend\Hydrator\StandaloneHydratorPluginManager` +otherwise. + +## Custom plugin managers + +If you do not want to use zend-servicemanager, but want a plugin manager that is +customizable, or at least capable of loading the hydrators you have defined for +your application, you should write a custom implementation of +`Zend\Hydrator\HydratorPluginManagerInterface`, and wire it to the +`HydratorManager` service, and/or one of the existing service names. + +As an example, if you want a configurable solution that uses factories, and want +those factories capable of pulling application-level dependencies, you might do +something like the following: + +```php +namespace YourApplication; + +use Psr\Container\NotFoundExceptionInterface; +use Psr\Container\ContainerInterface; +use RuntimeException; +use Zend\Hydrator\HydratorInterface; +use Zend\Hydrator\HydratorPluginManagerInterface; +use Zend\Hydrator\StandaloneHydratorPluginManager; + +class CustomHydratorPluginManager implements + ContainerInterface, + HydratorPluginManagerInterface +{ + /** @var ContainerInterface */ + private $appContainer; + + /** @var StandaloneHydratorPluginManager */ + private $defaults; + + /** @var array */ + private $factories = []; + + public function __construct(ContainerInterface $appContainer) + { + $this->appContainer = $appContainer; + $this->defaults = new StandaloneHydratorPluginManager(); + } + + /** + * {@inheritDoc} + */ + public function get($id) : HydratorInterface + { + if (! isset($this->factories[$id]) && ! $this->defaults->has($id)) { + $message = sprintf('Hydrator service %s not found', $id); + throw new class($message) extends RuntimeException implements NotFoundExceptionInterface {}; + } + + // Default was requested; fallback to standalone container + if (! isset($this->factories[$id])) { + return $this->defaults->get($id); + } + + $factory = $this->factories[$id]; + if (is_string($factory)) { + $this->factories[$id] = $factory = new $factory(); + } + + return $factory($this->appContainer, $id); + } + + public function has($id) : bool + { + return isset($this->factories[$id]) || $this->defaults->has($id); + } + + public function setFactoryClass(string $name, string $factory) : void + { + $this->factories[$name] = $factory; + } + + public function setFactory(string $name, callable $factory) : void + { + $this->factories[$name] = $factory; + } +} + +class CustomHydratorPluginManagerFactory +{ + public function __invoke(ContainerInterface $container) : CustomHydratorPluginManager + { + $config = $container->has('config') ? $container->get('config') : []; + $config = $config['hydrators']['factories'] ?? []; + + $manager = new CustomHydratorPluginManager($this); + + if ([] !== $config) { + $this->configureManager($manager, $config); + } + + return $manager; + } + + /** + * @param array $config + */ + private function configureManager(CustomHydratorPluginManager $manager, array $config) : void + { + foreach ($config as $name => $factory) { + is_string($factory) + ? $manager->setFactoryClass($name, $factory) + : $manager->setFactory($name, $factory); + } + } +} + +// in config/autoload/hydrators.global.php or similar: +return [ + 'dependencies' => [ + 'aliases' => [ + 'HydratorManager' => \YourApplication\CustomHydratorPluginManager::class, + ], + 'factories' => [ + \YourApplication\CustomHydratorPluginManager::class => \YourApplication\CustomHydratorPluginManagerFactory::class + ], + ], + 'hydrators' => [ + 'factories' => [ + \Blog\PostHydrator::class => \Blog\PostHydratorFactory::class, + \News\ItemHydrator::class => \News\ItemHydratorFactory::class, + // etc. + ], + ], +]; +``` diff --git a/mkdocs.yml b/mkdocs.yml index b494322..b188190 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ pages: - "Mapping": v3/naming-strategy/map-naming-strategy.md - "Underscore Mapping": v3/naming-strategy/underscore-naming-strategy.md - "Composite": v3/naming-strategy/composite-naming-strategy.md + - "Plugin Managers": v3/plugin-managers.md - Migration: v3/migration.md - v2: - "Quick Start": v2/quick-start.md From 4f9a01721f833ab5ee7d010c68214842488a9c25 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Tue, 4 Dec 2018 12:13:02 -0600 Subject: [PATCH 4/8] Adds CHANGELOG entries for #87 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2e106..c6bc476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to this project will be documented in this file, in reverse ### Added +- [#87](https://github.com/zendframework/zend-hydrator/pull/87) adds `Zend\Hydrator\HydratorPluginManagerInterface` to allow + type-hinting on plugin manager implementations. The interface simply extends + the [PSR-11 ContainerInterface](https://www.php-fig.org/psr/psr-11/). + +- [#87](https://github.com/zendframework/zend-hydrator/pull/87) adds `Zend\Hydrator\StandaloneHydratorPluginManager` as an implementation + of each of `Psr\Container\ContainerInterface` and `Zend\Hydrator\HydratorPluginManagerInterface`, + along with a factory for creating it, `Zend\Hydrator\StandaloneHydratorPluginManagerFactory`. + It can act as a replacement for `Zend\Hydrator\HydratorPluginManager`, but + only supports the shipped hydrator implementations. See the [plugin manager documentation](https://docs.zendframework.com/zend-hydrator/v3/plugin-managers/) + for more details on usage. + - [#79](https://github.com/zendframework/zend-hydrator/pull/79) adds a third, optional parameter to the `DateTimeFormatterStrategy` constructor. The parameter is a boolean, and, when enabled, a string that can be parsed by the `DateTime` constructor will still result in a `DateTime` instance during @@ -28,6 +39,15 @@ All notable changes to this project will be documented in this file, in reverse Aliases resolving the original class name to the new class were also added to the `HydratorPluginManager` to ensure you can still obtain instances. +- [#87](https://github.com/zendframework/zend-hydrator/pull/87) modifies `Zend\Hydrator\ConfigProvider` to add a factory entry for + `Zend\Hydrator\StandaloneHydratorPluginManager`. + +- [#87](https://github.com/zendframework/zend-hydrator/pull/87) modifies `Zend\Hydrator\ConfigProvider` to change the target of the + `HydratorManager` alias based on the presence of the zend-servicemanager + package; if the package is not available, the target points to + `Zend\Hydrator\StandaloneHydratorPluginManager` instead of + `Zend\Hydrator\HydratorPluginManager`. + - [#83](https://github.com/zendframework/zend-hydrator/pull/83) renames `Zend\Hydrator\FilterEnabledInterface` to `Zend\Hydrator\Filter\FilterEnabledInterface` (new namespace). - [#83](https://github.com/zendframework/zend-hydrator/pull/83) renames `Zend\Hydrator\NamingStrategyEnabledInterface` to `Zend\Hydrator\NamingStrategy\NamingStrategyEnabledInterface` (new namespace). From c3617027a438611c13aca8e71d421e8bc8d5da89 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 6 Dec 2018 10:07:44 -0600 Subject: [PATCH 5/8] Make documentation changes based on feedback from @Ocramius - Split code block into one for each discrete class and/or configuration; list location of file for each. - Remove `ContainerInterface` from list of what `CustomHydratorPluginManager` implements; it is implied by `HydratorPluginManagerInterface`. --- docs/book/v3/plugin-managers.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/book/v3/plugin-managers.md b/docs/book/v3/plugin-managers.md index 4c68f8d..6c88236 100644 --- a/docs/book/v3/plugin-managers.md +++ b/docs/book/v3/plugin-managers.md @@ -80,6 +80,8 @@ those factories capable of pulling application-level dependencies, you might do something like the following: ```php +// In src/YourApplication/CustomHydratorPluginManager.php: + namespace YourApplication; use Psr\Container\NotFoundExceptionInterface; @@ -89,9 +91,7 @@ use Zend\Hydrator\HydratorInterface; use Zend\Hydrator\HydratorPluginManagerInterface; use Zend\Hydrator\StandaloneHydratorPluginManager; -class CustomHydratorPluginManager implements - ContainerInterface, - HydratorPluginManagerInterface +class CustomHydratorPluginManager implements HydratorPluginManagerInterface { /** @var ContainerInterface */ private $appContainer; @@ -146,6 +146,14 @@ class CustomHydratorPluginManager implements $this->factories[$name] = $factory; } } +``` + +```php +// In src/YourApplication/CustomHydratorPluginManagerFactory.php: + +namespace YourApplication; + +use Psr\Container\ContainerInterface; class CustomHydratorPluginManagerFactory { @@ -175,8 +183,11 @@ class CustomHydratorPluginManagerFactory } } } +``` +```php // in config/autoload/hydrators.global.php or similar: + return [ 'dependencies' => [ 'aliases' => [ From 479299e6ada193aa27159957e88c253000f554f6 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 6 Dec 2018 10:09:59 -0600 Subject: [PATCH 6/8] Adds `@method` annotation for `get()` to `HydratorPluginManagerInterface` - Indicates that the `get()` method should return a hydrator implementation. --- src/HydratorPluginManagerInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/HydratorPluginManagerInterface.php b/src/HydratorPluginManagerInterface.php index bf643a5..82fa38d 100644 --- a/src/HydratorPluginManagerInterface.php +++ b/src/HydratorPluginManagerInterface.php @@ -11,6 +11,9 @@ use Psr\Container\ContainerInterface; +/** + * @method HydratorInterface get(string $id) + */ interface HydratorPluginManagerInterface extends ContainerInterface { } From ad7bbbb5397d8b76640b50658f67a73cafc27b50 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 6 Dec 2018 10:12:53 -0600 Subject: [PATCH 7/8] Remove `$invokableFactory` property from StandaloneHydratorPluginManager - Not needed, as assigned in constructor, and never again referenced. --- src/StandaloneHydratorPluginManager.php | 15 +++++---------- test/StandaloneHydratorPluginManagerTest.php | 10 ++-------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/StandaloneHydratorPluginManager.php b/src/StandaloneHydratorPluginManager.php index 8320d9d..9877c68 100644 --- a/src/StandaloneHydratorPluginManager.php +++ b/src/StandaloneHydratorPluginManager.php @@ -67,23 +67,18 @@ final class StandaloneHydratorPluginManager implements */ private $factories = []; - /** - * @var callable Invokable factory for hydrators without dedicated factories. - */ - private $invokableFactory; - public function __construct() { - $this->invokableFactory = function (ContainerInterface $container, string $class) { + $invokableFactory = function (ContainerInterface $container, string $class) { return new $class(); }; $this->factories = [ - ArraySerializableHydrator::class => $this->invokableFactory, - ClassMethodsHydrator::class => $this->invokableFactory, + ArraySerializableHydrator::class => $invokableFactory, + ClassMethodsHydrator::class => $invokableFactory, DelegatingHydrator::class => new DelegatingHydratorFactory(), - ObjectPropertyHydrator::class => $this->invokableFactory, - ReflectionHydrator::class => $this->invokableFactory, + ObjectPropertyHydrator::class => $invokableFactory, + ReflectionHydrator::class => $invokableFactory, ]; } diff --git a/test/StandaloneHydratorPluginManagerTest.php b/test/StandaloneHydratorPluginManagerTest.php index 0ea1d4c..52884a4 100644 --- a/test/StandaloneHydratorPluginManagerTest.php +++ b/test/StandaloneHydratorPluginManagerTest.php @@ -35,11 +35,6 @@ public function reflectProperty(object $class, string $property) return $r->getValue($class); } - public function testInstantiationInitializesInvokableFactoryProperty() - { - $this->assertAttributeInstanceOf(Closure::class, 'invokableFactory', $this->manager); - } - public function hydratorsWithoutConstructors() : iterable { yield 'ArraySerializable' => [Hydrator\ArraySerializableHydrator::class]; @@ -61,11 +56,10 @@ public function hydratorsWithoutConstructors() : iterable */ public function testInstantiationInitializesFactoriesForHydratorsWithoutConstructorArguments(string $class) { - $invokableFactory = $this->reflectProperty($this->manager, 'invokableFactory'); - $factories = $this->reflectProperty($this->manager, 'factories'); + $factories = $this->reflectProperty($this->manager, 'factories'); $this->assertArrayHasKey($class, $factories); - $this->assertSame($invokableFactory, $factories[$class]); + $this->assertInstanceOf(Closure::class, $factories[$class]); } public function testDelegatingHydratorFactoryIsInitialized() From 375386e3a4a5af91339a9a2ebeea98a6db7d0b3d Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 6 Dec 2018 10:15:59 -0600 Subject: [PATCH 8/8] Remove redundancies - useless variable assignment in `resolveName()` - basically useless docblock for `resolveName()` (as implementation can be easily understood looking at its five lines of code) --- src/StandaloneHydratorPluginManager.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/StandaloneHydratorPluginManager.php b/src/StandaloneHydratorPluginManager.php index 9877c68..c293940 100644 --- a/src/StandaloneHydratorPluginManager.php +++ b/src/StandaloneHydratorPluginManager.php @@ -34,9 +34,7 @@ * wire hydrators into your application container; or write your own * implementation. */ -final class StandaloneHydratorPluginManager implements - ContainerInterface, - HydratorPluginManagerInterface +final class StandaloneHydratorPluginManager implements HydratorPluginManagerInterface { /** * To allow using the short name (class name without namespace), this maps @@ -107,15 +105,6 @@ public function has($id) /** * Resolve a service name from an identifier. - * - * If $name is registered in $factories, the method returns it verbatim. - * - * Next it checks if the $name is registered verbatim in $aliases; if so, - * it returns the target of the alias. - * - * finally, it does a strtolower() on it and looks to see if it exists - * in the $aliases array; if so, it returns the target of the alias, - * otherwise it returns null indicating inability to resolve. */ private function resolveName(string $name) : ?string { @@ -127,7 +116,6 @@ private function resolveName(string $name) : ?string return $this->aliases[$name]; } - $alias = strtolower($name); - return $this->aliases[$alias] ?? null; + return $this->aliases[strtolower($name)] ?? null; } }