Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -2,37 +2,50 @@
namespace DeepCopy;
use ArrayObject;
use DateInterval;
use DateTimeInterface;
use DateTimeZone;
use DeepCopy\Exception\CloneException;
use DeepCopy\Filter\Filter;
use DeepCopy\Matcher\Matcher;
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedList;
use DeepCopy\Reflection\ReflectionHelper;
use DeepCopy\TypeFilter\Date\DateIntervalFilter;
use DeepCopy\TypeFilter\Spl\ArrayObjectFilter;
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedListFilter;
use DeepCopy\TypeFilter\TypeFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use ReflectionObject;
use ReflectionProperty;
use DeepCopy\Reflection\ReflectionHelper;
use SplDoublyLinkedList;
/**
* DeepCopy
* @final
*/
class DeepCopy
{
/**
* @var array
* @var object[] List of objects copied.
*/
private $hashMap = [];
/**
* Filters to apply.
* @var array
*
* @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
*/
private $filters = [];
/**
* Type Filters to apply.
* @var array
*
* @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
*/
private $typeFilters = [];
/**
* @var bool
*/
private $skipUncloneable = false;
/**
@@ -48,23 +61,30 @@ class DeepCopy
{
$this->useCloneMethod = $useCloneMethod;
$this->addTypeFilter(new SplDoublyLinkedList($this), new TypeMatcher('\SplDoublyLinkedList'));
$this->addTypeFilter(new ArrayObjectFilter($this), new TypeMatcher(ArrayObject::class));
$this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class));
$this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class));
}
/**
* Cloning uncloneable properties won't throw exception.
* If enabled, will not throw an exception when coming across an uncloneable property.
*
* @param $skipUncloneable
*
* @return $this
*/
public function skipUncloneable($skipUncloneable = true)
{
$this->skipUncloneable = $skipUncloneable;
return $this;
}
/**
* Perform a deep copy of the object.
* Deep copies the given object.
*
* @param mixed $object
*
* @return mixed
*/
public function copy($object)
@@ -82,6 +102,14 @@ class DeepCopy
];
}
public function prependFilter(Filter $filter, Matcher $matcher)
{
array_unshift($this->filters, [
'matcher' => $matcher,
'filter' => $filter,
]);
}
public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
{
$this->typeFilters[] = [
@@ -90,7 +118,6 @@ class DeepCopy
];
}
private function recursiveCopy($var)
{
// Matches Type Filter
@@ -102,14 +129,22 @@ class DeepCopy
if (is_resource($var)) {
return $var;
}
// Array
if (is_array($var)) {
return $this->copyArray($var);
}
// Scalar
if (! is_object($var)) {
return $var;
}
// Enum
if (PHP_VERSION_ID >= 80100 && enum_exists(get_class($var))) {
return $var;
}
// Object
return $this->copyObject($var);
}
@@ -129,8 +164,12 @@ class DeepCopy
}
/**
* Copy an object
* Copies an object.
*
* @param object $object
*
* @throws CloneException
*
* @return object
*/
private function copyObject($object)
@@ -141,29 +180,35 @@ class DeepCopy
return $this->hashMap[$objectHash];
}
$reflectedObject = new \ReflectionObject($object);
if (false === $isCloneable = $reflectedObject->isCloneable() and $this->skipUncloneable) {
$this->hashMap[$objectHash] = $object;
return $object;
}
$reflectedObject = new ReflectionObject($object);
$isCloneable = $reflectedObject->isCloneable();
if (false === $isCloneable) {
throw new CloneException(sprintf(
'Class "%s" is not cloneable.',
$reflectedObject->getName()
));
if ($this->skipUncloneable) {
$this->hashMap[$objectHash] = $object;
return $object;
}
throw new CloneException(
sprintf(
'The class "%s" is not cloneable.',
$reflectedObject->getName()
)
);
}
$newObject = clone $object;
$this->hashMap[$objectHash] = $newObject;
if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) {
return $object;
}
if ($newObject instanceof \DateTimeInterface) {
if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) {
return $newObject;
}
if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) {
return $newObject;
}
foreach (ReflectionHelper::getProperties($reflectedObject) as $property) {
$this->copyObjectProperty($newObject, $property);
}
@@ -193,12 +238,19 @@ class DeepCopy
return $this->recursiveCopy($object);
}
);
// If a filter matches, we stop processing this property
return;
}
}
$property->setAccessible(true);
// Ignore uninitialized properties (for PHP >7.4)
if (method_exists($property, 'isInitialized') && !$property->isInitialized($object)) {
return;
}
$propertyValue = $property->getValue($object);
// Copy the property
@@ -206,10 +258,12 @@ class DeepCopy
}
/**
* Returns first filter that matches variable, NULL if no such filter found.
* Returns first filter that matches variable, `null` if no such filter found.
*
* @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and
* 'matcher' with value of type {@see TypeMatcher}
* @param mixed $var
*
* @return TypeFilter|null
*/
private function getFirstMatchedTypeFilter(array $filterRecords, $var)
@@ -228,10 +282,13 @@ class DeepCopy
}
/**
* Returns first element that matches predicate, NULL if no such element found.
* @param array $elements
* Returns first element that matches predicate, `null` if no such element found.
*
* @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
* @param callable $predicate Predicate arguments are: element.
* @return mixed|null
*
* @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher'
* with value of type {@see TypeMatcher} or `null`.
*/
private function first(array $elements, callable $predicate)
{

View File

@@ -1,6 +1,9 @@
<?php
namespace DeepCopy\Exception;
class CloneException extends \UnexpectedValueException
use UnexpectedValueException;
class CloneException extends UnexpectedValueException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace DeepCopy\Exception;
use ReflectionException;
class PropertyException extends ReflectionException
{
}

View File

@@ -3,19 +3,21 @@
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
use ReflectionProperty;
use DeepCopy\Reflection\ReflectionHelper;
/**
* Set a null value for a property
* @final
*/
class DoctrineCollectionFilter implements Filter
{
/**
* Copies the object property doctrine collection.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new ReflectionProperty($object, $property);
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
$reflectionProperty->setAccessible(true);
$oldCollection = $reflectionProperty->getValue($object);

View File

@@ -3,12 +3,16 @@
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
use DeepCopy\Reflection\ReflectionHelper;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @final
*/
class DoctrineEmptyCollectionFilter implements Filter
{
/**
* Apply the filter to the object.
* Sets the object property to an empty doctrine collection.
*
* @param object $object
* @param string $property
@@ -16,7 +20,7 @@ class DoctrineEmptyCollectionFilter implements Filter
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new \ReflectionProperty($object, $property);
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, new ArrayCollection());

View File

@@ -5,12 +5,14 @@ namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
/**
* Trigger the magic method __load() on a Doctrine Proxy class to load the
* actual entity from the database.
* @final
*/
class DoctrineProxyFilter implements Filter
{
/**
* Triggers the magic method __load() on a Doctrine Proxy class to load the
* actual entity from the database.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)

View File

@@ -8,7 +8,8 @@ namespace DeepCopy\Filter;
interface Filter
{
/**
* Apply the filter to the object.
* Applies the filter to the object.
*
* @param object $object
* @param string $property
* @param callable $objectCopier

View File

@@ -2,12 +2,11 @@
namespace DeepCopy\Filter;
/**
* Keep the value of a property
*/
class KeepFilter implements Filter
{
/**
* Keeps the value of the object property.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)

View File

@@ -2,8 +2,10 @@
namespace DeepCopy\Filter;
use DeepCopy\Reflection\ReflectionHelper;
/**
* Replace the value of a property
* @final
*/
class ReplaceFilter implements Filter
{
@@ -21,11 +23,13 @@ class ReplaceFilter implements Filter
}
/**
* Replaces the object property by the result of the callback called with the object property.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new \ReflectionProperty($object, $property);
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
$reflectionProperty->setAccessible(true);
$value = call_user_func($this->callback, $reflectionProperty->getValue($object));

View File

@@ -2,19 +2,21 @@
namespace DeepCopy\Filter;
use ReflectionProperty;
use DeepCopy\Reflection\ReflectionHelper;
/**
* Set a null value for a property
* @final
*/
class SetNullFilter implements Filter
{
/**
* Sets the object property to null.
*
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new ReflectionProperty($object, $property);
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, null);

View File

@@ -3,14 +3,16 @@
namespace DeepCopy\Matcher\Doctrine;
use DeepCopy\Matcher\Matcher;
use Doctrine\Common\Persistence\Proxy;
use Doctrine\Persistence\Proxy;
/**
* Match a Doctrine Proxy class.
* @final
*/
class DoctrineProxyMatcher implements Matcher
{
/**
* Matches a Doctrine Proxy class.
*
* {@inheritdoc}
*/
public function matches($object, $property)

View File

@@ -2,14 +2,12 @@
namespace DeepCopy\Matcher;
/**
* Matcher interface
*/
interface Matcher
{
/**
* @param object $object
* @param string $property
*
* @return boolean
*/
public function matches($object, $property);

View File

@@ -3,7 +3,7 @@
namespace DeepCopy\Matcher;
/**
* Match a specific property of a specific class
* @final
*/
class PropertyMatcher implements Matcher
{
@@ -28,10 +28,12 @@ class PropertyMatcher implements Matcher
}
/**
* Matches a specific property of a specific class.
*
* {@inheritdoc}
*/
public function matches($object, $property)
{
return ($object instanceof $this->class) && ($property == $this->property);
return ($object instanceof $this->class) && $property == $this->property;
}
}

View File

@@ -3,7 +3,7 @@
namespace DeepCopy\Matcher;
/**
* Match a property by its name
* @final
*/
class PropertyNameMatcher implements Matcher
{
@@ -21,6 +21,8 @@ class PropertyNameMatcher implements Matcher
}
/**
* Matches a property by its name.
*
* {@inheritdoc}
*/
public function matches($object, $property)

View File

@@ -2,13 +2,16 @@
namespace DeepCopy\Matcher;
use ReflectionProperty;
use DeepCopy\Reflection\ReflectionHelper;
use ReflectionException;
/**
* Match a property by its type
* Matches a property by its type.
*
* It is recommended to use {@see DeepCopy\TypeFilter\TypeFilter} instead, as it applies on all occurrences
* of given type in copied context (eg. array elements), not just on object properties.
*
* @final
*/
class PropertyTypeMatcher implements Matcher
{
@@ -30,9 +33,20 @@ class PropertyTypeMatcher implements Matcher
*/
public function matches($object, $property)
{
$reflectionProperty = new ReflectionProperty($object, $property);
try {
$reflectionProperty = ReflectionHelper::getProperty($object, $property);
} catch (ReflectionException $exception) {
return false;
}
$reflectionProperty->setAccessible(true);
// Uninitialized properties (for PHP >7.4)
if (method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($object)) {
// null instanceof $this->propertyType
return false;
}
return $reflectionProperty->getValue($object) instanceof $this->propertyType;
}
}

View File

@@ -2,6 +2,12 @@
namespace DeepCopy\Reflection;
use DeepCopy\Exception\PropertyException;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use ReflectionProperty;
class ReflectionHelper
{
/**
@@ -12,10 +18,11 @@ class ReflectionHelper
* @author muratyaman@gmail.com
* @see http://php.net/manual/en/reflectionclass.getproperties.php
*
* @param \ReflectionClass $ref
* @return \ReflectionProperty[]
* @param ReflectionClass $ref
*
* @return ReflectionProperty[]
*/
public static function getProperties(\ReflectionClass $ref)
public static function getProperties(ReflectionClass $ref)
{
$props = $ref->getProperties();
$propsArr = array();
@@ -36,4 +43,36 @@ class ReflectionHelper
return $propsArr;
}
/**
* Retrieves property by name from object and all its ancestors.
*
* @param object|string $object
* @param string $name
*
* @throws PropertyException
* @throws ReflectionException
*
* @return ReflectionProperty
*/
public static function getProperty($object, $name)
{
$reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object);
if ($reflection->hasProperty($name)) {
return $reflection->getProperty($name);
}
if ($parentClass = $reflection->getParentClass()) {
return self::getProperty($parentClass->getName(), $name);
}
throw new PropertyException(
sprintf(
'The class "%s" doesn\'t have a property with the given name: "%s".',
is_object($object) ? get_class($object) : $object,
$name
)
);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace DeepCopy\TypeFilter\Date;
use DateInterval;
use DeepCopy\TypeFilter\TypeFilter;
/**
* @final
*
* @deprecated Will be removed in 2.0. This filter will no longer be necessary in PHP 7.1+.
*/
class DateIntervalFilter implements TypeFilter
{
/**
* {@inheritdoc}
*
* @param DateInterval $element
*
* @see http://news.php.net/php.bugs/205076
*/
public function apply($element)
{
$copy = new DateInterval('P0D');
foreach ($element as $propertyName => $propertyValue) {
$copy->{$propertyName} = $propertyValue;
}
return $copy;
}
}

View File

@@ -2,6 +2,9 @@
namespace DeepCopy\TypeFilter;
/**
* @final
*/
class ReplaceFilter implements TypeFilter
{
/**

View File

@@ -2,6 +2,9 @@
namespace DeepCopy\TypeFilter;
/**
* @final
*/
class ShallowCopyFilter implements TypeFilter
{
/**

View File

@@ -0,0 +1,36 @@
<?php
namespace DeepCopy\TypeFilter\Spl;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
/**
* In PHP 7.4 the storage of an ArrayObject isn't returned as
* ReflectionProperty. So we deep copy its array copy.
*/
final class ArrayObjectFilter implements TypeFilter
{
/**
* @var DeepCopy
*/
private $copier;
public function __construct(DeepCopy $copier)
{
$this->copier = $copier;
}
/**
* {@inheritdoc}
*/
public function apply($arrayObject)
{
$clone = clone $arrayObject;
foreach ($arrayObject->getArrayCopy() as $k => $v) {
$clone->offsetSet($k, $this->copier->copy($v));
}
return $clone;
}
}

View File

@@ -2,36 +2,9 @@
namespace DeepCopy\TypeFilter\Spl;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
class SplDoublyLinkedList implements TypeFilter
/**
* @deprecated Use {@see SplDoublyLinkedListFilter} instead.
*/
class SplDoublyLinkedList extends SplDoublyLinkedListFilter
{
/**
* @var DeepCopy
*/
private $deepCopy;
public function __construct(DeepCopy $deepCopy)
{
$this->deepCopy = $deepCopy;
}
/**
* {@inheritdoc}
*/
public function apply($element)
{
$newElement = clone $element;
if ($element instanceof \SplDoublyLinkedList) {
// Replace each element in the list with a deep copy of itself
for ($i = 1; $i <= $newElement->count(); $i++) {
$newElement->push($this->deepCopy->copy($newElement->shift()));
}
}
return $newElement;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace DeepCopy\TypeFilter\Spl;
use Closure;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
use SplDoublyLinkedList;
/**
* @final
*/
class SplDoublyLinkedListFilter implements TypeFilter
{
private $copier;
public function __construct(DeepCopy $copier)
{
$this->copier = $copier;
}
/**
* {@inheritdoc}
*/
public function apply($element)
{
$newElement = clone $element;
$copy = $this->createCopyClosure();
return $copy($newElement);
}
private function createCopyClosure()
{
$copier = $this->copier;
$copy = function (SplDoublyLinkedList $list) use ($copier) {
// Replace each element in the list with a deep copy of itself
for ($i = 1; $i <= $list->count(); $i++) {
$copy = $copier->recursiveCopy($list->shift());
$list->push($copy);
}
return $list;
};
return Closure::bind($copy, null, DeepCopy::class);
}
}

View File

@@ -5,7 +5,8 @@ namespace DeepCopy\TypeFilter;
interface TypeFilter
{
/**
* Apply the filter to the object.
* Applies the filter to the object.
*
* @param mixed $element
*/
public function apply($element);

View File

@@ -2,9 +2,6 @@
namespace DeepCopy\TypeMatcher;
/**
* TypeMatcher class
*/
class TypeMatcher
{
/**
@@ -21,7 +18,8 @@ class TypeMatcher
}
/**
* @param $element
* @param mixed $element
*
* @return boolean
*/
public function matches($element)

View File

@@ -0,0 +1,20 @@
<?php
namespace DeepCopy;
use function function_exists;
if (false === function_exists('DeepCopy\deep_copy')) {
/**
* Deep copies the given value.
*
* @param mixed $value
* @param bool $useCloneMethod
*
* @return mixed
*/
function deep_copy($value, $useCloneMethod = false)
{
return (new DeepCopy($useCloneMethod))->copy($value);
}
}