From e2560447522aa3e3541b2438e9cd5eac738fb80d Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 12:46:09 -0600 Subject: [PATCH 1/7] Move all "Enabled" interfaces to appropriate subnamespace - `Zend\Hydrator\FilterEnabledInterface` becomes `Zend\Hydrator\Filter\FilterEnabledInterface` - `Zend\Hydrator\NamingStrategyEnabledInterface` becomes `Zend\Hydrator\NamingStrategy\NamingStrategyEnabledInterface` - `Zend\Hydrator\StrategyEnabledInterface` becomes `Zend\Hydrator\Strategy\StrategyEnabledInterface` This puts them where they belong; in point of fact, `StrategyEnabledInterface` was documented as being under that namespace already! --- docs/book/v3/migration.md | 14 +++++++++++--- docs/book/v3/strategy.md | 2 +- src/AbstractHydrator.php | 6 +++--- src/{ => Filter}/FilterEnabledInterface.php | 8 ++++---- .../NamingStrategyEnabledInterface.php | 8 +++----- src/{ => Strategy}/StrategyEnabledInterface.php | 6 +++--- 6 files changed, 25 insertions(+), 19 deletions(-) rename src/{ => Filter}/FilterEnabledInterface.php (87%) rename src/{ => NamingStrategy}/NamingStrategyEnabledInterface.php (70%) rename src/{ => Strategy}/StrategyEnabledInterface.php (79%) diff --git a/docs/book/v3/migration.md b/docs/book/v3/migration.md index 84e1fb3..a885e31 100644 --- a/docs/book/v3/migration.md +++ b/docs/book/v3/migration.md @@ -30,6 +30,14 @@ The minimum supported version of zend-serializer (used by the The minimum supported version of zend-servicemanager (used by the `HydratorPluginManager`) is now 3.3.2. +## Renamed interfaces + +The following interfaces were renamed: + +- `Zend\Hydrator\FilterEnabledInterface` becomes `Zend\Hydrator\Filter\FilterEnabledInterface`. +- `Zend\Hydrator\NamingStrategyEnabledInterface` becomes `Zend\Hydrator\NamingStrategy\NamingStrategyEnabledInterface`. +- `Zend\Hydrator\StrategyEnabledInterface` becomes `Zend\Hydrator\Strategy\StrategyEnabledInterface`. + ## Interface changes Each of the interfaces provided by this package have been updated to add @@ -38,7 +46,7 @@ on parameters and return values. These include: - `Zend\Hydrator\ExtractionInterface`: - `extract($object)` becomes `extract(object $object) : array` -- `Zend\Hydrator\FilterEnabledInterface`: +- `Zend\Hydrator\Filter\FilterEnabledInterface` (was `Zend\Hydrator\FilterEnabledInterface`): - `addFilter($name, $filter, $condition = Zend\Hydrator\Filter\FilterComposite::CONDITION_OR)` becomes `addFilter(string $name, $filter, int $condition = Zend\Hydrator\Filter\FilterComposite::CONDITION_OR) : void` - `hasFilter($name)` becomes `hasFilter(string $name) : bool` - `removeFilter($name)` becomes `removeFilter(string $name) : void` @@ -53,11 +61,11 @@ on parameters and return values. These include: - `setOptions($options)` becomes `setOptions(iterable $options) : void` - `Zend\Hydrator\HydratorProviderInterface`: - `getHydratorConfig()` becomes `getHydratorConfig() : array` -- `Zend\Hydrator\NamingStrategyEnabledInterface`: +- `Zend\Hydrator\NamingStrategy\NamingStrategyEnabledInterface` (was `Zend\Hydrator\NamingStrategyEnabledInterface`): - `setNamingStrategy(Zend\Hydrator\NamingStrategy\NamingStrategyInterface $strategy)` becomes `setNamingStrategy(Zend\Hydrator\NamingStrategy\NamingStrategyInterface $strategy) : void` - `getNamingStrategy()` becomes `getNamingStrategy() : Zend\Hydrator\NamingStrategy\NamingStrategyInterface` - `removeNamingStrategy()` becomes `removeNamingStrategy() : void` -- `Zend\Hydrator\StrategyEnabledInterface`: +- `Zend\Hydrator\Strategy\StrategyEnabledInterface` (was `Zend\Hydrator\StrategyEnabledInterface`): - `addStrategy($name, Zend\Hydrator\Strategy\StrategyInterface $strategy)` becomes `addStrategy(string $name, Zend\Hydrator\Strategy\StrategyInterface $strategy) : void` - `getStrategy($name)` becomes `getStrategy(string $name) : Zend\Hydrator\Strategy\StrategyInterface` - `hasStrategy($name)` becomes `hasStrategy(string $name) : bool` diff --git a/docs/book/v3/strategy.md b/docs/book/v3/strategy.md index 6e06b8f..167e4e5 100644 --- a/docs/book/v3/strategy.md +++ b/docs/book/v3/strategy.md @@ -38,7 +38,7 @@ To allow strategies within your hydrator, `Zend\Hydrator\Strategy\StrategyEnable provides the following methods: ```php -namespace Zend\Hydrator; +namespace Zend\Hydrator\Strategy; use Zend\Hydrator\Strategy\StrategyInterface; diff --git a/src/AbstractHydrator.php b/src/AbstractHydrator.php index e343523..f9b931f 100644 --- a/src/AbstractHydrator.php +++ b/src/AbstractHydrator.php @@ -15,9 +15,9 @@ abstract class AbstractHydrator implements HydratorInterface, - StrategyEnabledInterface, - FilterEnabledInterface, - NamingStrategyEnabledInterface + Strategy\StrategyEnabledInterface, + Filter\FilterEnabledInterface, + NamingStrategy\NamingStrategyEnabledInterface { /** * The list with strategies that this hydrator has. diff --git a/src/FilterEnabledInterface.php b/src/Filter/FilterEnabledInterface.php similarity index 87% rename from src/FilterEnabledInterface.php rename to src/Filter/FilterEnabledInterface.php index 00861a4..e52d7b4 100644 --- a/src/FilterEnabledInterface.php +++ b/src/Filter/FilterEnabledInterface.php @@ -7,9 +7,9 @@ declare(strict_types=1); -namespace Zend\Hydrator; +namespace Zend\Hydrator\Filter; -interface FilterEnabledInterface extends Filter\FilterProviderInterface +interface FilterEnabledInterface extends FilterProviderInterface { /** * Add a new filter to take care of what needs to be hydrated. @@ -30,9 +30,9 @@ interface FilterEnabledInterface extends Filter\FilterProviderInterface * * * @param string $name Index in the composite - * @param callable|Filter\FilterInterface $filter + * @param callable|FilterInterface $filter */ - public function addFilter(string $name, $filter, int $condition = Filter\FilterComposite::CONDITION_OR) : void; + public function addFilter(string $name, $filter, int $condition = FilterComposite::CONDITION_OR) : void; /** * Check whether a specific filter exists at key $name or not diff --git a/src/NamingStrategyEnabledInterface.php b/src/NamingStrategy/NamingStrategyEnabledInterface.php similarity index 70% rename from src/NamingStrategyEnabledInterface.php rename to src/NamingStrategy/NamingStrategyEnabledInterface.php index 2e4aaae..a50ca5c 100644 --- a/src/NamingStrategyEnabledInterface.php +++ b/src/NamingStrategy/NamingStrategyEnabledInterface.php @@ -7,21 +7,19 @@ declare(strict_types=1); -namespace Zend\Hydrator; +namespace Zend\Hydrator\NamingStrategy; interface NamingStrategyEnabledInterface { /** * Adds the given naming strategy - * - * @param NamingStrategy\NamingStrategyInterface $strategy The naming to register. */ - public function setNamingStrategy(NamingStrategy\NamingStrategyInterface $strategy) : void; + public function setNamingStrategy(NamingStrategyInterface $strategy) : void; /** * Gets the naming strategy. */ - public function getNamingStrategy() : NamingStrategy\NamingStrategyInterface; + public function getNamingStrategy() : NamingStrategyInterface; /** * Checks if a naming strategy exists. diff --git a/src/StrategyEnabledInterface.php b/src/Strategy/StrategyEnabledInterface.php similarity index 79% rename from src/StrategyEnabledInterface.php rename to src/Strategy/StrategyEnabledInterface.php index 33aaed4..4723a6d 100644 --- a/src/StrategyEnabledInterface.php +++ b/src/Strategy/StrategyEnabledInterface.php @@ -7,19 +7,19 @@ declare(strict_types=1); -namespace Zend\Hydrator; +namespace Zend\Hydrator\Strategy; interface StrategyEnabledInterface { /** * Adds the given strategy under the given name. */ - public function addStrategy(string $name, Strategy\StrategyInterface $strategy) : void; + public function addStrategy(string $name, StrategyInterface $strategy) : void; /** * Gets the strategy with the given name. */ - public function getStrategy(string $name) : Strategy\StrategyInterface; + public function getStrategy(string $name) : StrategyInterface; /** * Checks if the strategy with the given name exists. From e199b0699aca55d55c699521f3b2c22b8ea40f78 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 13:23:38 -0600 Subject: [PATCH 2/7] Creates `FilterEnabledTrait` to implement `FilterEnabledInterface` Also modifies `AbstractHydrator` to use that instead. --- src/AbstractHydrator.php | 57 +----------------------- src/Filter/FilterEnabledTrait.php | 73 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 55 deletions(-) create mode 100644 src/Filter/FilterEnabledTrait.php diff --git a/src/AbstractHydrator.php b/src/AbstractHydrator.php index f9b931f..25acbad 100644 --- a/src/AbstractHydrator.php +++ b/src/AbstractHydrator.php @@ -19,6 +19,8 @@ abstract class AbstractHydrator implements Filter\FilterEnabledInterface, NamingStrategy\NamingStrategyEnabledInterface { + use Filter\FilterEnabledTrait; + /** * The list with strategies that this hydrator has. * @@ -33,13 +35,6 @@ abstract class AbstractHydrator implements */ protected $namingStrategy; - /** - * Composite to filter the methods, that need to be hydrated - * - * @var Filter\FilterComposite - */ - protected $filterComposite; - /** * Initializes a new instance of this class. */ @@ -185,54 +180,6 @@ public function getFilter() : Filter\FilterInterface return $this->filterComposite; } - /** - * Add a new filter to take care of what needs to be hydrated. - * To exclude e.g. the method getServiceLocator: - * - * - * $composite->addFilter("servicelocator", - * function ($property) { - * list($class, $method) = explode('::', $property); - * if ($method === 'getServiceLocator') { - * return false; - * } - * return true; - * }, FilterComposite::CONDITION_AND - * ); - * - * - * @param string $name Index in the composite - * @param callable|Filter\FilterInterface $filter - */ - public function addFilter(string $name, $filter, int $condition = Filter\FilterComposite::CONDITION_OR) : void - { - $this->filterComposite->addFilter($name, $filter, $condition); - } - - /** - * Check whether a specific filter exists at key $name or not - * - * @param string $name Index/name in the composite - */ - public function hasFilter(string $name) : bool - { - return $this->filterComposite->hasFilter($name); - } - - /** - * Remove a filter from the composition. - * - * To not extract "has" methods, unregister the filter. - * - * - * $filterComposite->removeFilter('has'); - * - */ - public function removeFilter(string $name) : void - { - $this->filterComposite->removeFilter($name); - } - /** * Adds the given naming strategy * diff --git a/src/Filter/FilterEnabledTrait.php b/src/Filter/FilterEnabledTrait.php new file mode 100644 index 0000000..29142ed --- /dev/null +++ b/src/Filter/FilterEnabledTrait.php @@ -0,0 +1,73 @@ + + * $composite->addFilter("servicelocator", + * function ($property) { + * list($class, $method) = explode('::', $property); + * if ($method === 'getServiceLocator') { + * return false; + * } + * return true; + * }, FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name Index in the composite + * @param callable|FilterInterface $filter + */ + public function addFilter(string $name, $filter, int $condition = FilterComposite::CONDITION_OR) : void + { + $this->filterComposite->addFilter($name, $filter, $condition); + } + + /** + * Check whether a specific filter exists at key $name or not + * + * @param string $name Index/name in the composite + */ + public function hasFilter(string $name) : bool + { + return $this->filterComposite->hasFilter($name); + } + + /** + * Remove a filter from the composition. + * + * To not extract "has" methods, unregister the filter. + * + * + * $filterComposite->removeFilter('has'); + * + */ + public function removeFilter(string $name) : void + { + $this->filterComposite->removeFilter($name); + } +} From a1272970b091b594819d8a12e1d5852c4ee30304 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 15:07:27 -0600 Subject: [PATCH 3/7] Adds StrategyEnabledTrait, for implementing StrategyEnabledInterface in hydrators This trait makes a few changes. First, `$strategies` is no longer an `ArrayObject`, and instead becomes an empty array by default; this means no additional initialization is required by implementors in order to be able to use it. Second, it checks that the current instance implements `NamingStrategyEnabledInterface` before attempting to call `hasNamingStrategy()` and `getNamingStrategy()`; this ensures that it can operate with or without a `NamingStrategyEnabledInterface` implementation when composed. Finally, it adapts `AbstractHydrator` to compose the trait, and remove its own implementation. --- src/AbstractHydrator.php | 84 +-------------------- src/Strategy/StrategyEnabledTrait.php | 101 ++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 83 deletions(-) create mode 100644 src/Strategy/StrategyEnabledTrait.php diff --git a/src/AbstractHydrator.php b/src/AbstractHydrator.php index 25acbad..40c08de 100644 --- a/src/AbstractHydrator.php +++ b/src/AbstractHydrator.php @@ -9,10 +9,6 @@ namespace Zend\Hydrator; -use ArrayObject; - -use function sprintf; - abstract class AbstractHydrator implements HydratorInterface, Strategy\StrategyEnabledInterface, @@ -20,13 +16,7 @@ abstract class AbstractHydrator implements NamingStrategy\NamingStrategyEnabledInterface { use Filter\FilterEnabledTrait; - - /** - * The list with strategies that this hydrator has. - * - * @var ArrayObject - */ - protected $strategies; + use Strategy\StrategyEnabledTrait; /** * An instance of NamingStrategy\NamingStrategyInterface @@ -40,81 +30,9 @@ abstract class AbstractHydrator implements */ public function __construct() { - $this->strategies = new ArrayObject(); $this->filterComposite = new Filter\FilterComposite(); } - /** - * Gets the strategy with the given name. - * - * @param string $name The name of the strategy to get. - * @throws Exception\InvalidArgumentException - */ - public function getStrategy(string $name) : Strategy\StrategyInterface - { - if (isset($this->strategies[$name])) { - return $this->strategies[$name]; - } - - if ($this->hasNamingStrategy() - && ($hydrated = $this->getNamingStrategy()->hydrate($name)) - && isset($this->strategies[$hydrated]) - ) { - return $this->strategies[$hydrated]; - } - - if (! isset($this->strategies['*'])) { - throw new Exception\InvalidArgumentException(sprintf( - '%s: no strategy by name of "%s", and no wildcard strategy present', - __METHOD__, - $name - )); - } - - return $this->strategies['*']; - } - - /** - * Checks if the strategy with the given name exists. - * - * @param string $name The name of the strategy to check for. - */ - public function hasStrategy(string $name) : bool - { - if ($this->strategies->offsetExists($name)) { - return true; - } - - if ($this->hasNamingStrategy() - && $this->strategies->offsetExists($this->getNamingStrategy()->hydrate($name)) - ) { - return true; - } - - return $this->strategies->offsetExists('*'); - } - - /** - * Adds the given strategy under the given name. - * - * @param string $name The name of the strategy to register. - * @param Strategy\StrategyInterface $strategy The strategy to register. - */ - public function addStrategy(string $name, Strategy\StrategyInterface $strategy) : void - { - $this->strategies[$name] = $strategy; - } - - /** - * Removes the strategy with the given name. - * - * @param string $name The name of the strategy to remove. - */ - public function removeStrategy(string $name) : void - { - unset($this->strategies[$name]); - } - /** * Converts a value for extraction. If no strategy exists the plain value is returned. * diff --git a/src/Strategy/StrategyEnabledTrait.php b/src/Strategy/StrategyEnabledTrait.php new file mode 100644 index 0000000..4e190b3 --- /dev/null +++ b/src/Strategy/StrategyEnabledTrait.php @@ -0,0 +1,101 @@ +strategies[$name] = $strategy; + } + + /** + * Gets the strategy with the given name. + * + * @param string $name The name of the strategy to get. + * @throws Exception\InvalidArgumentException + */ + public function getStrategy(string $name) : StrategyInterface + { + if (isset($this->strategies[$name])) { + return $this->strategies[$name]; + } + + if ($this instanceof NamingStrategyEnabledInterface + && $this->hasNamingStrategy() + && ($hydrated = $this->getNamingStrategy()->hydrate($name)) + && isset($this->strategies[$hydrated]) + ) { + return $this->strategies[$hydrated]; + } + + if (! isset($this->strategies['*'])) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: no strategy by name of "%s", and no wildcard strategy present', + __METHOD__, + $name + )); + } + + return $this->strategies['*']; + } + + /** + * Checks if the strategy with the given name exists. + * + * @param string $name The name of the strategy to check for. + */ + public function hasStrategy(string $name) : bool + { + if (isset($this->strategies[$name])) { + return true; + } + + if ($this instanceof NamingStrategyEnabledInterface + && $this->hasNamingStrategy() + && isset($this->strategies[$this->getNamingStrategy()->hydrate($name)]) + ) { + return true; + } + + return array_key_exists('*', $this->strategies); + } + + /** + * Removes the strategy with the given name. + * + * @param string $name The name of the strategy to remove. + */ + public function removeStrategy(string $name) : void + { + unset($this->strategies[$name]); + } +} From 29ea01b52325e059b7b96de05d85c9e32b728f6e Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 15:16:11 -0600 Subject: [PATCH 4/7] Adds NamingStrategyEnabledTrait to provide a NamingStrategyEnabledInterface implementation Extracts the various NamingStrategyEnabledInterface method implementations from AbstractHydrator into a new NamingStrategyEnabledTrait, and then modifies AbstractHydrator to use the new trait to provide its NamingStrategyEnabledInterface implementation --- src/AbstractHydrator.php | 50 +-------------- .../NamingStrategyEnabledInterface.php | 4 +- .../NamingStrategyEnabledTrait.php | 61 +++++++++++++++++++ 3 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 src/NamingStrategy/NamingStrategyEnabledTrait.php diff --git a/src/AbstractHydrator.php b/src/AbstractHydrator.php index 40c08de..8e5fa4c 100644 --- a/src/AbstractHydrator.php +++ b/src/AbstractHydrator.php @@ -16,15 +16,9 @@ abstract class AbstractHydrator implements NamingStrategy\NamingStrategyEnabledInterface { use Filter\FilterEnabledTrait; + use NamingStrategy\NamingStrategyEnabledTrait; use Strategy\StrategyEnabledTrait; - /** - * An instance of NamingStrategy\NamingStrategyInterface - * - * @var null|NamingStrategy\NamingStrategyInterface - */ - protected $namingStrategy; - /** * Initializes a new instance of this class. */ @@ -97,46 +91,4 @@ public function getFilter() : Filter\FilterInterface { return $this->filterComposite; } - - /** - * Adds the given naming strategy - * - * @param NamingStrategy\NamingStrategyInterface $strategy The naming to register. - */ - public function setNamingStrategy(NamingStrategy\NamingStrategyInterface $strategy) : void - { - $this->namingStrategy = $strategy; - } - - /** - * Gets the naming strategy. - * - * If no naming strategy is registered, registers the - * `IdentityNamingStrategy`, which acts essentially as a no-op. - * - * {@inheritDoc} - */ - public function getNamingStrategy() : NamingStrategy\NamingStrategyInterface - { - if (null === $this->namingStrategy) { - $this->namingStrategy = new NamingStrategy\IdentityNamingStrategy(); - } - return $this->namingStrategy; - } - - /** - * Checks if a naming strategy exists. - */ - public function hasNamingStrategy() : bool - { - return isset($this->namingStrategy); - } - - /** - * Removes the naming strategy - */ - public function removeNamingStrategy() : void - { - $this->namingStrategy = null; - } } diff --git a/src/NamingStrategy/NamingStrategyEnabledInterface.php b/src/NamingStrategy/NamingStrategyEnabledInterface.php index a50ca5c..684c079 100644 --- a/src/NamingStrategy/NamingStrategyEnabledInterface.php +++ b/src/NamingStrategy/NamingStrategyEnabledInterface.php @@ -12,7 +12,7 @@ interface NamingStrategyEnabledInterface { /** - * Adds the given naming strategy + * Sets the naming strategy. */ public function setNamingStrategy(NamingStrategyInterface $strategy) : void; @@ -27,7 +27,7 @@ public function getNamingStrategy() : NamingStrategyInterface; public function hasNamingStrategy() : bool; /** - * Removes the naming with the given name. + * Removes the naming strategy. */ public function removeNamingStrategy() : void; } diff --git a/src/NamingStrategy/NamingStrategyEnabledTrait.php b/src/NamingStrategy/NamingStrategyEnabledTrait.php new file mode 100644 index 0000000..bf4fd41 --- /dev/null +++ b/src/NamingStrategy/NamingStrategyEnabledTrait.php @@ -0,0 +1,61 @@ +namingStrategy = $strategy; + } + + /** + * Gets the naming strategy. + * + * If no naming strategy is registered, registers the + * `IdentityNamingStrategy`, which acts essentially as a no-op. + */ + public function getNamingStrategy() : NamingStrategyInterface + { + if (null === $this->namingStrategy) { + $this->namingStrategy = new IdentityNamingStrategy(); + } + return $this->namingStrategy; + } + + /** + * Checks if a naming strategy exists. + */ + public function hasNamingStrategy() : bool + { + return isset($this->namingStrategy); + } + + /** + * Removes the naming strategy. + */ + public function removeNamingStrategy() : void + { + $this->namingStrategy = null; + } +} From e1aa0601e9063ecd7867eb41ef8bad924acc2ef6 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 15:37:47 -0600 Subject: [PATCH 5/7] Implement getFilter method in FilterEnabledTrait Since FilterEnabledInterface extends FilterProviderInterface, the trait also needs to implement the `getFilter()` method. This allows us to remove all initialization out of the constructor, which makes the traits simply drop-in functionality. Interestingly, this caused some problems. The assumption from `getFilter()` is only that you have a `FilterInterface` instance; however, we expect a `FilterComposite` instance, and phpstan flags this as a problem when we start calling `FilterComposite` methods on something that may or may not define those methods. To get around this, I have `ClassMethods` and the individual methods of `FilterEnabledTrait` call `getFilter()`, and then use the `$filterComposite` property directly. As this is typehinted as `FilterComposite`, all warnings go away. --- src/AbstractHydrator.php | 16 ------------ src/ClassMethods.php | 8 +++--- src/Filter/FilterEnabledInterface.php | 7 ++---- src/Filter/FilterEnabledTrait.php | 35 +++++++++++++++++++++------ src/Reflection.php | 2 +- 5 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/AbstractHydrator.php b/src/AbstractHydrator.php index 8e5fa4c..babc8c9 100644 --- a/src/AbstractHydrator.php +++ b/src/AbstractHydrator.php @@ -19,14 +19,6 @@ abstract class AbstractHydrator implements use NamingStrategy\NamingStrategyEnabledTrait; use Strategy\StrategyEnabledTrait; - /** - * Initializes a new instance of this class. - */ - public function __construct() - { - $this->filterComposite = new Filter\FilterComposite(); - } - /** * Converts a value for extraction. If no strategy exists the plain value is returned. * @@ -83,12 +75,4 @@ public function hydrateName(string $name, ?array $data = null) : string ? $this->getNamingStrategy()->hydrate($name, $data) : $name; } - - /** - * Get the filter instance - */ - public function getFilter() : Filter\FilterInterface - { - return $this->filterComposite; - } } diff --git a/src/ClassMethods.php b/src/ClassMethods.php index b7ba4ed..422d637 100644 --- a/src/ClassMethods.php +++ b/src/ClassMethods.php @@ -65,13 +65,15 @@ class ClassMethods extends AbstractHydrator implements HydratorOptionsInterface */ public function __construct(bool $underscoreSeparatedKeys = true, bool $methodExistsCheck = false) { - parent::__construct(); - $this->setUnderscoreSeparatedKeys($underscoreSeparatedKeys); $this->setMethodExistsCheck($methodExistsCheck); $this->callableMethodFilter = new Filter\OptionalParametersFilter(); + // Ensure we have initialized the composite filter + $this->getFilter(); + + // Add the default filters we will use $this->filterComposite->addFilter('is', new Filter\IsFilter()); $this->filterComposite->addFilter('has', new Filter\HasFilter()); $this->filterComposite->addFilter('get', new Filter\GetFilter()); @@ -149,7 +151,7 @@ public function extract(object $object) : array // pass 1 - finding out which properties can be extracted, with which methods (populate hydration cache) if (! isset($this->extractionMethodsCache[$objectClass])) { $this->extractionMethodsCache[$objectClass] = []; - $filter = $this->filterComposite; + $filter = $this->getFilter(); $methods = get_class_methods($object); if ($object instanceof Filter\FilterProviderInterface) { diff --git a/src/Filter/FilterEnabledInterface.php b/src/Filter/FilterEnabledInterface.php index e52d7b4..103adb2 100644 --- a/src/Filter/FilterEnabledInterface.php +++ b/src/Filter/FilterEnabledInterface.php @@ -19,11 +19,8 @@ interface FilterEnabledInterface extends FilterProviderInterface * $composite->addFilter( * "servicelocator", * function ($property) { - * list($class, $method) = explode('::', $property); - * if ($method === 'getServiceLocator') { - * return false; - * } - * return true; + * [$class, $method] = explode('::', $property, 2); + * return $method !== 'getServiceLocator'; * }, * FilterComposite::CONDITION_AND * ); diff --git a/src/Filter/FilterEnabledTrait.php b/src/Filter/FilterEnabledTrait.php index 29142ed..50076ff 100644 --- a/src/Filter/FilterEnabledTrait.php +++ b/src/Filter/FilterEnabledTrait.php @@ -15,9 +15,11 @@ trait FilterEnabledTrait { /** - * Composite to filter the methods, that need to be hydrated - * - * The class composing this trait should set the value on this property. + * Composite filter for the purpose of determining if a property should be + * extracted or hydrated. A FilterComposite is specified to allow the + * ability to compose many filters, allowing each to inspect specific + * properties or to aggregate multiple criteria when determining if a + * property can be addressed. * * @var FilterComposite */ @@ -30,11 +32,8 @@ trait FilterEnabledTrait * * $composite->addFilter("servicelocator", * function ($property) { - * list($class, $method) = explode('::', $property); - * if ($method === 'getServiceLocator') { - * return false; - * } - * return true; + * [$class, $method] = explode('::', $property, 2); + * return $method !== 'getServiceLocator'; * }, FilterComposite::CONDITION_AND * ); * @@ -44,6 +43,8 @@ trait FilterEnabledTrait */ public function addFilter(string $name, $filter, int $condition = FilterComposite::CONDITION_OR) : void { + // Ensure we have initialized the composite filter + $this->getFilter(); $this->filterComposite->addFilter($name, $filter, $condition); } @@ -54,6 +55,8 @@ public function addFilter(string $name, $filter, int $condition = FilterComposit */ public function hasFilter(string $name) : bool { + // Ensure we have initialized the composite filter + $this->getFilter(); return $this->filterComposite->hasFilter($name); } @@ -68,6 +71,22 @@ public function hasFilter(string $name) : bool */ public function removeFilter(string $name) : void { + // Ensure we have initialized the composite filter + $this->getFilter(); $this->filterComposite->removeFilter($name); } + + /** + * Get the filter instance + * + * @return FilterComposite + */ + public function getFilter() : FilterInterface + { + if (! isset($this->filterComposite)) { + $this->filterComposite = new FilterComposite(); + } + + return $this->filterComposite; + } } diff --git a/src/Reflection.php b/src/Reflection.php index 25ed6c6..ab4e519 100644 --- a/src/Reflection.php +++ b/src/Reflection.php @@ -33,7 +33,7 @@ public function extract(object $object) : array $result = []; foreach (self::getReflProperties($object) as $property) { $propertyName = $this->extractName($property->getName(), $object); - if (! $this->filterComposite->filter($propertyName)) { + if (! $this->getFilter()->filter($propertyName)) { continue; } From 3b7fb81f0dfd4d6b2d624f6c84bea4121fd98dfd Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 16:33:22 -0600 Subject: [PATCH 6/7] Documents new `*EnabledTrait` implementations Documents each of the `FilterEnabledInterface` and related trait, `StrategyEnabledInterface` and related trait, and `NamingStrategyEnabledInterface` and related trait. In doing so, it also: - Updates and re-organizes the filter documentation significantly to detail filter composition, filter providers, filter-enabled hydrators, etc. - Adds an introductory chapter for naming strategies, detailing their use case, and how to use them in your hydrators. --- docs/book/v3/filter.md | 241 ++++++++++++++++---------- docs/book/v3/naming-strategy/intro.md | 88 ++++++++++ docs/book/v3/strategy.md | 101 ++++++----- mkdocs.yml | 1 + 4 files changed, 289 insertions(+), 142 deletions(-) create mode 100644 docs/book/v3/naming-strategy/intro.md diff --git a/docs/book/v3/filter.md b/docs/book/v3/filter.md index 679c683..097801b 100644 --- a/docs/book/v3/filter.md +++ b/docs/book/v3/filter.md @@ -15,13 +15,9 @@ namespace Zend\Hydrator\Filter; interface FilterInterface { /** - * Should return true, if the given filter - * does not match - * - * @param string $property The name of the property - * @return bool + * Should return true, if the given filter does not match. */ - public function filter($property); + public function filter(string $property) : bool; } ``` @@ -66,97 +62,28 @@ parameters, simply add the number to the constructor. The default value is 0. If the method has more or fewer parameters than what the filter accepts, it will be omitted. -## Remove filters - -If you want to tell e.g. the `ClassMethods` hydrator, to not extract methods that start with `is`, -remove the related filter: - -```php -$hydrator = new ClassMethods(false); -$hydrator->removeFilter('is'); -``` - -After performing the above, the key/value pairs for `is` methods will not end up -in your extracted array anymore. The filters can be used in any hydrator, but -the `ClassMethods` hydrator is the only one, that has pre-registered filters: - -```php -$this->filterComposite->addFilter('is', new IsFilter()); -$this->filterComposite->addFilter('has', new HasFilter()); -$this->filterComposite->addFilter('get', new GetFilter()); -$this->filterComposite->addFilter( - 'parameter', - new NumberOfParameterFilter(), - FilterComposite::CONDITION_AND -); -``` - -If these are not appropriate for your object, you can unregister them as shown -in the previous example. - -## Add filters - -You can add filters to any hydrator that extends the `AbstractHydrator`. Filters -can either implement `FilterInterface`, or simply be PHP callables: - -```php -$hydrator->addFilter('len', function($property) { - if (strlen($property) !== 3) { - return false; - } - return true; -}); -``` - -By default, every filter you add will be added with a conditional `or`. If you -want to add it with `and` (as the `NumberOfParameterFilter` that is added to the -`ClassMethods` hydrator by default), provide the conditon as the third argument -to `addFilter`: - -```php -$hydrator->addFilter('len', function($property) { - if (strlen($property) !== 3) { - return false; - } - return true; -}, FilterComposite::CONDITION_AND); -``` - -One common use case for filters is to omit getters for values that you do not -want to represent, such as a service manager instance: - -```php -$hydrator->addFilter( - 'servicemanager', - new MethodMatchFilter('getServiceManager'), - FilterComposite::CONDITION_AND -); -``` - -The example above will exclude the `getServiceManager()` method and the -`servicemanager` key from extraction, even if the `get` filter wants to add it. - ## Use FilterComposite for complex filters -`FilterComposite` implements `FilterInterface` as well, so you can add it as -a regular filter to the hydrator. One benefit of this implementation is that you +`FilterComposite` implements `FilterInterface` as well, so you can add it as a +regular filter to the hydrator. One benefit of this implementation is that you can add the filters with a condition and accomplish complex requirements using -different composites with different conditions. You can pass the following +different filters with different conditions. You can pass the following conditions to the 3rd parameter, when you add a filter: ### Zend\\Hydrator\\Filter\\FilterComposite::CONDITION\_OR -At the given level of the composite, at least one filter in that condition block -has to return true to extract the value. +At the given level of the composite, at least one filter set using +`CONDITION_OR` must return true to extract the value. ### Zend\\Hydrator\\Filter\\FilterComposite::CONDITION\_AND -At the given level of the composite, all filters in that condition block must -return true to extract the value. +At the given level of the composite, **all** filters set using `CONDITION_AND` +must return true to extract the value. ### FilterComposite Examples -This composition will have a similar logic as the if below: +To illustrate how conditions apply when composing filters, consider the +following set of filters: ```php $composite = new FilterComposite(); @@ -166,7 +93,11 @@ $composite->addFilter('two', $condition2); $composite->addFilter('three', $condition3); $composite->addFilter('four', $condition4, FilterComposite::CONDITION_AND); $composite->addFilter('five', $condition5, FilterComposite::CONDITION_AND); +``` +The above is roughly equivalent to the following conditional: + +``` // This is what's happening internally if ( ($condition1 @@ -176,12 +107,12 @@ if ( && $condition5 ) ) { - //do extraction + // do extraction } ``` -If you only have one condition (e.g., only an `and` or `or`) block, the other -one will be completely ignored. +If you only have one condition block (e.g., only `AND` or `OR` filters), the +other condition type will be completely ignored. A bit more complex filter can look like this: @@ -201,7 +132,7 @@ $composite->addFilter( $hydrator->addFilter('excludes', $composite, FilterComposite::CONDITION_AND); // Internal -if (( // default composite inside the hydrator +if (( // default composite inside the ClassMethods hydrator: ($getFilter || $hasFilter || $isFilter @@ -222,8 +153,8 @@ extracted, except for `getServiceManager()` and `getEventManager()`. ## Using the provider interface -`FilterProviderInterface` allows you to configure the behavior of the hydrator -inside your objects. +`Zend\Hydrator\Filter\FilterProviderInterface` allows you to configure the +behavior of the hydrator inside your objects. ```php namespace Zend\Hydrator\Filter; @@ -301,4 +232,132 @@ excluded from extraction. > ### Note > > All pre-registered filters from the `ClassMethods` hydrator are ignored when -> this interface is used. +> this interface is used. More on those methods below. + +## Filter-enabled hydrators and the composite filter + +Hydrators can indicate they are filter-enabled by implementing +`Zend\Hydrator\Filter\FilterEnabledInterface`: + +```php +namespace Zend\Hydrator\Filter; + +interface FilterEnabledInterface extends FilterProviderInterface +{ + /** + * Add a new filter to take care of what needs to be hydrated. + * To exclude e.g. the method getServiceLocator: + * + * + * $composite->addFilter( + * "servicelocator", + * function ($property) { + * [$class, $method] = explode('::', $property, 2); + * return $method !== 'getServiceLocator'; + * }, + * FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name Index in the composite + * @param callable|FilterInterface $filter + */ + public function addFilter(string $name, $filter, int $condition = FilterComposite::CONDITION_OR) : void; + + /** + * Check whether a specific filter exists at key $name or not + * + * @param string $name Index in the composite + */ + public function hasFilter(string $name) : bool; + + /** + * Remove a filter from the composition. + * + * To not extract "has" methods, you simply need to unregister it + * + * + * $filterComposite->removeFilter('has'); + * + */ + public function removeFilter(string $name) : void; +} +``` + +> Note that the interface extends `FilterProviderInterface`, which means it also +> includes the `getFilter()` method. + +The `FilterEnabledInterface` makes the assumption that the class will be backed +by a `Zend\Hydrator\Filter\FilterComposite`; the various `addFilter()`, +`hasFilter()`, and `removeFilter()` methods proxy to a `FilterComposite` +instance. + +We provide a default implementation of the `FilterEnabledInterface` via the +trait `Zend\Hydrator\Filter\FilterEnabledTrait`. That trait defines the +`protected` property `$filterComposite`, and lazy-loads the value via +`getFilter()`. All method it defines then operate on that property. + +`AbstractHydrator`, on which all the hydrators shipped in this package are +built, implements `FilterEnabledInterface`, via the `FilterEnabledTrait`. Of the +hydrators shipped, only one, `ClassMethods`, defines any filters from the +outset. Its constructor includes the following: + +```php +$this->filterComposite->addFilter('is', new IsFilter()); +$this->filterComposite->addFilter('has', new HasFilter()); +$this->filterComposite->addFilter('get', new GetFilter()); +$this->filterComposite->addFilter( + 'parameter', + new NumberOfParameterFilter(), + FilterComposite::CONDITION_AND +); +``` + +### Remove filters + +If you want to tell a filter-enabled hydrator such as `ClassMethods` not to +extract methods that start with `is`, remove the related filter: + +```php +$hydrator = new ClassMethods(false); +$hydrator->removeFilter('is'); +``` + +After performing the above, the key/value pairs for `is` methods will no longer +end up in your extracted array. + +### Add filters + +You can add filters using the `addFilter()` method. Filters can either implement +`FilterInterface`, or simply be PHP callables: + +```php +$hydrator->addFilter('len', function($property) { + return strlen($property) === 3; +}); +``` + +By default, every filter you add will be added with a conditional `OR`. If you +want to add it with `AND` (such as the `ClassMethods` hydrator does with its +composed `NumberOfParameterFilter`, demonstrated above) provide the conditon as +the third argument to `addFilter`: + +```php +$hydrator->addFilter('len', function($property) { + return strlen($property) === 3; +}, FilterComposite::CONDITION_AND); +``` + +One common use case for filters is to omit getters for values that you do not +want to represent, such as a service manager instance: + +```php +$hydrator->addFilter( + 'servicemanager', + new MethodMatchFilter('getServiceManager'), + FilterComposite::CONDITION_AND +); +``` + +The example above will exclude the `getServiceManager()` method and the +`servicemanager` key from extraction, even if the `get` filter wants to add it. diff --git a/docs/book/v3/naming-strategy/intro.md b/docs/book/v3/naming-strategy/intro.md new file mode 100644 index 0000000..db54e0a --- /dev/null +++ b/docs/book/v3/naming-strategy/intro.md @@ -0,0 +1,88 @@ +# Naming Strategies + +Sometimes, the representation of a property should not share the same name as +the property itself. As an example, when serializing an object for a JSON +payload, you may want to convert camelCase properties to underscore_separated +properties, and vice versa when deserializing JSON to an object. + +To make that possible, zend-hydrator provides _naming strategies_. These are +similar to [strategies](../strategies.md), but instead of operating on the +_value_, they operate on the _name_. + +## NamingStrategyInterface + +Naming strategies implement `Zend\Hydrator\NamingStrategy\NamingStrategyInterface`: + +```php +namespace Zend\Hydrator\NamingStrategy; + +/** + * Allow property extraction / hydration for hydrator + */ +interface NamingStrategyInterface +{ + /** + * Converts the given name so that it can be extracted by the hydrator. + * + * @param null|mixed[] $data The original data for context. + */ + public function hydrate(string $name, ?array $data = null) : string; + + /** + * Converts the given name so that it can be hydrated by the hydrator. + * + * @param null|object $object The original object for context. + */ + public function extract(string $name, ?object $object = null) : string; +} +``` + +## Providing naming strategies + +Hydrators can indicate they will consume naming strategies, as well as allow +registration of them, by implementing `Zend\Hydrator\NamingStrategy\NamingStrategyEnabledInterface`: + +```php +namespace Zend\Hydrator\NamingStrategy; + +interface NamingStrategyEnabledInterface +{ + /** + * Sets the naming strategy. + */ + public function setNamingStrategy(NamingStrategyInterface $strategy) : void; + + /** + * Gets the naming strategy. + */ + public function getNamingStrategy() : NamingStrategyInterface; + + /** + * Checks if a naming strategy exists. + */ + public function hasNamingStrategy() : bool; + + /** + * Removes the naming strategy. + */ + public function removeNamingStrategy() : void; +} +``` + +We provide a default implementation of this interface via +`Zend\Hydrator\NamingStrategy\NamingStrategyEnabledTrait`. Its +`getNamingStrategy()` will lazy-load an `IdentityNamingStrategy` if none has +been previously registered. + +`AbstractHydrator` uses `NamingStategyEnabledTrait` to implement +`NamingStrategyEnabledInterface`, and thus all shipped hydrators can consume +naming strategies. + +## Shipped naming strategies + +We provide the following naming strategies: + +- [CompositeNamingStrategy](composite-naming-strategy.md) +- [IdentityNamingStrategy](identity-naming-strategy.md) +- [MapNamingStrategy](map-naming-strategy.md) +- [UnderscoreNamingStrategy](underscore-naming-strategy.md) diff --git a/docs/book/v3/strategy.md b/docs/book/v3/strategy.md index 167e4e5..a57d057 100644 --- a/docs/book/v3/strategy.md +++ b/docs/book/v3/strategy.md @@ -1,87 +1,86 @@ # Zend\\Hydrator\\Strategy -You can add `Zend\Hydrator\Strategy\StrategyInterface` to any of the hydrators -(except if it extends `Zend\Hydrator\AbstractHydrator` or implements -`Zend\Hydrator\HydratorInterface` and `Zend\Hydrator\Strategy\StrategyEnabledInterface`) -to manipulate the way how they behave on `extract()` and `hydrate()` for -specific key / value pairs. This is the interface that needs to be implemented: +You can compose `Zend\Hydrator\Strategy\StrategyInterface` instances in any of +the hydrators to manipulate the way they behave on `extract()` and `hydrate()` +for specific key/value pairs. The interface offers the following definitions: ```php namespace Zend\Hydrator\Strategy; interface StrategyInterface { - /** - * Converts the given value so that it can be extracted by the hydrator. - * - * @param mixed $value The original value. - * @return mixed Returns the value that should be extracted. - */ - public function extract($value); - - /** - * Converts the given value so that it can be hydrated by the hydrator. - * - * @param mixed $value The original value. - * @return mixed Returns the value that should be hydrated. - */ - public function hydrate($value); + /** + * Converts the given value so that it can be extracted by the hydrator. + * + * @param mixed $value The original value. + * @param null|object $object (optional) The original object for context. + * @return mixed Returns the value that should be extracted. + */ + public function extract($value, ?object $object = null); + + /** + * Converts the given value so that it can be hydrated by the hydrator. + * + * @param mixed $value The original value. + * @param null|array $data (optional) The original data for context. + * @return mixed Returns the value that should be hydrated. + */ + public function hydrate($value, ?array $data = null); } ``` -This interface is similar to `Zend\Hydrator\HydratorInterface`; the reason -is that strategies provide a proxy implementation for `hydrate()` and `extract()`. +This interface is similar to what the `Zend\Hydrator\ExtractionInterface` and +`Zend\Hydrator\HydrationInterface` provide; the reason is that strategies +provide a proxy implementation for `hydrate()` and `extract()` on individual +values. For this reason, their return types are listed as mixed, versus as +`array` and `object`, respectively. ## Adding strategies to the hydrators -To allow strategies within your hydrator, `Zend\Hydrator\Strategy\StrategyEnabledInterface` -provides the following methods: +This package provides the interface `Zend\Hydrator\Strategy\StrategyEnabledInterface`. +Hydrators can implement this interface, and then call on its `getStrategy()` +method in order to extract or hydrate individual values. The interface has the +following definition: ```php namespace Zend\Hydrator\Strategy; -use Zend\Hydrator\Strategy\StrategyInterface; - interface StrategyEnabledInterface { /** * Adds the given strategy under the given name. - * - * @param string $name The name of the strategy to register. - * @param StrategyInterface $strategy The strategy to register. - * @return HydratorInterface */ - public function addStrategy($name, StrategyInterface $strategy); + public function addStrategy(string $name, StrategyInterface $strategy) : void; /** * Gets the strategy with the given name. - * - * @param string $name The name of the strategy to get. - * @return StrategyInterface */ - public function getStrategy($name); + public function getStrategy(string $name) : StrategyInterface; /** * Checks if the strategy with the given name exists. - * - * @param string $name The name of the strategy to check for. - * @return bool */ - public function hasStrategy($name); + public function hasStrategy(string $name) : bool; /** * Removes the strategy with the given name. - * - * @param string $name The name of the strategy to remove. - * @return HydratorInterface */ - public function removeStrategy($name); + public function removeStrategy(string $name) : void; } ``` -Every hydrator shipped by default provides this functionality; -`AbstractHydrator` fully implements it as well. As such, if you want to use this -functionality in your own hydrators, you should extend `AbstractHydrator`. +We provide a default implementation of the interface via +`Zend\Hydrator\Strategy\StrategyEnabledTrait`; it uses an array property to +store and retrieve strategies by name. The `AbstractHydrator`, on which all +shipped hydrators are based, uses this trait to provide an implementation of +`StrategyEnabledInterface`. Internally, `AbstractHydrator` creates the methods +`extractValue()` and `hydrateValue()`, which proxy to the strategy corresponding +to the value if present. + +Additionally, the `StrategyEnabledTrait` will check to see if the class +composing it also implements `Zend\Hydrator\NamingStrategy\NamingStrategyEnabledInterface`, +and use the composed naming strategy, if present, to translate the property name +prior to looking up a strategy for it. ## Available implementations @@ -104,11 +103,11 @@ This is a strategy that allows you to pass in options for: and DateTime instances. The input and output formats can be provided as constructor arguments. -As of version 2.4.1, this strategy now allows `DateTime` formats that use `!` to -prepend the format, or `|` or `+` to append it; these ensure that, during -hydration, the new `DateTime` instance created will set the time element -accordingly. As a specific example, `Y-m-d|` will drop the time component, -ensuring comparisons are based on a midnight time value. +The strategy allows `DateTime` formats that use `!` to prepend the format, or +`|` or `+` to append it; these ensure that, during hydration, the new `DateTime` +instance created will set the time element accordingly. As a specific example, +`Y-m-d|` will drop the time component, ensuring comparisons are based on a +midnight time value. ### Zend\\Hydrator\\Strategy\\DefaultStrategy diff --git a/mkdocs.yml b/mkdocs.yml index f485870..b494322 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,7 @@ pages: - "Strategies": v3/strategy.md - "Aggregates": v3/aggregate.md - "Naming Strategies": + - "Introduction": v3/naming-strategy/intro.md - "Identity": v3/naming-strategy/identity-naming-strategy.md - "Mapping": v3/naming-strategy/map-naming-strategy.md - "Underscore Mapping": v3/naming-strategy/underscore-naming-strategy.md From 7c83ac8c62bf75eb18b27c682462b43681dc954a Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 29 Nov 2018 16:42:10 -0600 Subject: [PATCH 7/7] Split out FilterProviderTrait from FilterEnabledTrait Separates out the `FilterProviderInterface` implementation from `FilterEnabledTrait`, creating a new trait, `FilterProviderTrait`. `FilterEnabledTrait` now uses that internally. --- docs/book/v3/filter.md | 106 ++++++++++++++++++----------- src/Filter/FilterEnabledTrait.php | 27 +------- src/Filter/FilterProviderTrait.php | 41 +++++++++++ 3 files changed, 109 insertions(+), 65 deletions(-) create mode 100644 src/Filter/FilterProviderTrait.php diff --git a/docs/book/v3/filter.md b/docs/book/v3/filter.md index 097801b..bb60b15 100644 --- a/docs/book/v3/filter.md +++ b/docs/book/v3/filter.md @@ -179,47 +179,47 @@ For example: ```php Class Foo implements FilterProviderInterface { - public function getFoo() - { - return 'foo'; - } - - public function hasFoo() - { - return true; - } - - public function getServiceManager() - { - return 'servicemanager'; - } - - public function getEventManager() - { - return 'eventmanager'; - } - - public function getFilter() - { - $composite = new FilterComposite(); - $composite->addFilter('get', new GetFilter()); - - $exclusionComposite = new FilterComposite(); - $exclusionComposite->addFilter( - 'servicemanager', - new MethodMatchFilter('getServiceManager'), - FilterComposite::CONDITION_AND - ); - $exclusionComposite->addFilter( - 'eventmanager', - new MethodMatchFilter('getEventManager'), - FilterComposite::CONDITION_AND - ); - - $composite->addFilter('excludes', $exclusionComposite, FilterComposite::CONDITION_AND); - - return $composite; - } + public function getFoo() + { + return 'foo'; + } + + public function hasFoo() + { + return true; + } + + public function getServiceManager() + { + return 'servicemanager'; + } + + public function getEventManager() + { + return 'eventmanager'; + } + + public function getFilter() + { + $composite = new FilterComposite(); + $composite->addFilter('get', new GetFilter()); + + $exclusionComposite = new FilterComposite(); + $exclusionComposite->addFilter( + 'servicemanager', + new MethodMatchFilter('getServiceManager'), + FilterComposite::CONDITION_AND + ); + $exclusionComposite->addFilter( + 'eventmanager', + new MethodMatchFilter('getEventManager'), + FilterComposite::CONDITION_AND + ); + + $composite->addFilter('excludes', $exclusionComposite, FilterComposite::CONDITION_AND); + + return $composite; + } } $hydrator = new ClassMethods(false); @@ -234,6 +234,30 @@ excluded from extraction. > All pre-registered filters from the `ClassMethods` hydrator are ignored when > this interface is used. More on those methods below. +### FilterProviderTrait + +We provide `Zend\Hydrator\Filter\FilterProviderTrait` as a default +implementation of `FilterProviderInterface`. It lazy-loads a `FilterComposite` +instance and returns it. This allows you to define filters in your constructor, +or to allow others to define composite filters for specific instances: + +```php +// Within a constructor: +$this->getFilter()->addFilter( + 'servicemanager', + new MethodMatchFilter('getServiceManager'), + FilterComposite::CONDITION_AND +); + +// By a consumer: +$hydrator = new SomeFilterEnabledHydrator(); +$hydrator->getFilter()->addFilter( + 'servicemanager', + new MethodMatchFilter('getServiceManager'), + FilterComposite::CONDITION_AND +); +``` + ## Filter-enabled hydrators and the composite filter Hydrators can indicate they are filter-enabled by implementing diff --git a/src/Filter/FilterEnabledTrait.php b/src/Filter/FilterEnabledTrait.php index 50076ff..6020e57 100644 --- a/src/Filter/FilterEnabledTrait.php +++ b/src/Filter/FilterEnabledTrait.php @@ -14,16 +14,9 @@ */ trait FilterEnabledTrait { - /** - * Composite filter for the purpose of determining if a property should be - * extracted or hydrated. A FilterComposite is specified to allow the - * ability to compose many filters, allowing each to inspect specific - * properties or to aggregate multiple criteria when determining if a - * property can be addressed. - * - * @var FilterComposite - */ - protected $filterComposite; + // FilterEnabledInterface extends FilterProviderInterface; we will use + // the FilterProviderTrait to implement that part of the interface. + use FilterProviderTrait; /** * Add a new filter to take care of what needs to be hydrated. @@ -75,18 +68,4 @@ public function removeFilter(string $name) : void $this->getFilter(); $this->filterComposite->removeFilter($name); } - - /** - * Get the filter instance - * - * @return FilterComposite - */ - public function getFilter() : FilterInterface - { - if (! isset($this->filterComposite)) { - $this->filterComposite = new FilterComposite(); - } - - return $this->filterComposite; - } } diff --git a/src/Filter/FilterProviderTrait.php b/src/Filter/FilterProviderTrait.php new file mode 100644 index 0000000..c2b8f31 --- /dev/null +++ b/src/Filter/FilterProviderTrait.php @@ -0,0 +1,41 @@ +filterComposite)) { + $this->filterComposite = new FilterComposite(); + } + + return $this->filterComposite; + } +}