eniams/spy

处理程序,以了解对象是否被修改

v0.1-beta.5 2020-05-10 15:51 UTC

README

Build Status

Spy 帮助您了解对象是否被修改,并允许您在给定的对象被修改时触发/监听事件。

⚠️ 此项目正在开发中。 ⚠️

安装

$ composer require eniams/spy

它是如何工作的?

Spy Workflow

SpyBase Workflow

幕后

初始对象将使用特定的克隆器进行复制,然后根据需要比较初始(复制的)对象和操作(当前)对象的值,以检查是否有修改。

假设您想监视一个 foo 对象,以了解它是否被修改

  1. 为要监视的类标记一个接口,该接口将对应于选择的克隆器,有 2 个内置克隆器
  • Eniams\Spy\Cloner\DeepCopyClonerInterface,它使用著名的库 DeepCopy

  • Eniams\Spy\Cloner\SpyClonerLoadPropertyObjectInterfaceEniams\Spy\Cloner\SpyClonerInterface,它允许您更深入地克隆存储在属性中的对象/数组
<?php
namespace App\Entity\Foo;

class Foo implements \Eniams\Spy\Cloner\DeepCopyClonerInterface
// or SpyClonerLoadPropertyObjectInterface 
// or SpyClonerInterface
{}

您可以创建一个自定义的克隆器来复制您的对象

  • 创建一个应实现 Eniams\Spy\Cloner\ClonerInterface 的克隆器。
  • 创建一个与创建的克隆器相关的接口,该接口应实现 Eniams\Spy\SpyInterface
<?php
namespace App\Service;
// Create the Cloner
class UserLandCloner implements \Eniams\Spy\Cloner\ClonerInterface
{
    public function doClone($object)
    {
        // Stuff to clone the given $object.
    }
    
    public function supports($object): bool
    {
        return $object instanceof UserLandClonerInterface;
    }   
}

// Create the Interface
interface UserLandClonerInterface extends \Eniams\Spy\SpyInterface

// Use your Cloner (Implement the created interface in the class) 
class Foo implements \Eniams\Spy\Cloner\UserLandClonerInterface

如果您使用 Symfony,则可利用 自动配置标签,您无需执行下一步,创建的克隆器将被注册到 ChainCloner 中,该克隆器负责克隆要监视的对象。因此,您可以进入第 3 步。

  1. 对于纯 PHP,如果您不想使用默认的克隆器,您可以在 Eniams\Spy\ClonerChainCloner 中注册您的克隆器
<?php
 $chainCloner = new \Eniams\Spy\Cloner\ChainCloner([new UserLandCloner()]);
  1. 现在是监视对象的时候了 :shipit:
<?php
// $chainCloner is optional and need to be use only if you want to use a custom cloners,
// for Symfony remember that your custom cloner is already registered in the `ChainCloner $chainCloner` and it is a public service that can be retrieve from the container.
$spied = new \Eniams\Spy\Spy($foo, $chainCloner); 

$spied->isModified();
$spied->isNotModified();
现在,您想了解是否修改了特定的属性,并获取初始值和当前值。
<?php
$foo = (new \App\Entity\Foo())->setName('Smaone');

$spied = new \Eniams\Spy\Spy($foo, $chainCloner);

$foo->setName('Dude');

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

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

$propertyState->getFqcn(); // App\Entity\Foo
$propertyState->getProperty(); // 'name'
$propertyState->getInitialValue(); // 'Smaone'
$propertyState->getCurrentValue(); // 'Dude'
使用服务容器,您可以将对象存储在 SpyBase 中,以便稍后在您的应用程序中检索它
Symfony
<?php
class FooController extends AbstractController
{
    /**
     * @Route("/foo", name="foo")
     */
    public function index(SpyBase $spyBase)
    {
        $user = (new \Foo\Entity\User())->setName('Smaone');
        $spyBase = (new \Eniams\Spy\SpyBase());
        $spyBase->add('your_key', $user);
        
纯 PHP
<?php
$user = (new \Foo\Entity\User())->setName('Smaone');
$spyBase = (new \Eniams\Spy\SpyBase());
$spyBase->add('your_key', $user); // behind the scene $object is converted to a \Eniams\Spy\Spy object and the cloner class will be resolve by the interface implemented by the $object.

$yourContainer
    ->register('spy_base_service', $spyBase);

$spyBase = $yourContainer->get('spy_base_service');
// fetch the object
$spyBase->get('your_key');

// remove
$spyBase->remove('your_key');
对于不需要克隆对象的简单用例,您也可以检查两个“相同”类之间的差异。
<?php
$firstUser = (new App\Entity\User())->setName('Smaone');
$secondUser = (new App\Entity\User())->setName('Dude');

$propertyState = Eniams\Spy\Property\PropertyStateFactory::createPropertyState('name', $firstUser, $secondUser);

$propertyState->getFqcn(); // App\Entity\User
$propertyState->getProperty(); // 'name'
$propertyState->getInitialValue(); // 'Smaone'
$propertyState->getCurrentValue(); // 'Dude'
更高级的用例
您可以定义一个上下文来检查一些属性。
<?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
您可以动态地定义要检查的属性。
<?php
class User implements SpyClonerInterface{

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

// index.php
$spied->isModifiedForProperties(['age']); // true only if age was modified
您可以排除一些属性。
<?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