Skip to content

Commit

Permalink
add dynamic startegy (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
ismail1432 authored May 10, 2020
1 parent e68a46b commit c59a37a
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 117 deletions.
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ $foo->setName('Dude');

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

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

$propertyState->getFqcn(); // App\Entity\Foo
$propertyState->getProperty(); // 'name'
Expand Down Expand Up @@ -158,3 +158,61 @@ $propertyState->getProperty(); // 'name'
$propertyState->getInitialValue(); // 'Smaone'
$propertyState->getCurrentValue(); // 'Dude'
```

##### More advanced use case

##### You can define a context to check some properties.
```php
class User implements SpyClonerInterface, PropertyCheckerContextInterface {

private $age;
private $adresse;
private $firstname;
public static function propertiesInContext(): array
{
return [
'context_check_firstname' => ['firstname', 'age'],
'context_check_adresse' => ['adresse'],
];
}
}

// index.php
$spied->isModifiedInContext(['context_check_firstname']); // true only if 'firstname', 'age' were modified
$spied->isModifiedInContext(['context_check_adresse']); // true only if 'adresse' is modified
$spied->getPropertiesModifiedInContext(['context_check_adresse']); // return modified properties for context context_check_adresse
```

##### You can define dynamically which properties to check
```php
class User implements SpyClonerInterface{

private $age;
private $adresse;
private $firstname;
}

// index.php
$spied->isModifiedForProperties(['age']); // true only if age was modified
```

##### You can exclude some properties.

```php
class User implements SpyClonerInterface, PropertyCheckerBlackListInterface {

private $age;
private $adresse;
private $firstname;

public static function propertiesBlackList(): array
{
return ['age'];
}
}
// index.php
$user->setAge(33);
$spied->isModified(); // return false because $age is blacklisted
$spied->getPropertiesModifiedWithoutBlackListContext(); // return age even it's blacklisted

```
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
/**
* @author Smaïne Milianni <contact@smaine.me>
*/
class UndefinedContextForBlackListPropertiesException extends \RuntimeException
class UndefinedContextException extends \RuntimeException
{
}
94 changes: 70 additions & 24 deletions src/Property/PropertyChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Eniams\Spy\Property;

use Eniams\Spy\Assertion\SpyAssertion;
use Eniams\Spy\Exception\UndefinedContextForBlackListPropertiesException;
use Eniams\Spy\Exception\UndefinedContextException;
use Eniams\Spy\Reflection\CacheClassInfoTrait;
use Eniams\Spy\Reflection\ClassInfo;

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

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

foreach ($properties as $property) {
if ($this->isPropertyModified($initial, $current, $property->getName(), $classInfo)) {
return true;
}
}

return false;
return $this->doCheck($initial, $current, $classInfo, $properties);
}

/**
* object $initial and object $current will be compared to know if $initial was modified.
* object $initial and object $current will be compared to know if $initial was modified in a specifc $context.
*
* @param object $initial
* @param object $current
Expand All @@ -53,6 +47,23 @@ public function isModifiedInContext(PropertyCheckerContextInterface $initial, Pr
return $this->isModified($initial, $current, $context);
}

/**
* object $initial and object $current will be compared to know if $initial was modified in a specifc $context.
*
* @param object $initial
* @param object $current
*/
public function isModifiedForProperties($initial, $current, array $propertiesToCheck = []): bool
{
SpyAssertion::isComparable($initial, $current);

$classInfo = $this->getCacheClassInfo()->getClassInfo($initial);

$properties = $this->filterProperties($classInfo->getProperties(), $propertiesToCheck);

return $this->doCheck($initial, $current, $classInfo, $properties);
}

/**
* $property of object $initial and object $current will be compared to know if the property was modified.
*
Expand Down Expand Up @@ -90,19 +101,22 @@ public function isPropertyModified($initial, $current, string $propertyName, Cla
* @param object $current
*
* @return PropertyState[]
*
* @see PropertyCheckerBlackListInterface::propertiesBlackList()
*/
public function getPropertiesModified($initial, $current, array $context = []): array
{
SpyAssertion::isComparable($initial, $current);

$propertiesModified = [];

$classInfo = $this->getCacheClassInfo()->getClassInfo($initial);

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

return $this->doExtractPropertiesModified($initial, $current, $classInfo, $properties);
}

public function doExtractPropertiesModified($initial, $current, $classInfo, $properties)
{
$propertiesModified = [];

foreach ($properties as $property) {
$propertyName = $property->getName();

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

/**
* Return modified properties even they are excluded with the black list strategy.
*
* @param $initial
* @param $current
*
* @return PropertyState[]
*/
public function getPropertiesModifiedWithBlackListContext($initial, $current)
public function getPropertiesModifiedWithoutBlackListContext($initial, $current)
{
return $this->getPropertiesModified($initial, $current);
SpyAssertion::isComparable($initial, $current);

$classInfo = $this->getCacheClassInfo()->getClassInfo($initial);

return $this->doExtractPropertiesModified($initial, $current, $classInfo, $classInfo->getProperties());
}

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

/**
* @param \ReflectionProperty[]
*/
private function doCheck($initial, $current, $classInfo, array $properties = []): bool
{
foreach ($properties as $property) {
if ($this->isPropertyModified($initial, $current, $property->getName(), $classInfo)) {
return true;
}
}

return false;
}

private function compareCollection(array $first, array $second): array
{
return array_udiff($first, $second, static function () use ($first, $second) {
Expand All @@ -157,7 +191,7 @@ private function compareCollection(array $first, array $second): array
}

/**
* @param array $properties \ReflectionProperty[]
* @param $properties \ReflectionProperty[]
*
* @return \ReflectionProperty[]
*/
Expand All @@ -178,35 +212,47 @@ private function filterBlackListedProperties(PropertyCheckerBlackListInterface $
private function filterPropertiesInContext(PropertyCheckerContextInterface $object, array $properties = [], array $context = []): array
{
$contextProperties = $object::propertiesInContext();
$filteredFromContext = [];
$propertiesExtractedFromContext = [];

foreach ($context as $contextName) {
if (null === $propertiesToCheck = $contextProperties[$contextName] ?? null) {
throw new UndefinedContextForBlackListPropertiesException(sprintf('There is no properties for contex %s', $contextName));
throw new UndefinedContextException(sprintf('There is no properties for context %s', $contextName));
}
$filteredFromContext = array_merge($filteredFromContext, $propertiesToCheck);
$propertiesExtractedFromContext = array_merge($propertiesExtractedFromContext, $propertiesToCheck);
}

return array_filter($properties, static function (\ReflectionProperty $property) use ($filteredFromContext) {
return \in_array($property->getName(), $filteredFromContext);
return $this->filterProperties($properties, $propertiesExtractedFromContext);
}

/**
* @param $properties \ReflectionProperty[]
*
* @return \ReflectionProperty[]
*/
private function filterProperties(array $properties, array $propertyToExtract): array
{
return array_filter($properties, static function (\ReflectionProperty $property) use ($propertyToExtract) {
return \in_array($property->getName(), $propertyToExtract);
});
}

/**
* Filter properties with the good strategy, Blacklist, Context or no strategy.
*
* @param $object
* @param $properties \ReflectionProperty[]
*
* @return \ReflectionProperty[]
*/
private function getFilteredProperties($object, array $properties = [], array $context = []): array
{
if ($object instanceof PropertyCheckerContextInterface && [] !== $context) {
return $this->filterPropertiesInContext($object, $properties, $context);
}

if ($object instanceof PropertyCheckerBlackListInterface) {
return $this->filterBlackListedProperties($object, $properties);
}
if ($object instanceof PropertyCheckerContextInterface) {
return $this->filterPropertiesInContext($object, $properties, $context);
}

return $properties;
}
Expand Down
42 changes: 23 additions & 19 deletions src/Spy.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,24 +85,19 @@ public function isNotModified(): bool
return !$this->getPropertyChecker()->isModified($this->getInitial(), $this->getCurrent());
}

public function isPropertyModified(string $property)
public function isModifiedForProperties(array $properties): bool
{
return $this->getPropertyChecker()->isPropertyModified($this->getInitial(), $this->getCurrent(), $property);
}

public function getPropertyState(string $property): PropertyState
{
return $this->getPropertyStateFactory()::createPropertyState($property, $this->getInitial(), $this->getCurrent());
return $this->getPropertyChecker()->isModifiedForProperties($this->initial, $this->current, $properties);
}

private function getPropertyStateFactory(): PropertyStateFactory
public function isModifiedInContext(array $context): bool
{
return $this->propertyStateFactory = $this->propertyStateFactory ?: new PropertyStateFactory();
return $this->getPropertyChecker()->isModifiedInContext($this->initial, $this->current, $context);
}

public function getPropertyChecker()
public function isPropertyModified(string $property): bool
{
return $this->propertyChecker = $this->propertyChecker ?: new PropertyChecker();
return $this->getPropertyChecker()->isPropertyModified($this->getInitial(), $this->getCurrent(), $property);
}

/**
Expand All @@ -114,11 +109,15 @@ public function getModifiedProperties(): array
}

/**
* Return modified properties even they are excluded with the black list strategy.
*
* @see PropertyCheckerBlackListInterface
*
* @return PropertyState[]
*/
public function getPropertiesModifiedWithBlackListContext(): array
public function getPropertiesModifiedWithoutBlackListContext(): array
{
return $this->getPropertyChecker()->getPropertiesModifiedWithBlackListContext($this->initial, $this->current);
return $this->getPropertyChecker()->getPropertiesModifiedWithoutBlackListContext($this->initial, $this->current);
}

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

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

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

private function getCloner(): ChainCloner
private function getPropertyStateFactory(): PropertyStateFactory
{
return new ChainCloner([new DeepCopyCloner(), new SpyCloner()]);
return $this->propertyStateFactory = $this->propertyStateFactory ?: new PropertyStateFactory();
}

private function getPropertyChecker()
{
return $this->propertyChecker = $this->propertyChecker ?: new PropertyChecker();
}
}
28 changes: 28 additions & 0 deletions tests/Fixtures/ContentBlackListedFoo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Eniams\Spy\Tests\Fixtures;

use Eniams\Spy\Cloner\SpyClonerInterface;
use Eniams\Spy\Property\PropertyCheckerBlackListInterface;

class ContentBlackListedFoo implements SpyClonerInterface, PropertyCheckerBlackListInterface
{
public $title;
public $content;

public function __construct($title, $content)
{
$this->title = $title;
$this->content = $content;
}

public static function propertiesBlackList(): array
{
return ['content'];
}

public function getIdentifier(): string
{
return 'blackListed';
}
}
Loading

0 comments on commit c59a37a

Please sign in to comment.