/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php

https://gitlab.com/codegod/evricommunity · PHP · 246 lines · 143 code · 37 blank · 66 comment · 13 complexity · 361b7249c3d9ec7728c6372cb533b95c MD5 · raw file

  1. <?php
  2. namespace DeepCopy;
  3. use DeepCopy\Exception\CloneException;
  4. use DeepCopy\Filter\Filter;
  5. use DeepCopy\Matcher\Matcher;
  6. use DeepCopy\TypeFilter\Spl\SplDoublyLinkedList;
  7. use DeepCopy\TypeFilter\TypeFilter;
  8. use DeepCopy\TypeMatcher\TypeMatcher;
  9. use ReflectionProperty;
  10. use DeepCopy\Reflection\ReflectionHelper;
  11. /**
  12. * DeepCopy
  13. */
  14. class DeepCopy
  15. {
  16. /**
  17. * @var array
  18. */
  19. private $hashMap = [];
  20. /**
  21. * Filters to apply.
  22. * @var array
  23. */
  24. private $filters = [];
  25. /**
  26. * Type Filters to apply.
  27. * @var array
  28. */
  29. private $typeFilters = [];
  30. private $skipUncloneable = false;
  31. /**
  32. * @var bool
  33. */
  34. private $useCloneMethod;
  35. /**
  36. * @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used
  37. * instead of the regular deep cloning.
  38. */
  39. public function __construct($useCloneMethod = false)
  40. {
  41. $this->useCloneMethod = $useCloneMethod;
  42. $this->addTypeFilter(new SplDoublyLinkedList($this), new TypeMatcher('\SplDoublyLinkedList'));
  43. }
  44. /**
  45. * Cloning uncloneable properties won't throw exception.
  46. * @param $skipUncloneable
  47. * @return $this
  48. */
  49. public function skipUncloneable($skipUncloneable = true)
  50. {
  51. $this->skipUncloneable = $skipUncloneable;
  52. return $this;
  53. }
  54. /**
  55. * Perform a deep copy of the object.
  56. * @param mixed $object
  57. * @return mixed
  58. */
  59. public function copy($object)
  60. {
  61. $this->hashMap = [];
  62. return $this->recursiveCopy($object);
  63. }
  64. public function addFilter(Filter $filter, Matcher $matcher)
  65. {
  66. $this->filters[] = [
  67. 'matcher' => $matcher,
  68. 'filter' => $filter,
  69. ];
  70. }
  71. public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
  72. {
  73. $this->typeFilters[] = [
  74. 'matcher' => $matcher,
  75. 'filter' => $filter,
  76. ];
  77. }
  78. private function recursiveCopy($var)
  79. {
  80. // Matches Type Filter
  81. if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) {
  82. return $filter->apply($var);
  83. }
  84. // Resource
  85. if (is_resource($var)) {
  86. return $var;
  87. }
  88. // Array
  89. if (is_array($var)) {
  90. return $this->copyArray($var);
  91. }
  92. // Scalar
  93. if (! is_object($var)) {
  94. return $var;
  95. }
  96. // Object
  97. return $this->copyObject($var);
  98. }
  99. /**
  100. * Copy an array
  101. * @param array $array
  102. * @return array
  103. */
  104. private function copyArray(array $array)
  105. {
  106. foreach ($array as $key => $value) {
  107. $array[$key] = $this->recursiveCopy($value);
  108. }
  109. return $array;
  110. }
  111. /**
  112. * Copy an object
  113. * @param object $object
  114. * @return object
  115. */
  116. private function copyObject($object)
  117. {
  118. $objectHash = spl_object_hash($object);
  119. if (isset($this->hashMap[$objectHash])) {
  120. return $this->hashMap[$objectHash];
  121. }
  122. $reflectedObject = new \ReflectionObject($object);
  123. if (false === $isCloneable = $reflectedObject->isCloneable() and $this->skipUncloneable) {
  124. $this->hashMap[$objectHash] = $object;
  125. return $object;
  126. }
  127. if (false === $isCloneable) {
  128. throw new CloneException(sprintf(
  129. 'Class "%s" is not cloneable.',
  130. $reflectedObject->getName()
  131. ));
  132. }
  133. $newObject = clone $object;
  134. $this->hashMap[$objectHash] = $newObject;
  135. if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) {
  136. return $object;
  137. }
  138. if ($newObject instanceof \DateTimeInterface) {
  139. return $newObject;
  140. }
  141. foreach (ReflectionHelper::getProperties($reflectedObject) as $property) {
  142. $this->copyObjectProperty($newObject, $property);
  143. }
  144. return $newObject;
  145. }
  146. private function copyObjectProperty($object, ReflectionProperty $property)
  147. {
  148. // Ignore static properties
  149. if ($property->isStatic()) {
  150. return;
  151. }
  152. // Apply the filters
  153. foreach ($this->filters as $item) {
  154. /** @var Matcher $matcher */
  155. $matcher = $item['matcher'];
  156. /** @var Filter $filter */
  157. $filter = $item['filter'];
  158. if ($matcher->matches($object, $property->getName())) {
  159. $filter->apply(
  160. $object,
  161. $property->getName(),
  162. function ($object) {
  163. return $this->recursiveCopy($object);
  164. }
  165. );
  166. // If a filter matches, we stop processing this property
  167. return;
  168. }
  169. }
  170. $property->setAccessible(true);
  171. $propertyValue = $property->getValue($object);
  172. // Copy the property
  173. $property->setValue($object, $this->recursiveCopy($propertyValue));
  174. }
  175. /**
  176. * Returns first filter that matches variable, NULL if no such filter found.
  177. * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and
  178. * 'matcher' with value of type {@see TypeMatcher}
  179. * @param mixed $var
  180. * @return TypeFilter|null
  181. */
  182. private function getFirstMatchedTypeFilter(array $filterRecords, $var)
  183. {
  184. $matched = $this->first(
  185. $filterRecords,
  186. function (array $record) use ($var) {
  187. /* @var TypeMatcher $matcher */
  188. $matcher = $record['matcher'];
  189. return $matcher->matches($var);
  190. }
  191. );
  192. return isset($matched) ? $matched['filter'] : null;
  193. }
  194. /**
  195. * Returns first element that matches predicate, NULL if no such element found.
  196. * @param array $elements
  197. * @param callable $predicate Predicate arguments are: element.
  198. * @return mixed|null
  199. */
  200. private function first(array $elements, callable $predicate)
  201. {
  202. foreach ($elements as $element) {
  203. if (call_user_func($predicate, $element)) {
  204. return $element;
  205. }
  206. }
  207. return null;
  208. }
  209. }