myclabs/deep-copy

创建对象的深度副本(克隆)

资助包维护!
Tidelift

安装量: 610,888,867

依赖项: 158

建议者: 1

安全性: 0

星标: 8,736

关注者: 26

分支: 101

开放问题: 18

1.12.0 2024-06-12 14:39 UTC

README

DeepCopy 帮助您创建对象的深度副本(克隆)。它旨在处理关联图中的循环。

Total Downloads Integrate

目录

  1. 如何
  2. 为什么
    1. 使用简单的 clone
    2. 重写 __clone()
    3. 使用 DeepCopy
  3. 它是如何工作的
  4. 更进一步
    1. 匹配器
      1. 属性名
      2. 特定属性
      3. 类型
    2. 过滤器
      1. SetNullFilter
      2. KeepFilter
      3. DoctrineCollectionFilter
      4. DoctrineEmptyCollectionFilter
      5. DoctrineProxyFilter
      6. ReplaceFilter
      7. ShallowCopyFilter
  5. 边缘情况
  6. 贡献
    1. 测试

如何?

使用 Composer 安装

composer require myclabs/deep-copy

使用它

use DeepCopy\DeepCopy;

$copier = new DeepCopy();
$myCopy = $copier->copy($myObject);

为什么?

  • 您如何创建对象的副本?
$myCopy = clone $myObject;
  • 您如何创建对象的 深度 副本(即在属性中复制所有引用的对象)?

您使用 __clone() 并自行实现行为。

  • 但是如何处理关联图中的 循环

现在你陷入了一个大麻烦 :)

association graph

使用简单的 clone

Using clone

重写 __clone()

Overriding __clone

使用 DeepCopy

With DeepCopy

它是如何工作的

DeepCopy 递归遍历所有对象的属性并将它们克隆。为了避免克隆同一个对象两次,它保留一个所有实例的哈希表,从而保留对象图。

使用它

use function DeepCopy\deep_copy;

$copy = deep_copy($var);

或者,您可以创建自己的 DeepCopy 实例以不同方式配置它,例如

use DeepCopy\DeepCopy;

$copier = new DeepCopy(true);

$copy = $copier->copy($var);

您可能想自己编写深度复制函数

namespace Acme;

use DeepCopy\DeepCopy;

function deep_copy($var)
{
    static $copier = null;
    
    if (null === $copier) {
        $copier = new DeepCopy(true);
    }
    
    return $copier->copy($var);
}

更进一步

您可以向复制过程添加过滤器来自定义。

添加过滤器的方法是 DeepCopy\DeepCopy::addFilter($filter, $matcher),其中 $filter 实现 DeepCopy\Filter\Filter,而 $matcher 实现 DeepCopy\Matcher\Matcher

我们提供了一些通用的过滤器和匹配器。

匹配器

  • DeepCopy\Matcher 作用于对象属性。
  • DeepCopy\TypeMatcher 作用于图中找到的任何元素,包括数组元素。

属性名

PropertyNameMatcher 将通过属性名匹配属性

use DeepCopy\Matcher\PropertyNameMatcher;

// Will apply a filter to any property of any objects named "id"
$matcher = new PropertyNameMatcher('id');

特定属性

PropertyMatcher 将匹配特定类的特定属性

use DeepCopy\Matcher\PropertyMatcher;

// Will apply a filter to the property "id" of any objects of the class "MyClass"
$matcher = new PropertyMatcher('MyClass', 'id');

类型

TypeMatcher 将通过类型匹配任何元素(类的实例或任何可以是 gettype() 函数参数的值)

use DeepCopy\TypeMatcher\TypeMatcher;

// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection
$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection');

过滤器

  • DeepCopy\Filter 对由 DeepCopy\Matcher 匹配的对象属性应用转换
  • DeepCopy\TypeFilter 对由 DeepCopy\TypeMatcher 匹配的任何元素应用转换

按设计,匹配过滤器将停止过滤器链(即下一个过滤器将不会应用)。使用 ChainableFilter 不会停止过滤器链。

SetNullFilter(过滤器)

例如,如果您正在复制数据库记录(或 Doctrine 实体),则希望副本不包含任何 ID

