PageRenderTime 28ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php

https://gitlab.com/Pasantias/pasantiasASLG
PHP | 273 lines | 126 code | 36 blank | 111 comment | 17 complexity | b471e9766e58011c7c53e5841ca6f536 MD5 | raw file
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\Instantiator;
  20. use Closure;
  21. use Doctrine\Instantiator\Exception\InvalidArgumentException;
  22. use Doctrine\Instantiator\Exception\UnexpectedValueException;
  23. use Exception;
  24. use ReflectionClass;
  25. /**
  26. * {@inheritDoc}
  27. *
  28. * @author Marco Pivetta <ocramius@gmail.com>
  29. */
  30. final class Instantiator implements InstantiatorInterface
  31. {
  32. /**
  33. * Markers used internally by PHP to define whether {@see \unserialize} should invoke
  34. * the method {@see \Serializable::unserialize()} when dealing with classes implementing
  35. * the {@see \Serializable} interface.
  36. */
  37. const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C';
  38. const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
  39. /**
  40. * @var \Closure[] of {@see \Closure} instances used to instantiate specific classes
  41. */
  42. private static $cachedInstantiators = array();
  43. /**
  44. * @var object[] of objects that can directly be cloned
  45. */
  46. private static $cachedCloneables = array();
  47. /**
  48. * {@inheritDoc}
  49. */
  50. public function instantiate($className)
  51. {
  52. if (isset(self::$cachedCloneables[$className])) {
  53. return clone self::$cachedCloneables[$className];
  54. }
  55. if (isset(self::$cachedInstantiators[$className])) {
  56. $factory = self::$cachedInstantiators[$className];
  57. return $factory();
  58. }
  59. return $this->buildAndCacheFromFactory($className);
  60. }
  61. /**
  62. * Builds the requested object and caches it in static properties for performance
  63. *
  64. * @param string $className
  65. *
  66. * @return object
  67. */
  68. private function buildAndCacheFromFactory($className)
  69. {
  70. $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className);
  71. $instance = $factory();
  72. if ($this->isSafeToClone(new ReflectionClass($instance))) {
  73. self::$cachedCloneables[$className] = clone $instance;
  74. }
  75. return $instance;
  76. }
  77. /**
  78. * Builds a {@see \Closure} capable of instantiating the given $className without
  79. * invoking its constructor.
  80. *
  81. * @param string $className
  82. *
  83. * @return Closure
  84. */
  85. private function buildFactory($className)
  86. {
  87. $reflectionClass = $this->getReflectionClass($className);
  88. if ($this->isInstantiableViaReflection($reflectionClass)) {
  89. return function () use ($reflectionClass) {
  90. return $reflectionClass->newInstanceWithoutConstructor();
  91. };
  92. }
  93. $serializedString = sprintf(
  94. '%s:%d:"%s":0:{}',
  95. $this->getSerializationFormat($reflectionClass),
  96. strlen($className),
  97. $className
  98. );
  99. $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
  100. return function () use ($serializedString) {
  101. return unserialize($serializedString);
  102. };
  103. }
  104. /**
  105. * @param string $className
  106. *
  107. * @return ReflectionClass
  108. *
  109. * @throws InvalidArgumentException
  110. */
  111. private function getReflectionClass($className)
  112. {
  113. if (! class_exists($className)) {
  114. throw InvalidArgumentException::fromNonExistingClass($className);
  115. }
  116. $reflection = new ReflectionClass($className);
  117. if ($reflection->isAbstract()) {
  118. throw InvalidArgumentException::fromAbstractClass($reflection);
  119. }
  120. return $reflection;
  121. }
  122. /**
  123. * @param ReflectionClass $reflectionClass
  124. * @param string $serializedString
  125. *
  126. * @throws UnexpectedValueException
  127. *
  128. * @return void
  129. */
  130. private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, $serializedString)
  131. {
  132. set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) {
  133. $error = UnexpectedValueException::fromUncleanUnSerialization(
  134. $reflectionClass,
  135. $message,
  136. $code,
  137. $file,
  138. $line
  139. );
  140. });
  141. $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
  142. restore_error_handler();
  143. if ($error) {
  144. throw $error;
  145. }
  146. }
  147. /**
  148. * @param ReflectionClass $reflectionClass
  149. * @param string $serializedString
  150. *
  151. * @throws UnexpectedValueException
  152. *
  153. * @return void
  154. */
  155. private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString)
  156. {
  157. try {
  158. unserialize($serializedString);
  159. } catch (Exception $exception) {
  160. restore_error_handler();
  161. throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
  162. }
  163. }
  164. /**
  165. * @param ReflectionClass $reflectionClass
  166. *
  167. * @return bool
  168. */
  169. private function isInstantiableViaReflection(ReflectionClass $reflectionClass)
  170. {
  171. if (\PHP_VERSION_ID >= 50600) {
  172. return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
  173. }
  174. return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass);
  175. }
  176. /**
  177. * Verifies whether the given class is to be considered internal
  178. *
  179. * @param ReflectionClass $reflectionClass
  180. *
  181. * @return bool
  182. */
  183. private function hasInternalAncestors(ReflectionClass $reflectionClass)
  184. {
  185. do {
  186. if ($reflectionClass->isInternal()) {
  187. return true;
  188. }
  189. } while ($reflectionClass = $reflectionClass->getParentClass());
  190. return false;
  191. }
  192. /**
  193. * Verifies if the given PHP version implements the `Serializable` interface serialization
  194. * with an incompatible serialization format. If that's the case, use serialization marker
  195. * "C" instead of "O".
  196. *
  197. * @link http://news.php.net/php.internals/74654
  198. *
  199. * @param ReflectionClass $reflectionClass
  200. *
  201. * @return string the serialization format marker, either self::SERIALIZATION_FORMAT_USE_UNSERIALIZER
  202. * or self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER
  203. */
  204. private function getSerializationFormat(ReflectionClass $reflectionClass)
  205. {
  206. if ($this->isPhpVersionWithBrokenSerializationFormat()
  207. && $reflectionClass->implementsInterface('Serializable')
  208. ) {
  209. return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER;
  210. }
  211. return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER;
  212. }
  213. /**
  214. * Checks whether the current PHP runtime uses an incompatible serialization format
  215. *
  216. * @return bool
  217. */
  218. private function isPhpVersionWithBrokenSerializationFormat()
  219. {
  220. return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513;
  221. }
  222. /**
  223. * Checks if a class is cloneable
  224. *
  225. * @param ReflectionClass $reflection
  226. *
  227. * @return bool
  228. */
  229. private function isSafeToClone(ReflectionClass $reflection)
  230. {
  231. if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) {
  232. return false;
  233. }
  234. // not cloneable if it implements `__clone`, as we want to avoid calling it
  235. return ! $reflection->hasMethod('__clone');
  236. }
  237. }