Skip to content

Commit c59a37a

Browse files
authored
add dynamic startegy (#23)
1 parent e68a46b commit c59a37a

9 files changed

Lines changed: 368 additions & 117 deletions

File tree

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ $foo->setName('Dude');
103103

104104
$spied->isPropertyModified('name'); // output true
105105

106-
$propertyState = $spied->getPropertState('name');
106+
$propertyState = $spied->getPropertyState('name');
107107

108108
$propertyState->getFqcn(); // App\Entity\Foo
109109
$propertyState->getProperty(); // 'name'
@@ -158,3 +158,61 @@ $propertyState->getProperty(); // 'name'
158158
$propertyState->getInitialValue(); // 'Smaone'
159159
$propertyState->getCurrentValue(); // 'Dude'
160160
```
161+
162+
##### More advanced use case
163+
164+
##### You can define a context to check some properties.
165+
```php
166+
class User implements SpyClonerInterface, PropertyCheckerContextInterface {
167+
168+
private $age;
169+
private $adresse;
170+
private $firstname;
171+
public static function propertiesInContext(): array
172+
{
173+
return [
174+
'context_check_firstname' => ['firstname', 'age'],
175+
'context_check_adresse' => ['adresse'],
176+
];
177+
}
178+
}
179+
180+
// index.php
181+
$spied->isModifiedInContext(['context_check_firstname']); // true only if 'firstname', 'age' were modified
182+
$spied->isModifiedInContext(['context_check_adresse']); // true only if 'adresse' is modified
183+
$spied->getPropertiesModifiedInContext(['context_check_adresse']); // return modified properties for context context_check_adresse
184+
```
185+
186+
##### You can define dynamically which properties to check
187+
```php
188+
class User implements SpyClonerInterface{
189+
190+
private $age;
191+
private $adresse;
192+
private $firstname;
193+
}
194+
195+
// index.php
196+
$spied->isModifiedForProperties(['age']); // true only if age was modified
197+
```
198+
199+
##### You can exclude some properties.
200+
201+
```php
202+
class User implements SpyClonerInterface, PropertyCheckerBlackListInterface {
203+
204+
private $age;
205+
private $adresse;
206+
private $firstname;
207+
208+
public static function propertiesBlackList(): array
209+
{
210+
return ['age'];
211+
}
212+
}
213+
// index.php
214+
$user->setAge(33);
215+
$spied->isModified(); // return false because $age is blacklisted
216+
$spied->getPropertiesModifiedWithoutBlackListContext(); // return age even it's blacklisted
217+
218+
```

src/Exception/UndefinedContextForBlackListPropertiesException.php renamed to src/Exception/UndefinedContextException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
/**
66
* @author Smaïne Milianni <contact@smaine.me>
77
*/
8-
class UndefinedContextForBlackListPropertiesException extends \RuntimeException
8+
class UndefinedContextException extends \RuntimeException
99
{
1010
}

src/Property/PropertyChecker.php

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Eniams\Spy\Property;
44

55
use Eniams\Spy\Assertion\SpyAssertion;
6-
use Eniams\Spy\Exception\UndefinedContextForBlackListPropertiesException;
6+
use Eniams\Spy\Exception\UndefinedContextException;
77
use Eniams\Spy\Reflection\CacheClassInfoTrait;
88
use Eniams\Spy\Reflection\ClassInfo;
99

@@ -33,17 +33,11 @@ public function isModified($initial, $current, array $context = []): bool
3333

3434
$properties = $this->getFilteredProperties($initial, $classInfo->getProperties(), $context);
3535

36-
foreach ($properties as $property) {
37-
if ($this->isPropertyModified($initial, $current, $property->getName(), $classInfo)) {
38-
return true;
39-
}
40-
}
41-
42-
return false;
36+
return $this->doCheck($initial, $current, $classInfo, $properties);
4337
}
4438

4539
/**
46-
* object $initial and object $current will be compared to know if $initial was modified.
40+
* object $initial and object $current will be compared to know if $initial was modified in a specifc $context.
4741
*
4842
* @param object $initial
4943
* @param object $current
@@ -53,6 +47,23 @@ public function isModifiedInContext(PropertyCheckerContextInterface $initial, Pr
5347
return $this->isModified($initial, $current, $context);
5448
}
5549

50+
/**
51+
* object $initial and object $current will be compared to know if $initial was modified in a specifc $context.
52+
*
53+
* @param object $initial
54+
* @param object $current
55+
*/
56+
public function isModifiedForProperties($initial, $current, array $propertiesToCheck = []): bool
57+
{
58+
SpyAssertion::isComparable($initial, $current);
59+
60+
$classInfo = $this->getCacheClassInfo()->getClassInfo($initial);
61+
62+
$properties = $this->filterProperties($classInfo->getProperties(), $propertiesToCheck);
63+
64+
return $this->doCheck($initial, $current, $classInfo, $properties);
65+
}
66+
5667
/**
5768
* $property of object $initial and object $current will be compared to know if the property was modified.
5869
*
@@ -90,19 +101,22 @@ public function isPropertyModified($initial, $current, string $propertyName, Cla
90101
* @param object $current
91102
*
92103
* @return PropertyState[]
93-
*
94-
* @see PropertyCheckerBlackListInterface::propertiesBlackList()
95104
*/
96105
public function getPropertiesModified($initial, $current, array $context = []): array
97106
{
98107
SpyAssertion::isComparable($initial, $current);
99108

100-
$propertiesModified = [];
101-
102109
$classInfo = $this->getCacheClassInfo()->getClassInfo($initial);
103110

104111
$properties = $this->getFilteredProperties($initial, $classInfo->getProperties(), $context);
105112

113+
return $this->doExtractPropertiesModified($initial, $current, $classInfo, $properties);
114+
}
115+
116+
public function doExtractPropertiesModified($initial, $current, $classInfo, $properties)
117+
{
118+
$propertiesModified = [];
119+
106120
foreach ($properties as $property) {
107121
$propertyName = $property->getName();
108122

@@ -128,14 +142,20 @@ public function getPropertiesModified($initial, $current, array $context = []):
128142
}
129143

130144
/**
145+
* Return modified properties even they are excluded with the black list strategy.
146+
*
131147
* @param $initial
132148
* @param $current
133149
*
134150
* @return PropertyState[]
135151
*/
136-
public function getPropertiesModifiedWithBlackListContext($initial, $current)
152+
public function getPropertiesModifiedWithoutBlackListContext($initial, $current)
137153
{
138-
return $this->getPropertiesModified($initial, $current);
154+
SpyAssertion::isComparable($initial, $current);
155+
156+
$classInfo = $this->getCacheClassInfo()->getClassInfo($initial);
157+
158+
return $this->doExtractPropertiesModified($initial, $current, $classInfo, $classInfo->getProperties());
139159
}
140160

141161
/**
@@ -149,6 +169,20 @@ public function getPropertiesModifiedInContext($initial, $current, array $contex
149169
return $this->getPropertiesModified($initial, $current, $context);
150170
}
151171

172+
/**
173+
* @param \ReflectionProperty[]
174+
*/
175+
private function doCheck($initial, $current, $classInfo, array $properties = []): bool
176+
{
177+
foreach ($properties as $property) {
178+
if ($this->isPropertyModified($initial, $current, $property->getName(), $classInfo)) {
179+
return true;
180+
}
181+
}
182+
183+
return false;
184+
}
185+
152186
private function compareCollection(array $first, array $second): array
153187
{
154188
return array_udiff($first, $second, static function () use ($first, $second) {
@@ -157,7 +191,7 @@ private function compareCollection(array $first, array $second): array
157191
}
158192

159193
/**
160-
* @param array $properties \ReflectionProperty[]
194+
* @param $properties \ReflectionProperty[]
161195
*
162196
* @return \ReflectionProperty[]
163197
*/
@@ -178,35 +212,47 @@ private function filterBlackListedProperties(PropertyCheckerBlackListInterface $
178212
private function filterPropertiesInContext(PropertyCheckerContextInterface $object, array $properties = [], array $context = []): array
179213
{
180214
$contextProperties = $object::propertiesInContext();
181-
$filteredFromContext = [];
215+
$propertiesExtractedFromContext = [];
182216

183217
foreach ($context as $contextName) {
184218
if (null === $propertiesToCheck = $contextProperties[$contextName] ?? null) {
185-
throw new UndefinedContextForBlackListPropertiesException(sprintf('There is no properties for contex %s', $contextName));
219+
throw new UndefinedContextException(sprintf('There is no properties for context %s', $contextName));
186220
}
187-
$filteredFromContext = array_merge($filteredFromContext, $propertiesToCheck);
221+
$propertiesExtractedFromContext = array_merge($propertiesExtractedFromContext, $propertiesToCheck);
188222
}
189223

190-
return array_filter($properties, static function (\ReflectionProperty $property) use ($filteredFromContext) {
191-
return \in_array($property->getName(), $filteredFromContext);
224+
return $this->filterProperties($properties, $propertiesExtractedFromContext);
225+
}
226+
227+
/**
228+
* @param $properties \ReflectionProperty[]
229+
*
230+
* @return \ReflectionProperty[]
231+
*/
232+
private function filterProperties(array $properties, array $propertyToExtract): array
233+
{
234+
return array_filter($properties, static function (\ReflectionProperty $property) use ($propertyToExtract) {
235+
return \in_array($property->getName(), $propertyToExtract);
192236
});
193237
}
194238

195239
/**
196240
* Filter properties with the good strategy, Blacklist, Context or no strategy.
197241
*
198242
* @param $object
243+
* @param $properties \ReflectionProperty[]
199244
*
200245
* @return \ReflectionProperty[]
201246
*/
202247
private function getFilteredProperties($object, array $properties = [], array $context = []): array
203248
{
249+
if ($object instanceof PropertyCheckerContextInterface && [] !== $context) {
250+
return $this->filterPropertiesInContext($object, $properties, $context);
251+
}
252+
204253
if ($object instanceof PropertyCheckerBlackListInterface) {
205254
return $this->filterBlackListedProperties($object, $properties);
206255
}
207-
if ($object instanceof PropertyCheckerContextInterface) {
208-
return $this->filterPropertiesInContext($object, $properties, $context);
209-
}
210256

211257
return $properties;
212258
}

src/Spy.php

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,19 @@ public function isNotModified(): bool
8585
return !$this->getPropertyChecker()->isModified($this->getInitial(), $this->getCurrent());
8686
}
8787

88-
public function isPropertyModified(string $property)
88+
public function isModifiedForProperties(array $properties): bool
8989
{
90-
return $this->getPropertyChecker()->isPropertyModified($this->getInitial(), $this->getCurrent(), $property);
91-
}
92-
93-
public function getPropertyState(string $property): PropertyState
94-
{
95-
return $this->getPropertyStateFactory()::createPropertyState($property, $this->getInitial(), $this->getCurrent());
90+
return $this->getPropertyChecker()->isModifiedForProperties($this->initial, $this->current, $properties);
9691
}
9792

98-
private function getPropertyStateFactory(): PropertyStateFactory
93+
public function isModifiedInContext(array $context): bool
9994
{
100-
return $this->propertyStateFactory = $this->propertyStateFactory ?: new PropertyStateFactory();
95+
return $this->getPropertyChecker()->isModifiedInContext($this->initial, $this->current, $context);
10196
}
10297

103-
public function getPropertyChecker()
98+
public function isPropertyModified(string $property): bool
10499
{
105-
return $this->propertyChecker = $this->propertyChecker ?: new PropertyChecker();
100+
return $this->getPropertyChecker()->isPropertyModified($this->getInitial(), $this->getCurrent(), $property);
106101
}
107102

108103
/**
@@ -114,11 +109,15 @@ public function getModifiedProperties(): array
114109
}
115110

116111
/**
112+
* Return modified properties even they are excluded with the black list strategy.
113+
*
114+
* @see PropertyCheckerBlackListInterface
115+
*
117116
* @return PropertyState[]
118117
*/
119-
public function getPropertiesModifiedWithBlackListContext(): array
118+
public function getPropertiesModifiedWithoutBlackListContext(): array
120119
{
121-
return $this->getPropertyChecker()->getPropertiesModifiedWithBlackListContext($this->initial, $this->current);
120+
return $this->getPropertyChecker()->getPropertiesModifiedWithoutBlackListContext($this->initial, $this->current);
122121
}
123122

124123
/**
@@ -129,18 +128,23 @@ public function getPropertiesModifiedInContext(array $context): array
129128
return $this->getPropertyChecker()->getPropertiesModifiedInContext($this->initial, $this->current, $context);
130129
}
131130

132-
// @Todo Dispatch an event when the given property is modified
133-
public function spyProperty(string $property)
131+
public function getPropertyState(string $property): PropertyState
134132
{
133+
return $this->getPropertyStateFactory()::createPropertyState($property, $this->getInitial(), $this->getCurrent());
135134
}
136135

137-
// @Todo Dispatch an event when the given method is called
138-
public function spyMethod(string $method)
136+
private function getCloner(): ChainCloner
139137
{
138+
return new ChainCloner([new DeepCopyCloner(), new SpyCloner()]);
140139
}
141140

142-
private function getCloner(): ChainCloner
141+
private function getPropertyStateFactory(): PropertyStateFactory
143142
{
144-
return new ChainCloner([new DeepCopyCloner(), new SpyCloner()]);
143+
return $this->propertyStateFactory = $this->propertyStateFactory ?: new PropertyStateFactory();
144+
}
145+
146+
private function getPropertyChecker()
147+
{
148+
return $this->propertyChecker = $this->propertyChecker ?: new PropertyChecker();
145149
}
146150
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Eniams\Spy\Tests\Fixtures;
4+
5+
use Eniams\Spy\Cloner\SpyClonerInterface;
6+
use Eniams\Spy\Property\PropertyCheckerBlackListInterface;
7+
8+
class ContentBlackListedFoo implements SpyClonerInterface, PropertyCheckerBlackListInterface
9+
{
10+
public $title;
11+
public $content;
12+
13+
public function __construct($title, $content)
14+
{
15+
$this->title = $title;
16+
$this->content = $content;
17+
}
18+
19+
public static function propertiesBlackList(): array
20+
{
21+
return ['content'];
22+
}
23+
24+
public function getIdentifier(): string
25+
{
26+
return 'blackListed';
27+
}
28+
}

0 commit comments

Comments
 (0)