myclabs / deep-copy
创建对象的深度副本(克隆)
Requires
- php: ^7.1 || ^8.0
Requires (Dev)
- doctrine/collections: ^1.6.8
- doctrine/common: ^2.13.3 || ^3.2.2
- phpspec/prophecy: ^1.10
- phpunit/phpunit: ^7.5.20 || ^8.5.23 || ^9.5.13
Conflicts
- doctrine/collections: <1.6.8
- doctrine/common: <2.13.3 || >=3 <3.2.2
README
DeepCopy 帮助您创建对象的深度副本(克隆)。它旨在处理关联图中的循环。
目录
如何?
使用 Composer 安装
composer require myclabs/deep-copy
使用它
use DeepCopy\DeepCopy; $copier = new DeepCopy(); $myCopy = $copier->copy($myObject);
为什么?
- 您如何创建对象的副本?
$myCopy = clone $myObject;
- 您如何创建对象的 深度 副本(即在属性中复制所有引用的对象)?
您使用 __clone()
并自行实现行为。
- 但是如何处理关联图中的 循环?
现在你陷入了一个大麻烦 :)
使用简单的 clone
重写 __clone()
使用 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
(过滤器)
如果您对代理类使用克隆,您可能需要对
- 加载数据
- 应用转换
您可以将 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
(类型过滤器)
- 如果您想替换属性的值
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)'
- 如果您想替换整个元素
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 订阅 获得专业支持。