From 0de033622bb94266a9ac30809db342daaab11091 Mon Sep 17 00:00:00 2001 From: fsevestre Date: Tue, 7 Mar 2023 10:55:10 +0100 Subject: [PATCH] Allow applying further filters with ChainableFilter --- README.md | 35 ++++++++++++++++++++++++- fixtures/f013/A.php | 24 +++++++++++++++++ fixtures/f013/B.php | 34 ++++++++++++++++++++++++ fixtures/f013/C.php | 13 +++++++++ src/DeepCopy/DeepCopy.php | 5 ++++ src/DeepCopy/Filter/ChainableFilter.php | 24 +++++++++++++++++ tests/DeepCopyTest/DeepCopyTest.php | 34 ++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 fixtures/f013/A.php create mode 100644 fixtures/f013/B.php create mode 100644 fixtures/f013/C.php create mode 100644 src/DeepCopy/Filter/ChainableFilter.php diff --git a/README.md b/README.md index 503e93d..94aaa06 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,9 @@ $matcher = new TypeMatcher('Doctrine\Common\Collections\Collection'); - `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher` - `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher` +By design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied). +Using the ([`ChainableFilter`](#chainablefilter-filter)) won't stop the chain of filters. + #### `SetNullFilter` (filter) @@ -226,6 +229,34 @@ $copy = $copier->copy($object); ``` +#### `ChainableFilter` (filter) + +If you use cloning on proxy classes, you might want to apply two filters for: +1. loading the data +2. applying a transformation + +You can use the `ChainableFilter` as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e. +the next ones may be applied). + + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\ChainableFilter; +use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; +use DeepCopy\Filter\SetNullFilter; +use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; +use DeepCopy\Matcher\PropertyNameMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); +$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); + +$copy = $copier->copy($object); + +echo $copy->id; // null +``` + + #### `DoctrineCollectionFilter` (filter) If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`: @@ -268,6 +299,8 @@ Doctrine proxy class (...\\\_\_CG\_\_\Proxy). You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class. **Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded before other filters are applied!** +We recommend to decorate the `DoctrineProxyFilter` with the `ChainableFilter` to allow applying other filters to the +cloned lazy loaded entities. ```php use DeepCopy\DeepCopy; @@ -275,7 +308,7 @@ use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; $copier = new DeepCopy(); -$copier->addFilter(new DoctrineProxyFilter(), new DoctrineProxyMatcher()); +$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); $copy = $copier->copy($object); diff --git a/fixtures/f013/A.php b/fixtures/f013/A.php new file mode 100644 index 0000000..aa0393d --- /dev/null +++ b/fixtures/f013/A.php @@ -0,0 +1,24 @@ +foo; + } + + public function setFoo($foo) + { + $this->foo = $foo; + } +} diff --git a/fixtures/f013/C.php b/fixtures/f013/C.php new file mode 100644 index 0000000..26d4af2 --- /dev/null +++ b/fixtures/f013/C.php @@ -0,0 +1,13 @@ +foo = null; + } +} diff --git a/src/DeepCopy/DeepCopy.php b/src/DeepCopy/DeepCopy.php index 5e68c64..6e766d8 100644 --- a/src/DeepCopy/DeepCopy.php +++ b/src/DeepCopy/DeepCopy.php @@ -7,6 +7,7 @@ use DateTimeInterface; use DateTimeZone; use DeepCopy\Exception\CloneException; +use DeepCopy\Filter\ChainableFilter; use DeepCopy\Filter\Filter; use DeepCopy\Matcher\Matcher; use DeepCopy\Reflection\ReflectionHelper; @@ -239,6 +240,10 @@ function ($object) { } ); + if ($filter instanceof ChainableFilter) { + continue; + } + // If a filter matches, we stop processing this property return; } diff --git a/src/DeepCopy/Filter/ChainableFilter.php b/src/DeepCopy/Filter/ChainableFilter.php new file mode 100644 index 0000000..4e3f7bb --- /dev/null +++ b/src/DeepCopy/Filter/ChainableFilter.php @@ -0,0 +1,24 @@ +filter = $filter; + } + + public function apply($object, $property, $objectCopier) + { + $this->filter->apply($object, $property, $objectCopier); + } +} diff --git a/tests/DeepCopyTest/DeepCopyTest.php b/tests/DeepCopyTest/DeepCopyTest.php index 8d11c5b..0b44648 100644 --- a/tests/DeepCopyTest/DeepCopyTest.php +++ b/tests/DeepCopyTest/DeepCopyTest.php @@ -20,8 +20,12 @@ use DeepCopy\f009; use DeepCopy\f011; use DeepCopy\f012\Suit; +use DeepCopy\f013; +use DeepCopy\Filter\ChainableFilter; +use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; use DeepCopy\Filter\KeepFilter; use DeepCopy\Filter\SetNullFilter; +use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; use DeepCopy\Matcher\PropertyNameMatcher; use DeepCopy\Matcher\PropertyTypeMatcher; use DeepCopy\TypeFilter\ShallowCopyFilter; @@ -508,6 +512,36 @@ public function test_it_keeps_enums() $this->assertSame($enum, $copy); } + /** + * @ticket https://github.com/myclabs/DeepCopy/issues/98 + */ + public function test_it_can_apply_two_filters_with_chainable_filter() + { + $object = new f013\A(); + + $deepCopy = new DeepCopy(); + $deepCopy->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); + $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('foo')); + + $copy = $deepCopy->copy($object); + + $this->assertNull($copy->foo); + } + + public function test_it_can_copy_property_after_applying_doctrine_proxy_filter_with_chainable_filter() + { + $object = new f013\B(); + $object->setFoo(new f013\C()); + + $deepCopy = new DeepCopy(); + $deepCopy->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); + + /** @var f013\B $copy */ + $copy = $deepCopy->copy($object); + + $this->assertNotEquals($copy->getFoo(), $object->getFoo()); + } + private function assertEqualButNotSame($expected, $val) { $this->assertEquals($expected, $val);