Added Laravel project

This commit is contained in:
2017-09-17 00:35:10 +02:00
parent a3c19304d5
commit ecf605b8f5
6246 changed files with 682270 additions and 2 deletions

View File

@@ -0,0 +1,246 @@
<?php
namespace DeepCopy;
use DeepCopy\Exception\CloneException;
use DeepCopy\Filter\Filter;
use DeepCopy\Matcher\Matcher;
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedList;
use DeepCopy\TypeFilter\TypeFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use ReflectionProperty;
use DeepCopy\Reflection\ReflectionHelper;
/**
* DeepCopy
*/
class DeepCopy
{
/**
* @var array
*/
private $hashMap = [];
/**
* Filters to apply.
* @var array
*/
private $filters = [];
/**
* Type Filters to apply.
* @var array
*/
private $typeFilters = [];
private $skipUncloneable = false;
/**
* @var bool
*/
private $useCloneMethod;
/**
* @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used
* instead of the regular deep cloning.
*/
public function __construct($useCloneMethod = false)
{
$this->useCloneMethod = $useCloneMethod;
$this->addTypeFilter(new SplDoublyLinkedList($this), new TypeMatcher('\SplDoublyLinkedList'));
}
/**
* Cloning uncloneable properties won't throw exception.
* @param $skipUncloneable
* @return $this
*/
public function skipUncloneable($skipUncloneable = true)
{
$this->skipUncloneable = $skipUncloneable;
return $this;
}
/**
* Perform a deep copy of the object.
* @param mixed $object
* @return mixed
*/
public function copy($object)
{
$this->hashMap = [];
return $this->recursiveCopy($object);
}
public function addFilter(Filter $filter, Matcher $matcher)
{
$this->filters[] = [
'matcher' => $matcher,
'filter' => $filter,
];
}
public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
{
$this->typeFilters[] = [
'matcher' => $matcher,
'filter' => $filter,
];
}
private function recursiveCopy($var)
{
// Matches Type Filter
if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) {
return $filter->apply($var);
}
// Resource
if (is_resource($var)) {
return $var;
}
// Array
if (is_array($var)) {
return $this->copyArray($var);
}
// Scalar
if (! is_object($var)) {
return $var;
}
// Object
return $this->copyObject($var);
}
/**
* Copy an array
* @param array $array
* @return array
*/
private function copyArray(array $array)
{
foreach ($array as $key => $value) {
$array[$key] = $this->recursiveCopy($value);
}
return $array;
}
/**
* Copy an object
* @param object $object
* @return object
*/
private function copyObject($object)
{
$objectHash = spl_object_hash($object);
if (isset($this->hashMap[$objectHash])) {
return $this->hashMap[$objectHash];
}
$reflectedObject = new \ReflectionObject($object);
if (false === $isCloneable = $reflectedObject->isCloneable() and $this->skipUncloneable) {
$this->hashMap[$objectHash] = $object;
return $object;
}
if (false === $isCloneable) {
throw new CloneException(sprintf(
'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) {
return $newObject;
}
foreach (ReflectionHelper::getProperties($reflectedObject) as $property) {
$this->copyObjectProperty($newObject, $property);
}
return $newObject;
}
private function copyObjectProperty($object, ReflectionProperty $property)
{
// Ignore static properties
if ($property->isStatic()) {
return;
}
// Apply the filters
foreach ($this->filters as $item) {
/** @var Matcher $matcher */
$matcher = $item['matcher'];
/** @var Filter $filter */
$filter = $item['filter'];
if ($matcher->matches($object, $property->getName())) {
$filter->apply(
$object,
$property->getName(),
function ($object) {
return $this->recursiveCopy($object);
}
);
// If a filter matches, we stop processing this property
return;
}
}
$property->setAccessible(true);
$propertyValue = $property->getValue($object);
// Copy the property
$property->setValue($object, $this->recursiveCopy($propertyValue));
}
/**
* 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)
{
$matched = $this->first(
$filterRecords,
function (array $record) use ($var) {
/* @var TypeMatcher $matcher */
$matcher = $record['matcher'];
return $matcher->matches($var);
}
);
return isset($matched) ? $matched['filter'] : null;
}
/**
* Returns first element that matches predicate, NULL if no such element found.
* @param array $elements
* @param callable $predicate Predicate arguments are: element.
* @return mixed|null
*/
private function first(array $elements, callable $predicate)
{
foreach ($elements as $element) {
if (call_user_func($predicate, $element)) {
return $element;
}
}
return null;
}
}

View File

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

View File

