PageRenderTime 42ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/jhonn/rest
PHP | 254 lines | 119 code | 34 blank | 101 comment | 17 complexity | 7e9f721a81ddaead623903df9ef03107 MD5 | raw file
Possible License(s): BSD-3-Clause
  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. $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className);
  60. $instance = $factory();
  61. $reflection = new ReflectionClass($instance);
  62. if ($this->isSafeToClone($reflection)) {
  63. self::$cachedCloneables[$className] = clone $instance;
  64. }
  65. return $instance;
  66. }
  67. /**
  68. * @internal
  69. * @private
  70. *
  71. * Builds a {@see \Closure} capable of instantiating the given $className without
  72. * invoking its constructor.
  73. * This method is only exposed as public because of PHP 5.3 compatibility. Do not
  74. * use this method in your own code
  75. *
  76. * @param string $className
  77. *
  78. * @return Closure
  79. */
  80. public function buildFactory($className)
  81. {
  82. $reflectionClass = $this->getReflectionClass($className);
  83. if ($this->isInstantiableViaReflection($reflectionClass)) {
  84. return function () use ($reflectionClass) {
  85. return $reflectionClass->newInstanceWithoutConstructor();
  86. };
  87. }
  88. $serializedString = sprintf(
  89. '%s:%d:"%s":0:{}',
  90. $this->getSerializationFormat($reflectionClass),
  91. strlen($className),
  92. $className
  93. );
  94. $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
  95. return function () use ($serializedString) {
  96. return unserialize($serializedString);
  97. };
  98. }
  99. /**
  100. * @param string $className
  101. *
  102. * @return ReflectionClass
  103. *
  104. * @throws InvalidArgumentException
  105. */
  106. private function getReflectionClass($className)
  107. {
  108. if (! class_exists($className)) {
  109. throw InvalidArgumentException::fromNonExistingClass($className);
  110. }
  111. $reflection = new ReflectionClass($className);
  112. if ($reflection->isAbstract()) {
  113. throw InvalidArgumentException::fromAbstractClass($reflection);
  114. }
  115. return $reflection;
  116. }
  117. /**
  118. * @param ReflectionClass $reflectionClass
  119. * @param string $serializedString
  120. *
  121. * @throws UnexpectedValueException
  122. *
  123. * @return void
  124. */
  125. private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString)
  126. {
  127. set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) {
  128. $error = UnexpectedValueException::fromUncleanUnSerialization(
  129. $reflectionClass,
  130. $message,
  131. $code,
  132. $file,
  133. $line
  134. );
  135. });
  136. try {
  137. unserialize($serializedString);
  138. } catch (Exception $exception) {
  139. restore_error_handler();
  140. throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
  141. }
  142. restore_error_handler();
  143. if ($error) {
  144. throw $error;
  145. }
  146. }
  147. /**
  148. * @param ReflectionClass $reflectionClass
  149. *
  150. * @return bool
  151. */
  152. private function isInstantiableViaReflection(ReflectionClass $reflectionClass)
  153. {
  154. if (\PHP_VERSION_ID >= 50600) {
  155. return ! ($reflectionClass->isInternal() && $reflectionClass->isFinal());
  156. }
  157. return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass);
  158. }
  159. /**
  160. * Verifies whether the given class is to be considered internal
  161. *
  162. * @param ReflectionClass $reflectionClass
  163. *
  164. * @return bool
  165. */
  166. private function hasInternalAncestors(ReflectionClass $reflectionClass)
  167. {
  168. do {
  169. if ($reflectionClass->isInternal()) {
  170. return true;
  171. }
  172. } while ($reflectionClass = $reflectionClass->getParentClass());
  173. return false;
  174. }
  175. /**
  176. * Verifies if the given PHP version implements the `Serializable` interface serialization
  177. * with an incompatible serialization format. If that's the case, use serialization marker
  178. * "C" instead of "O".
  179. *
  180. * @link http://news.php.net/php.internals/74654
  181. *
  182. * @param ReflectionClass $reflectionClass
  183. *
  184. * @return string the serialization format marker, either self::SERIALIZATION_FORMAT_USE_UNSERIALIZER
  185. * or self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER
  186. */
  187. private function getSerializationFormat(ReflectionClass $reflectionClass)
  188. {
  189. if ($this->isPhpVersionWithBrokenSerializationFormat()
  190. && $reflectionClass->implementsInterface('Serializable')
  191. ) {
  192. return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER;
  193. }
  194. return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER;
  195. }
  196. /**
  197. * Checks whether the current PHP runtime uses an incompatible serialization format
  198. *
  199. * @return bool
  200. */
  201. private function isPhpVersionWithBrokenSerializationFormat()
  202. {
  203. return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513;
  204. }
  205. /**
  206. * Checks if a class is cloneable
  207. *
  208. * @param ReflectionClass $reflection
  209. *
  210. * @return bool
  211. */
  212. private function isSafeToClone(ReflectionClass $reflection)
  213. {
  214. if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) {
  215. return false;
  216. }
  217. // not cloneable if it implements `__clone`, as we want to avoid calling it
  218. return ! $reflection->hasMethod('__clone');
  219. }
  220. }