use DeepCopy\DeepCopy;
use DeepCopy\Filter\SetNullFilter;
use DeepCopy\Matcher\PropertyNameMatcher;

$object = MyClass::load(123);
echo $object->id; // 123

$copier = new DeepCopy();
$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));

$copy = $copier->copy($object);

echo $copy->id; // null

KeepFilter(过滤器)

如果您想保留属性不变(例如,一个到对象的关联)

use DeepCopy\DeepCopy;
use DeepCopy\Filter\KeepFilter;
use DeepCopy\Matcher\PropertyMatcher;

$copier = new DeepCopy();
$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category'));

$copy = $copier->copy($object);
// $copy->category has not been touched

ChainableFilter(过滤器)

如果您对代理类使用克隆,您可能需要对

  1. 加载数据
  2. 应用转换

您可以将 ChainableFilter 用作代理加载器过滤器的装饰器,它不会停止过滤链(即,下一个可能被应用)。

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(过滤器)

如果您使用 Doctrine 并想复制一个实体,您将需要使用 DoctrineCollectionFilter

use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter;
use DeepCopy\Matcher\PropertyTypeMatcher;

$copier = new DeepCopy();
$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection'));

$copy = $copier->copy($object);

DoctrineEmptyCollectionFilter(过滤器)

如果您使用 Doctrine 并想复制一个包含您想重置的 Collection 的实体,您可以使用 DoctrineEmptyCollectionFilter

use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter;
use DeepCopy\Matcher\PropertyMatcher;

$copier = new DeepCopy();
$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty'));

$copy = $copier->copy($object);

// $copy->myProperty will return an empty collection

DoctrineProxyFilter(过滤器)

如果您使用 Doctrine 并在对延迟加载的实体进行克隆时遇到错误,这些错误提到了 Doctrine 代理类(...\__CG__\Proxy)上缺少的字段。您可以使用 DoctrineProxyFilter 来加载 Doctrine 代理类后面的实际实体。但是,请确保将其作为您过滤链中的第一个过滤器之一,以便在应用其他过滤器之前加载实体! 我们建议使用 ChainableFilter 装饰 DoctrineProxyFilter 以允许对克隆的延迟加载实体应用其他过滤器。

use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineProxyFilter;
use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher;

$copier = new DeepCopy();
$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher());

$copy = $copier->copy($object);

// $copy should now contain a clone of all entities, including those that were not yet fully loaded.

ReplaceFilter(类型过滤器)

  1. 如果您想替换属性的值
use DeepCopy\DeepCopy;
use DeepCopy\Filter\ReplaceFilter;
use DeepCopy\Matcher\PropertyMatcher;

$copier = new DeepCopy();
$callback = function ($currentValue) {
  return $currentValue . ' (copy)'
};
$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title'));

$copy = $copier->copy($object);

// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)'
  1. 如果您想替换整个元素
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\ReplaceFilter;
use DeepCopy\TypeMatcher\TypeMatcher;

$copier = new DeepCopy();
$callback = function (MyClass $myClass) {
  return get_class($myClass);
};
$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass'));

$copy = $copier->copy([new MyClass, 'some string', new MyClass]);

// $copy will contain ['MyClass', 'some string', 'MyClass']

ReplaceFilter 构造函数的 $callback 参数接受任何 PHP 可调用者。

ShallowCopyFilter(类型过滤器)

停止 DeepCopy 递归复制元素,使用标准的 clone

use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\ShallowCopyFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use Mockery as m;

$this->deepCopy = new DeepCopy();
$this->deepCopy->addTypeFilter(
	new ShallowCopyFilter,
	new TypeMatcher(m\MockInterface::class)
);

$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class));
// All mocks will be just cloned, not deep copied

边缘情况

以下结构不能使用 PHP 反射进行深度复制。因此,它们被浅复制,并且过滤器不被应用。您有两种处理方法

  • 实现自己的 __clone() 方法
  • 使用类型匹配器过滤器

贡献

DeepCopy 在 MIT 许可下分发。

测试

运行测试很简单

vendor/bin/phpunit

支持

通过 Tidelift 订阅 获得专业支持。