@@ -0,0 +1,31 @@
<?php
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
use ReflectionProperty;
/**
* Set a null value for a property
*/
class DoctrineCollectionFilter implements Filter
{
/**
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new ReflectionProperty($object, $property);
$reflectionProperty->setAccessible(true);
$oldCollection = $reflectionProperty->getValue($object);
$newCollection = $oldCollection->map(
function ($item) use ($objectCopier) {
return $objectCopier($item);
}
);
$reflectionProperty->setValue($object, $newCollection);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace DeepCopy\Filter\Doctrine;
use DeepCopy\Filter\Filter;
use Doctrine\Common\Collections\ArrayCollection;
class DoctrineEmptyCollectionFilter implements Filter
{
/**
* Apply the filter to the object.
*
* @param object $object
* @param string $property
* @param callable $objectCopier
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new \ReflectionProperty($object, $property);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, new ArrayCollection());
}
}

View File

@@ -0,0 +1,20 @@
<?php
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.
*/
class DoctrineProxyFilter implements Filter
{
/**
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$object->__load();
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace DeepCopy\Filter;
/**
* Filter to apply to a property while copying an object
*/
interface Filter
{
/**
* Apply the filter to the object.
* @param object $object
* @param string $property
* @param callable $objectCopier
*/
public function apply($object, $property, $objectCopier);
}

View File

@@ -0,0 +1,17 @@
<?php
namespace DeepCopy\Filter;
/**
* Keep the value of a property
*/
class KeepFilter implements Filter
{
/**
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
// Nothing to do
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace DeepCopy\Filter;
/**
* Replace the value of a property
*/
class ReplaceFilter implements Filter
{
/**
* @var callable
*/
protected $callback;
/**
* @param callable $callable Will be called to get the new value for each property to replace
*/
public function __construct(callable $callable)
{
$this->callback = $callable;
}
/**
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new \ReflectionProperty($object, $property);
$reflectionProperty->setAccessible(true);
$value = call_user_func($this->callback, $reflectionProperty->getValue($object));
$reflectionProperty->setValue($object, $value);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace DeepCopy\Filter;
use ReflectionProperty;
/**
* Set a null value for a property
*/
class SetNullFilter implements Filter
{
/**
* {@inheritdoc}
*/
public function apply($object, $property, $objectCopier)
{
$reflectionProperty = new ReflectionProperty($object, $property);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, null);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace DeepCopy\Matcher\Doctrine;
use DeepCopy\Matcher\Matcher;
use Doctrine\Common\Persistence\Proxy;
/**
* Match a Doctrine Proxy class.
*/
class DoctrineProxyMatcher implements Matcher
{
/**
* {@inheritdoc}
*/
public function matches($object, $property)
{
return $object instanceof Proxy;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace DeepCopy\Matcher;
/**
* Matcher interface
*/
interface Matcher
{
/**
* @param object $object
* @param string $property
* @return boolean
*/
public function matches($object, $property);
}

View File

@@ -0,0 +1,37 @@
<?php
namespace DeepCopy\Matcher;
/**
* Match a specific property of a specific class
*/
class PropertyMatcher implements Matcher
{
/**
* @var string
*/
private $class;
/**
* @var string
*/
private $property;
/**
* @param string $class Class name
* @param string $property Property name
*/
public function __construct($class, $property)
{
$this->class = $class;
$this->property = $property;
}
/**
* {@inheritdoc}
*/
public function matches($object, $property)
{
return ($object instanceof $this->class) && ($property == $this->property);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace DeepCopy\Matcher;
/**
* Match a property by its name
*/
class PropertyNameMatcher implements Matcher
{
/**
* @var string
*/
private $property;
/**
* @param string $property Property name
*/
public function __construct($property)
{
$this->property = $property;
}
/**
* {@inheritdoc}
*/
public function matches($object, $property)
{
return $property == $this->property;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace DeepCopy\Matcher;
use ReflectionProperty;
/**
* Match 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.
*/
class PropertyTypeMatcher implements Matcher
{
/**
* @var string
*/
private $propertyType;
/**
* @param string $propertyType Property type
*/
public function __construct($propertyType)
{
$this->propertyType = $propertyType;
}
/**
* {@inheritdoc}
*/
public function matches($object, $property)
{
$reflectionProperty = new ReflectionProperty($object, $property);
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($object) instanceof $this->propertyType;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace DeepCopy\Reflection;
class ReflectionHelper
{
/**
* Retrieves all properties (including private ones), from object and all its ancestors.
*
* Standard \ReflectionClass->getProperties() does not return private properties from ancestor classes.
*
* @author muratyaman@gmail.com
* @see http://php.net/manual/en/reflectionclass.getproperties.php
*
* @param \ReflectionClass $ref
* @return \ReflectionProperty[]
*/
public static function getProperties(\ReflectionClass $ref)
{
$props = $ref->getProperties();
$propsArr = array();
foreach ($props as $prop) {
$propertyName = $prop->getName();
$propsArr[$propertyName] = $prop;
}
if ($parentClass = $ref->getParentClass()) {
$parentPropsArr = self::getProperties($parentClass);
foreach ($propsArr as $key => $property) {
$parentPropsArr[$key] = $property;
}
return $parentPropsArr;
}
return $propsArr;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace DeepCopy\TypeFilter;
class ReplaceFilter implements TypeFilter
{
/**
* @var callable
*/
protected $callback;
/**
* @param callable $callable Will be called to get the new value for each element to replace
*/
public function __construct(callable $callable)
{
$this->callback = $callable;
}
/**
* {@inheritdoc}
*/
public function apply($element)
{
return call_user_func($this->callback, $element);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace DeepCopy\TypeFilter;
class ShallowCopyFilter implements TypeFilter
{
/**
* {@inheritdoc}
*/
public function apply($element)
{
return clone $element;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace DeepCopy\TypeFilter\Spl;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
class SplDoublyLinkedList implements TypeFilter
{
/**
* @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,12 @@
<?php
namespace DeepCopy\TypeFilter;
interface TypeFilter
{
/**
* Apply the filter to the object.
* @param mixed $element
*/
public function apply($element);
}

View File

@@ -0,0 +1,31 @@
<?php
namespace DeepCopy\TypeMatcher;
/**
* TypeMatcher class
*/
class TypeMatcher
{
/**
* @var string
*/
private $type;
/**
* @param string $type
*/
public function __construct($type)
{
$this->type = $type;
}
/**
* @param $element
* @return boolean
*/
public function matches($element)
{
return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type;
}
}