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

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