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

/Classes/TYPO3/FLOW3/Reflection/ReflectionService.php

https://github.com/christianjul/FLOW3-Composer
PHP | 1746 lines | 1218 code | 103 blank | 425 comment | 102 complexity | fdf66f6a7b2a4df3491932b27ce586dd MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Reflection;
  3. /* *
  4. * This script belongs to the FLOW3 framework. *
  5. * *
  6. * It is free software; you can redistribute it and/or modify it under *
  7. * the terms of the GNU Lesser General Public License, either version 3 *
  8. * of the License, or (at your option) any later version. *
  9. * *
  10. * The TYPO3 project - inspiring people to share! *
  11. * */
  12. use TYPO3\FLOW3\Annotations as FLOW3;
  13. use TYPO3\FLOW3\Utility\Files;
  14. /**
  15. * A service for acquiring reflection based information in a performant way. This
  16. * service also builds up class schema information which is used by the FLOW3's
  17. * persistence layer.
  18. *
  19. * Reflection of classes of all active packages is triggered through the bootstrap's
  20. * initializeReflectionService() method. In a development context, single classes
  21. * may be re-reflected once files are modified whereas in a production context
  22. * reflection is done once and successive requests read from the frozen caches for
  23. * performance reasons.
  24. *
  25. * The list of available classes is determined by the CompiletimeObjectManager which
  26. * also triggers the initial build of reflection data in this service.
  27. *
  28. * The invalidation of reflection cache entries is done by the CacheManager which
  29. * in turn is triggered by signals sent by the file monitor.
  30. *
  31. * The internal representation of cache data is optimized for memory consumption and
  32. * speed by using constants which have an integer value.
  33. *
  34. * @api
  35. * @FLOW3\Scope("singleton")
  36. * @FLOW3\Proxy(false)
  37. */
  38. class ReflectionService {
  39. const
  40. VISIBILITY_PRIVATE = 1,
  41. VISIBILITY_PROTECTED = 2,
  42. VISIBILITY_PUBLIC = 3,
  43. // Implementations of an interface
  44. DATA_INTERFACE_IMPLEMENTATIONS = 1,
  45. // Implemented interfaces of a class
  46. DATA_CLASS_INTERFACES = 2,
  47. // Subclasses of a class
  48. DATA_CLASS_SUBCLASSES = 3,
  49. // Class tag values
  50. DATA_CLASS_TAGS_VALUES = 4,
  51. // Class annotations
  52. DATA_CLASS_ANNOTATIONS = 5,
  53. DATA_CLASS_ABSTRACT = 6,
  54. DATA_CLASS_FINAL = 7,
  55. DATA_CLASS_METHODS = 8,
  56. DATA_CLASS_PROPERTIES = 9,
  57. DATA_METHOD_FINAL = 10,
  58. DATA_METHOD_STATIC = 11,
  59. DATA_METHOD_VISIBILITY = 12,
  60. DATA_METHOD_PARAMETERS = 13,
  61. DATA_PROPERTY_TAGS_VALUES = 14,
  62. DATA_PROPERTY_ANNOTATIONS = 15,
  63. DATA_PROPERTY_VISIBILITY = 24,
  64. DATA_PARAMETER_POSITION = 16,
  65. DATA_PARAMETER_OPTIONAL = 17,
  66. DATA_PARAMETER_TYPE = 18,
  67. DATA_PARAMETER_ARRAY = 19,
  68. DATA_PARAMETER_CLASS = 20,
  69. DATA_PARAMETER_ALLOWS_NULL = 21,
  70. DATA_PARAMETER_DEFAULT_VALUE = 22,
  71. DATA_PARAMETER_BY_REFERENCE = 23;
  72. /**
  73. * @var \Doctrine\Common\Annotations\Reader
  74. */
  75. protected $annotationReader;
  76. /**
  77. * @var \TYPO3\FLOW3\Core\ClassLoader
  78. */
  79. protected $classLoader;
  80. /**
  81. * @var array
  82. */
  83. protected $availableClassNames = array();
  84. /**
  85. * @var \TYPO3\FLOW3\Cache\Frontend\StringFrontend
  86. */
  87. protected $statusCache;
  88. /**
  89. * @var \TYPO3\FLOW3\Cache\Frontend\VariableFrontend
  90. */
  91. protected $reflectionDataCompiletimeCache;
  92. /**
  93. * @var \TYPO3\FLOW3\Cache\Frontend\VariableFrontend
  94. */
  95. protected $reflectionDataRuntimeCache;
  96. /**
  97. * @var \TYPO3\FLOW3\Cache\Frontend\VariableFrontend
  98. */
  99. protected $classSchemataRuntimeCache;
  100. /**
  101. * @var \TYPO3\FLOW3\Log\SystemLoggerInterface
  102. */
  103. protected $systemLogger;
  104. /**
  105. * @var \TYPO3\FLOW3\Package\PackageManagerInterface
  106. */
  107. protected $packageManager;
  108. /**
  109. * @var \TYPO3\FLOW3\Utility\Environment
  110. */
  111. protected $environment;
  112. /**
  113. * @var \TYPO3\FLOW3\Core\ApplicationContext
  114. */
  115. protected $context;
  116. /**
  117. * In Production context, with frozen caches, this flag will be TRUE
  118. * @var boolean
  119. */
  120. protected $loadFromClassSchemaRuntimeCache = FALSE;
  121. /**
  122. * @var array
  123. */
  124. protected $settings;
  125. /**
  126. * Array of annotation classnames and the names of classes which are annotated with them
  127. * @var array
  128. */
  129. protected $annotatedClasses = array();
  130. /**
  131. * Array of method annotations and the classes and methods which are annotated with them
  132. * @var array
  133. */
  134. protected $classesByMethodAnnotations = array();
  135. /**
  136. * Schemata of all classes which can be persisted
  137. * @var array<\TYPO3\FLOW3\Reflection\ClassSchema>
  138. */
  139. protected $classSchemata = array();
  140. /**
  141. * An array of class names which are currently being forgotten by forgetClass(). Acts as a safeguard against infinite loops.
  142. * @var array
  143. */
  144. protected $classesCurrentlyBeingForgotten = array();
  145. /**
  146. * Array with reflection information indexed by class name
  147. * @var array
  148. */
  149. protected $classReflectionData = array();
  150. /**
  151. * Array with updated reflection information (e.g. in Development context after classes have changed)
  152. * @var array
  153. */
  154. protected $updatedReflectionData = array();
  155. /**
  156. * Sets the status cache
  157. *
  158. * The cache must be set before initializing the Reflection Service
  159. *
  160. * @param \TYPO3\FLOW3\Cache\Frontend\StringFrontend $cache Cache for the reflection service
  161. * @return void
  162. */
  163. public function setStatusCache(\TYPO3\FLOW3\Cache\Frontend\StringFrontend $cache) {
  164. $this->statusCache = $cache;
  165. $this->statusCache->getBackend()->initializeObject();
  166. }
  167. /**
  168. * Sets the compiletime data cache
  169. *
  170. * @param \TYPO3\FLOW3\Cache\Frontend\VariableFrontend $cache Cache for the reflection service
  171. * @return void
  172. */
  173. public function setReflectionDataCompiletimeCache(\TYPO3\FLOW3\Cache\Frontend\VariableFrontend $cache) {
  174. $this->reflectionDataCompiletimeCache = $cache;
  175. }
  176. /**
  177. * Sets the runtime data cache
  178. *
  179. * @param \TYPO3\FLOW3\Cache\Frontend\VariableFrontend $cache Cache for the reflection service
  180. * @return void
  181. */
  182. public function setReflectionDataRuntimeCache(\TYPO3\FLOW3\Cache\Frontend\VariableFrontend $cache) {
  183. $this->reflectionDataRuntimeCache = $cache;
  184. }
  185. /**
  186. * Sets the dedicated class schema cache for runtime purposes
  187. *
  188. * @param \TYPO3\FLOW3\Cache\Frontend\VariableFrontend $cache
  189. * @return void
  190. */
  191. public function setClassSchemataRuntimeCache(\TYPO3\FLOW3\Cache\Frontend\VariableFrontend $cache) {
  192. $this->classSchemataRuntimeCache = $cache;
  193. }
  194. /**
  195. * @param array $settings Settings of the FLOW3 package
  196. * @return void
  197. */
  198. public function injectSettings(array $settings) {
  199. $this->settings = $settings;
  200. }
  201. /**
  202. * @param \TYPO3\FLOW3\Log\SystemLoggerInterface $systemLogger
  203. * @return void
  204. */
  205. public function injectSystemLogger(\TYPO3\FLOW3\Log\SystemLoggerInterface $systemLogger) {
  206. $this->systemLogger = $systemLogger;
  207. }
  208. /**
  209. * @param \TYPO3\FLOW3\Core\ClassLoader $classLoader
  210. * @return void
  211. */
  212. public function injectClassLoader(\TYPO3\FLOW3\Core\ClassLoader $classLoader) {
  213. $this->classLoader = $classLoader;
  214. }
  215. /**
  216. * @param \TYPO3\FLOW3\Package\PackageManagerInterface $packageManager
  217. * @return void
  218. */
  219. public function injectPackageManager(\TYPO3\FLOW3\Package\PackageManagerInterface $packageManager) {
  220. $this->packageManager = $packageManager;
  221. }
  222. /**
  223. * @param \TYPO3\FLOW3\Utility\Environment $environment
  224. * @return void
  225. */
  226. public function injectEnvironment(\TYPO3\FLOW3\Utility\Environment $environment) {
  227. $this->environment = $environment;
  228. }
  229. /**
  230. * Initializes this service.
  231. *
  232. * This method must be run only after all dependencies have been injected.
  233. *
  234. * @param \TYPO3\FLOW3\Core\Bootstrap $bootstrap
  235. * @return void
  236. */
  237. public function initialize(\TYPO3\FLOW3\Core\Bootstrap $bootstrap) {
  238. $this->context = $bootstrap->getContext();
  239. if ($this->context->isProduction() && $this->reflectionDataRuntimeCache->getBackend()->isFrozen()) {
  240. $this->classReflectionData = $this->reflectionDataRuntimeCache->get('__classNames');
  241. $this->annotatedClasses = $this->reflectionDataRuntimeCache->get('__annotatedClasses');
  242. $this->loadFromClassSchemaRuntimeCache = TRUE;
  243. } else {
  244. $this->loadClassReflectionCompiletimeCache();
  245. }
  246. $this->annotationReader = new \Doctrine\Common\Annotations\AnnotationReader();
  247. foreach ($this->settings['reflection']['ignoredTags'] as $tag) {
  248. \Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredName($tag);
  249. }
  250. \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($this->classLoader, 'loadClass'));
  251. }
  252. /**
  253. * Builds the reflection data cache during compile time.
  254. *
  255. * This method is called by the CompiletimeObjectManager which also determines
  256. * the list of classes to consider for reflection.
  257. *
  258. * @param array $availableClassNames List of all class names to consider for reflection
  259. * @return void
  260. */
  261. public function buildReflectionData(array $availableClassNames) {
  262. $this->availableClassNames = $availableClassNames;
  263. $this->forgetChangedClasses();
  264. $this->reflectEmergedClasses();
  265. }
  266. /**
  267. * Tells if the specified class is known to this reflection service and
  268. * reflection information is available.
  269. *
  270. * @param string $className Name of the class
  271. * @return boolean If the class is reflected by this service
  272. * @api
  273. */
  274. public function isClassReflected($className) {
  275. if ($className[0] === '\\') {
  276. $className = substr($className, 1);
  277. }
  278. return isset($this->classReflectionData[$className]);
  279. }
  280. /**
  281. * Returns the names of all classes known to this reflection service.
  282. *
  283. * @return array Class names
  284. * @api
  285. */
  286. public function getAllClassNames() {
  287. return array_keys($this->classReflectionData);
  288. }
  289. /**
  290. * Searches for and returns the class name of the default implementation of the given
  291. * interface name. If no class implementing the interface was found or more than one
  292. * implementation was found in the package defining the interface, FALSE is returned.
  293. *
  294. * @param string $interfaceName Name of the interface
  295. * @return mixed Either the class name of the default implementation for the object type or FALSE
  296. * @throws \InvalidArgumentException if the given interface does not exist
  297. * @api
  298. */
  299. public function getDefaultImplementationClassNameForInterface($interfaceName) {
  300. if ($interfaceName[0] === '\\') {
  301. $interfaceName = substr($interfaceName, 1);
  302. }
  303. if (interface_exists($interfaceName) === FALSE) throw new \InvalidArgumentException('"' . $interfaceName . '" does not exist or is not the name of an interface.', 1238769559);
  304. $this->loadOrReflectClassIfNecessary($interfaceName);
  305. $classNamesFound = isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS]) ? array_keys($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS]) : array();
  306. if (count($classNamesFound) === 1) {
  307. return $classNamesFound[0];
  308. }
  309. if (count($classNamesFound) === 2 && isset($this->classReflectionData['TYPO3\FLOW3\Object\Proxy\ProxyInterface'][self::DATA_INTERFACE_IMPLEMENTATIONS])) {
  310. if (isset($this->classReflectionData['TYPO3\FLOW3\Object\Proxy\ProxyInterface'][self::DATA_INTERFACE_IMPLEMENTATIONS][$classNamesFound[0]])) {
  311. return $classNamesFound[0];
  312. }
  313. if (isset($this->classReflectionData['TYPO3\FLOW3\Object\Proxy\ProxyInterface'][self::DATA_INTERFACE_IMPLEMENTATIONS][$classNamesFound[1]])) {
  314. return $classNamesFound[1];
  315. }
  316. }
  317. return FALSE;
  318. }
  319. /**
  320. * Searches for and returns all class names of implementations of the given object type
  321. * (interface name). If no class implementing the interface was found, an empty array is returned.
  322. *
  323. * @param string $interfaceName Name of the interface
  324. * @return array An array of class names of the default implementation for the object type
  325. * @throws \InvalidArgumentException if the given interface does not exist
  326. * @api
  327. */
  328. public function getAllImplementationClassNamesForInterface($interfaceName) {
  329. if ($interfaceName[0] === '\\') {
  330. $interfaceName = substr($interfaceName, 1);
  331. }
  332. $this->loadOrReflectClassIfNecessary($interfaceName);
  333. if (interface_exists($interfaceName) === FALSE) {
  334. throw new \InvalidArgumentException('"' . $interfaceName . '" does not exist or is not the name of an interface.', 1238769560);
  335. }
  336. return (isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS])) ? array_keys($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS]) : array();
  337. }
  338. /**
  339. * Searches for and returns all names of classes inheriting the specified class.
  340. * If no class inheriting the given class was found, an empty array is returned.
  341. *
  342. * @param string $className Name of the parent class
  343. * @return array An array of names of those classes being a direct or indirect subclass of the specified class
  344. * @throws \InvalidArgumentException if the given class does not exist
  345. * @api
  346. */
  347. public function getAllSubClassNamesForClass($className) {
  348. if ($className[0] === '\\') {
  349. $className = substr($className, 1);
  350. }
  351. if (class_exists($className) === FALSE) {
  352. throw new \InvalidArgumentException('"' . $className . '" does not exist or is not the name of a class.', 1257168042);
  353. }
  354. $this->loadOrReflectClassIfNecessary($className);
  355. return (isset($this->classReflectionData[$className][self::DATA_CLASS_SUBCLASSES])) ? array_keys($this->classReflectionData[$className][self::DATA_CLASS_SUBCLASSES]) : array();
  356. }
  357. /**
  358. * Returns the class name of the given object. This is a convenience
  359. * method that returns the expected class names even for proxy classes.
  360. *
  361. * @param object $object
  362. * @return string The class name of the given object
  363. */
  364. public function getClassNameByObject($object) {
  365. if ($object instanceof \Doctrine\ORM\Proxy\Proxy) {
  366. $className = get_parent_class($object);
  367. } else {
  368. $className = get_class($object);
  369. }
  370. return $className;
  371. }
  372. /**
  373. * Searches for and returns all names of classes which are tagged by the specified
  374. * annotation. If no classes were found, an empty array is returned.
  375. *
  376. * @param string $annotationClassName Name of the annotation class, for example "TYPO3\FLOW3\Annotations\Aspect"
  377. * @return array
  378. */
  379. public function getClassNamesByAnnotation($annotationClassName) {
  380. if ($annotationClassName[0] === '\\') {
  381. $annotationClassName = substr($annotationClassName, 1);
  382. }
  383. return (isset($this->annotatedClasses[$annotationClassName]) ? array_keys($this->annotatedClasses[$annotationClassName]) : array());
  384. }
  385. /**
  386. * Tells if the specified class has the given annotation
  387. *
  388. * @param string $className Name of the class
  389. * @param string $annotationClassName Annotation to check for
  390. * @return boolean
  391. * @api
  392. */
  393. public function isClassAnnotatedWith($className, $annotationClassName) {
  394. if ($className[0] === '\\') {
  395. $className = substr($className, 1);
  396. }
  397. if ($annotationClassName[0] === '\\') {
  398. $annotationClassName = substr($annotationClassName, 1);
  399. }
  400. return (isset($this->annotatedClasses[$annotationClassName][$className]));
  401. }
  402. /**
  403. * Returns the specified class annotations or an empty array
  404. *
  405. * @param string $className Name of the class
  406. * @param string $annotationClassName Annotation to filter for
  407. * @return array<object>
  408. */
  409. public function getClassAnnotations($className, $annotationClassName = NULL) {
  410. if ($className[0] === '\\') {
  411. $className = substr($className, 1);
  412. }
  413. if ($annotationClassName !== NULL && $annotationClassName[0] === '\\') {
  414. $annotationClassName = substr($annotationClassName, 1);
  415. }
  416. $this->loadOrReflectClassIfNecessary($className);
  417. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_ANNOTATIONS])) {
  418. return array();
  419. }
  420. if ($annotationClassName === NULL) {
  421. return $this->classReflectionData[$className][self::DATA_CLASS_ANNOTATIONS];
  422. } else {
  423. $annotations = array();
  424. foreach ($this->classReflectionData[$className][self::DATA_CLASS_ANNOTATIONS] as $annotation) {
  425. if ($annotation instanceof $annotationClassName) {
  426. $annotations[] = $annotation;
  427. }
  428. }
  429. return $annotations;
  430. }
  431. }
  432. /**
  433. * Returns the specified class annotation or NULL.
  434. *
  435. * If multiple annotations are set on the target you will
  436. * get one (random) instance of them.
  437. *
  438. * @param string $className Name of the class
  439. * @param string $annotationClassName Annotation to filter for
  440. * @return object
  441. */
  442. public function getClassAnnotation($className, $annotationClassName) {
  443. $annotations = $this->getClassAnnotations($className, $annotationClassName);
  444. return $annotations === array() ? NULL : current($annotations);
  445. }
  446. /**
  447. * Tells if the specified class implements the given interface
  448. *
  449. * @param string $className Name of the class
  450. * @param string $interfaceName interface to check for
  451. * @return boolean TRUE if the class implements $interfaceName, otherwise FALSE
  452. * @api
  453. */
  454. public function isClassImplementationOf($className, $interfaceName) {
  455. if ($className[0] === '\\') {
  456. $className = substr($className, 1);
  457. }
  458. if ($interfaceName[0] === '\\') {
  459. $interfaceName = substr($interfaceName, 1);
  460. }
  461. $this->loadOrReflectClassIfNecessary($className);
  462. $this->loadOrReflectClassIfNecessary($interfaceName);
  463. if (!isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS])) {
  464. return FALSE;
  465. }
  466. return (isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className]));
  467. }
  468. /**
  469. * Tells if the specified class is abstract or not
  470. *
  471. * @param string $className Name of the class to analyze
  472. * @return boolean TRUE if the class is abstract, otherwise FALSE
  473. * @api
  474. */
  475. public function isClassAbstract($className) {
  476. if ($className[0] === '\\') {
  477. $className = substr($className, 1);
  478. }
  479. $this->loadOrReflectClassIfNecessary($className);
  480. return isset($this->classReflectionData[$className][self::DATA_CLASS_ABSTRACT]);
  481. }
  482. /**
  483. * Tells if the specified class is final or not
  484. *
  485. * @param string $className Name of the class to analyze
  486. * @return boolean TRUE if the class is final, otherwise FALSE
  487. * @api
  488. */
  489. public function isClassFinal($className) {
  490. if ($className[0] === '\\') {
  491. $className = substr($className, 1);
  492. }
  493. $this->loadOrReflectClassIfNecessary($className);
  494. return isset($this->classReflectionData[$className][self::DATA_CLASS_FINAL]);
  495. }
  496. /**
  497. * Returns all class names of classes containing at least one method annotated
  498. * with the given annotation class
  499. *
  500. * @param string $annotationClassName The annotation class name for a method annotation
  501. * @return array An array of class names
  502. */
  503. public function getClassesContainingMethodsAnnotatedWith($annotationClassName) {
  504. return isset($this->classesByMethodAnnotations[$annotationClassName]) ? array_keys($this->classesByMethodAnnotations[$annotationClassName]) : array();
  505. }
  506. /**
  507. * Tells if the specified method is final or not
  508. *
  509. * @param string $className Name of the class containing the method
  510. * @param string $methodName Name of the method to analyze
  511. * @return boolean TRUE if the method is final, otherwise FALSE
  512. * @api
  513. */
  514. public function isMethodFinal($className, $methodName) {
  515. if ($className[0] === '\\') {
  516. $className = substr($className, 1);
  517. }
  518. $this->loadOrReflectClassIfNecessary($className);
  519. return isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_FINAL]);
  520. }
  521. /**
  522. * Tells if the specified method is declared as static or not
  523. *
  524. * @param string $className Name of the class containing the method
  525. * @param string $methodName Name of the method to analyze
  526. * @return boolean TRUE if the method is static, otherwise FALSE
  527. * @api
  528. */
  529. public function isMethodStatic($className, $methodName) {
  530. if ($className[0] === '\\') {
  531. $className = substr($className, 1);
  532. }
  533. $this->loadOrReflectClassIfNecessary($className);
  534. return isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_STATIC]);
  535. }
  536. /**
  537. * Tells if the specified method is public
  538. *
  539. * @param string $className Name of the class containing the method
  540. * @param string $methodName Name of the method to analyze
  541. * @return boolean TRUE if the method is public, otherwise FALSE
  542. * @api
  543. */
  544. public function isMethodPublic($className, $methodName) {
  545. if ($className[0] === '\\') {
  546. $className = substr($className, 1);
  547. }
  548. $this->loadOrReflectClassIfNecessary($className);
  549. return (isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY]) && $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY] === self::VISIBILITY_PUBLIC);
  550. }
  551. /**
  552. * Tells if the specified method is protected
  553. *
  554. * @param string $className Name of the class containing the method
  555. * @param string $methodName Name of the method to analyze
  556. * @return boolean TRUE if the method is protected, otherwise FALSE
  557. * @api
  558. */
  559. public function isMethodProtected($className, $methodName) {
  560. if ($className[0] === '\\') {
  561. $className = substr($className, 1);
  562. }
  563. $this->loadOrReflectClassIfNecessary($className);
  564. return (isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY]) && $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY] === self::VISIBILITY_PROTECTED);
  565. }
  566. /**
  567. * Tells if the specified method is private
  568. *
  569. * @param string $className Name of the class containing the method
  570. * @param string $methodName Name of the method to analyze
  571. * @return boolean TRUE if the method is private, otherwise FALSE
  572. * @api
  573. */
  574. public function isMethodPrivate($className, $methodName) {
  575. if ($className[0] === '\\') {
  576. $className = substr($className, 1);
  577. }
  578. $this->loadOrReflectClassIfNecessary($className);
  579. return (isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY]) && $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY] === self::VISIBILITY_PRIVATE);
  580. }
  581. /**
  582. * Tells if the specified method is tagged with the given tag
  583. *
  584. * @param string $className Name of the class containing the method
  585. * @param string $methodName Name of the method to analyze
  586. * @param string $tag Tag to check for
  587. * @return boolean TRUE if the method is tagged with $tag, otherwise FALSE
  588. * @api
  589. */
  590. public function isMethodTaggedWith($className, $methodName, $tag) {
  591. $method = new \TYPO3\FLOW3\Reflection\MethodReflection(trim($className, '\\'), $methodName);
  592. $tagsValues = $method->getTagsValues();
  593. return isset($tagsValues[$tag]);
  594. }
  595. /**
  596. * Tells if the specified method has the given annotation
  597. *
  598. * @param string $className Name of the class
  599. * @param string $methodName Name of the method
  600. * @param string $annotationClassName Annotation to check for
  601. * @return boolean
  602. * @api
  603. */
  604. public function isMethodAnnotatedWith($className, $methodName, $annotationClassName) {
  605. return $this->getMethodAnnotations($className, $methodName, $annotationClassName) !== array();
  606. }
  607. /**
  608. * Returns the specified method annotations or an empty array
  609. *
  610. * @param string $className Name of the class
  611. * @param string $methodName Name of the method
  612. * @param string $annotationClassName Annotation to filter for
  613. * @return array<object>
  614. * @api
  615. */
  616. public function getMethodAnnotations($className, $methodName, $annotationClassName = NULL) {
  617. if ($className[0] === '\\') {
  618. $className = substr($className, 1);
  619. }
  620. if ($annotationClassName[0] === '\\') {
  621. $annotationClassName = substr($annotationClassName, 1);
  622. }
  623. $annotations = array();
  624. $methodAnnotations = $this->annotationReader->getMethodAnnotations(new MethodReflection($className, $methodName));
  625. if ($annotationClassName === NULL) {
  626. return $methodAnnotations;
  627. } else {
  628. foreach ($methodAnnotations as $annotation) {
  629. if ($annotation instanceof $annotationClassName) {
  630. $annotations[] = $annotation;
  631. }
  632. }
  633. return $annotations;
  634. }
  635. }
  636. /**
  637. * Returns the specified method annotation or NULL.
  638. *
  639. * If multiple annotations are set on the target you will
  640. * get one (random) instance of them.
  641. *
  642. * @param string $className Name of the class
  643. * @param string $methodName Name of the method
  644. * @param string $annotationClassName Annotation to filter for
  645. * @return object
  646. */
  647. public function getMethodAnnotation($className, $methodName, $annotationClassName) {
  648. $annotations = $this->getMethodAnnotations($className, $methodName, $annotationClassName);
  649. return $annotations === array() ? NULL : current($annotations);
  650. }
  651. /**
  652. * Returns the names of all properties of the specified class
  653. *
  654. * @param string $className Name of the class to return the property names of
  655. * @return array An array of property names or an empty array if none exist
  656. * @api
  657. */
  658. public function getClassPropertyNames($className) {
  659. if ($className[0] === '\\') {
  660. $className = substr($className, 1);
  661. }
  662. $className = trim($className, '\\');
  663. $this->loadOrReflectClassIfNecessary($className);
  664. return isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES]) ? array_keys($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES]) : array();
  665. }
  666. /**
  667. * Wrapper for method_exists() which tells if the given method exists.
  668. *
  669. * @param string $className Name of the class containing the method
  670. * @param string $methodName Name of the method
  671. * @return boolean
  672. * @api
  673. */
  674. public function hasMethod($className, $methodName) {
  675. if ($className[0] === '\\') {
  676. $className = substr($className, 1);
  677. }
  678. $this->loadOrReflectClassIfNecessary($className);
  679. return isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName]);
  680. }
  681. /**
  682. * Returns all tags and their values the specified method is tagged with
  683. *
  684. * @param string $className Name of the class containing the method
  685. * @param string $methodName Name of the method to return the tags and values of
  686. * @return array An array of tags and their values or an empty array of no tags were found
  687. * @api
  688. */
  689. public function getMethodTagsValues($className, $methodName) {
  690. if ($className[0] === '\\') {
  691. $className = substr($className, 1);
  692. }
  693. $method = new \TYPO3\FLOW3\Reflection\MethodReflection($className, $methodName);
  694. return $method->getTagsValues();
  695. }
  696. /**
  697. * Returns an array of parameters of the given method. Each entry contains
  698. * additional information about the parameter position, type hint etc.
  699. *
  700. * @param string $className Name of the class containing the method
  701. * @param string $methodName Name of the method to return parameter information of
  702. * @return array An array of parameter names and additional information or an empty array of no parameters were found
  703. * @api
  704. */
  705. public function getMethodParameters($className, $methodName) {
  706. if ($className[0] === '\\') {
  707. $className = substr($className, 1);
  708. }
  709. $this->loadOrReflectClassIfNecessary($className);
  710. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_PARAMETERS])) {
  711. return array();
  712. }
  713. return $this->convertParameterDataToArray($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_PARAMETERS]);
  714. }
  715. /**
  716. * Searches for and returns all names of class properties which are tagged by the specified tag.
  717. * If no properties were found, an empty array is returned.
  718. *
  719. * @param string $className Name of the class containing the properties
  720. * @param string $tag Tag to search for
  721. * @return array An array of property names tagged by the tag
  722. * @api
  723. */
  724. public function getPropertyNamesByTag($className, $tag) {
  725. if ($className[0] === '\\') {
  726. $className = substr($className, 1);
  727. }
  728. $this->loadOrReflectClassIfNecessary($className);
  729. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES])) {
  730. return array();
  731. }
  732. $propertyNames = array();
  733. foreach ($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES] as $propertyName => $propertyData) {
  734. if (isset($propertyData[self::DATA_PROPERTY_TAGS_VALUES][$tag])) {
  735. $propertyNames[$propertyName] = TRUE;
  736. }
  737. }
  738. return array_keys($propertyNames);
  739. }
  740. /**
  741. * Returns all tags and their values the specified class property is tagged with
  742. *
  743. * @param string $className Name of the class containing the property
  744. * @param string $propertyName Name of the property to return the tags and values of
  745. * @return array An array of tags and their values or an empty array of no tags were found
  746. * @api
  747. */
  748. public function getPropertyTagsValues($className, $propertyName) {
  749. if ($className[0] === '\\') {
  750. $className = substr($className, 1);
  751. }
  752. $this->loadOrReflectClassIfNecessary($className);
  753. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName])) {
  754. return array();
  755. }
  756. return (isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_TAGS_VALUES])) ? $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_TAGS_VALUES] : array();
  757. }
  758. /**
  759. * Returns the values of the specified class property tag
  760. *
  761. * @param string $className Name of the class containing the property
  762. * @param string $propertyName Name of the tagged property
  763. * @param string $tag Tag to return the values of
  764. * @return array An array of values or an empty array if the tag was not found
  765. * @api
  766. */
  767. public function getPropertyTagValues($className, $propertyName, $tag) {
  768. if ($className[0] === '\\') {
  769. $className = substr($className, 1);
  770. }
  771. $this->loadOrReflectClassIfNecessary($className);
  772. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName])) {
  773. return array();
  774. }
  775. return (isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_TAGS_VALUES][$tag])) ? $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_TAGS_VALUES][$tag] : array();
  776. }
  777. /**
  778. * Tells if the specified property is private
  779. *
  780. * @param string $className Name of the class containing the method
  781. * @param string $propertyName Name of the property to analyze
  782. * @return boolean TRUE if the property is private, otherwise FALSE
  783. * @api
  784. */
  785. public function isPropertyPrivate($className, $propertyName) {
  786. if ($className[0] === '\\') {
  787. $className = substr($className, 1);
  788. }
  789. $this->loadOrReflectClassIfNecessary($className);
  790. return (isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_VISIBILITY])
  791. && $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_VISIBILITY] === self::VISIBILITY_PRIVATE);
  792. }
  793. /**
  794. * Tells if the specified class property is tagged with the given tag
  795. *
  796. * @param string $className Name of the class
  797. * @param string $propertyName Name of the property
  798. * @param string $tag Tag to check for
  799. * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
  800. * @api
  801. */
  802. public function isPropertyTaggedWith($className, $propertyName, $tag) {
  803. if ($className[0] === '\\') {
  804. $className = substr($className, 1);
  805. }
  806. $this->loadOrReflectClassIfNecessary($className);
  807. return isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_TAGS_VALUES][$tag]);
  808. }
  809. /**
  810. * Tells if the specified property has the given annotation
  811. *
  812. * @param string $className Name of the class
  813. * @param string $propertyName Name of the method
  814. * @param string $annotationClassName Annotation to check for
  815. * @return boolean
  816. * @api
  817. */
  818. public function isPropertyAnnotatedWith($className, $propertyName, $annotationClassName) {
  819. if ($className[0] === '\\') {
  820. $className = substr($className, 1);
  821. }
  822. $this->loadOrReflectClassIfNecessary($className);
  823. return isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][$annotationClassName]);
  824. }
  825. /**
  826. * Searches for and returns all names of class properties which are marked by the
  827. * specified annotation. If no properties were found, an empty array is returned.
  828. *
  829. * @param string $className Name of the class containing the properties
  830. * @param string $annotationClassName Class name of the annotation to search for
  831. * @return array An array of property names carrying the annotation
  832. * @api
  833. */
  834. public function getPropertyNamesByAnnotation($className, $annotationClassName) {
  835. if ($className[0] === '\\') {
  836. $className = substr($className, 1);
  837. }
  838. $this->loadOrReflectClassIfNecessary($className);
  839. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES])) {
  840. return array();
  841. }
  842. $propertyNames = array();
  843. foreach ($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES] as $propertyName => $propertyData) {
  844. if (isset($propertyData[self::DATA_PROPERTY_ANNOTATIONS][$annotationClassName])) {
  845. $propertyNames[$propertyName] = TRUE;
  846. }
  847. }
  848. return array_keys($propertyNames);
  849. }
  850. /**
  851. * Returns the specified property annotations or an empty array
  852. *
  853. * @param string $className Name of the class
  854. * @param string $propertyName Name of the property
  855. * @param string $annotationClassName Annotation to filter for
  856. * @return array<object>
  857. * @api
  858. */
  859. public function getPropertyAnnotations($className, $propertyName, $annotationClassName = NULL) {
  860. if ($className[0] === '\\') {
  861. $className = substr($className, 1);
  862. }
  863. $this->loadOrReflectClassIfNecessary($className);
  864. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS])) {
  865. return array();
  866. }
  867. if ($annotationClassName === NULL) {
  868. return $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS];
  869. } elseif (isset($this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][$annotationClassName])) {
  870. return $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][$annotationClassName];
  871. } else {
  872. return array();
  873. }
  874. }
  875. /**
  876. * Returns the specified property annotation or NULL.
  877. *
  878. * If multiple annotations are set on the target you will
  879. * get one (random) instance of them.
  880. *
  881. * @param string $className Name of the class
  882. * @param string $propertyName Name of the property
  883. * @param string $annotationClassName Annotation to filter for
  884. * @return object
  885. */
  886. public function getPropertyAnnotation($className, $propertyName, $annotationClassName) {
  887. $annotations = $this->getPropertyAnnotations($className, $propertyName, $annotationClassName);
  888. return $annotations === array() ? NULL : current($annotations);
  889. }
  890. /**
  891. * Returns the class schema for the given class
  892. *
  893. * @param mixed $classNameOrObject The class name or an object
  894. * @return \TYPO3\FLOW3\Reflection\ClassSchema
  895. */
  896. public function getClassSchema($classNameOrObject) {
  897. if (is_object($classNameOrObject)) {
  898. $className = get_class($classNameOrObject);
  899. } else {
  900. $className = ($classNameOrObject[0] === '\\' ? substr($classNameOrObject, 1) : $classNameOrObject);
  901. }
  902. if (!isset($this->classSchemata[$className])) {
  903. $this->classSchemata[$className] = $this->classSchemataRuntimeCache->get(str_replace('\\', '_', $className));
  904. }
  905. return is_object($this->classSchemata[$className]) ? $this->classSchemata[$className] : NULL;
  906. }
  907. /**
  908. * Checks if the given class names match those which already have been
  909. * reflected. If the given array contains class names not yet known to
  910. * this service, these classes will be reflected.
  911. *
  912. * @return void
  913. * @throws \TYPO3\FLOW3\Reflection\Exception
  914. */
  915. protected function reflectEmergedClasses() {
  916. $classNamesToReflect = array();
  917. foreach ($this->availableClassNames as $classNamesInOnePackage) {
  918. $classNamesToReflect = array_merge($classNamesToReflect, $classNamesInOnePackage);
  919. }
  920. $reflectedClassNames = array_keys($this->classReflectionData);
  921. sort($classNamesToReflect);
  922. sort($reflectedClassNames);
  923. $newClassNames = array_diff($classNamesToReflect, $reflectedClassNames);
  924. if ($newClassNames !== array()) {
  925. $this->systemLogger->log('Reflected class names did not match class names to reflect', LOG_DEBUG);
  926. $classNamesToBuildSchemaFor = array();
  927. $count = 0;
  928. foreach ($newClassNames as $className) {
  929. $count++;
  930. try {
  931. $this->reflectClass($className);
  932. } catch (Exception\ClassLoadingForReflectionFailedException $exception) {
  933. $this->systemLogger->log('Could not reflect "' . $className . '" because the class could not be loaded.', LOG_DEBUG);
  934. continue;
  935. }
  936. if ($this->isClassAnnotatedWith($className, 'TYPO3\FLOW3\Annotations\Entity') || $this->isClassAnnotatedWith($className, 'Doctrine\ORM\Mapping\Entity') || $this->isClassAnnotatedWith($className, 'TYPO3\FLOW3\Annotations\ValueObject')) {
  937. $scopeAnnotation = $this->getClassAnnotation($className, 'TYPO3\FLOW3\Annotations\Scope');
  938. if ($scopeAnnotation !== NULL && $scopeAnnotation->value !== 'prototype') {
  939. throw new \TYPO3\FLOW3\Reflection\Exception(sprintf('Classes tagged as entity or value object must be of scope prototype, however, %s is declared as %s.', $className, $scopeAnnotation->value), 1264103349);
  940. }
  941. $classNamesToBuildSchemaFor[] = $className;
  942. }
  943. }
  944. $this->buildClassSchemata($classNamesToBuildSchemaFor);
  945. if ($count > 0) {
  946. $this->log(sprintf('Reflected %s emerged classes.', $count), LOG_INFO);
  947. }
  948. }
  949. }
  950. /**
  951. * Reflects the given class and stores the results in this service's properties.
  952. *
  953. * @param string $className Full qualified name of the class to reflect
  954. * @return void
  955. * @throws \TYPO3\FLOW3\Reflection\Exception\InvalidClassException
  956. */
  957. protected function reflectClass($className) {
  958. $this->log(sprintf('Reflecting class %s', $className), LOG_DEBUG);
  959. $className = trim($className, '\\');
  960. if (strpos($className, 'TYPO3\FLOW3\Persistence\Doctrine\Proxies') === 0 && array_search('Doctrine\ORM\Proxy\Proxy', class_implements($className))) {
  961. // Somebody tried to reflect a doctrine proxy, which will have severe side effects.
  962. // see bug http://forge.typo3.org/issues/29449 for details.
  963. throw new Exception\InvalidClassException('The class with name "' . $className . '" is a Doctrine proxy. It is not supported to reflect doctrine proxy classes.', 1314944681);
  964. }
  965. $class = new ClassReflection($className);
  966. if (!isset($this->classReflectionData[$className])) {
  967. $this->classReflectionData[$className] = array();
  968. }
  969. if ($class->isAbstract()) $this->classReflectionData[$className][self::DATA_CLASS_ABSTRACT] = TRUE;
  970. if ($class->isFinal()) $this->classReflectionData[$className][self::DATA_CLASS_FINAL] = TRUE;
  971. foreach ($this->getParentClasses($class) as $parentClass) {
  972. $parentClassName = $parentClass->getName();
  973. if (!isset($this->classReflectionData[$parentClassName])) {
  974. $this->reflectClass($parentClassName);
  975. }
  976. $this->classReflectionData[$parentClassName][self::DATA_CLASS_SUBCLASSES][$className] = TRUE;
  977. }
  978. foreach ($class->getInterfaces() as $interface) {
  979. if (!isset($this->classReflectionData[$className][self::DATA_CLASS_ABSTRACT])) {
  980. $interfaceName = $interface->getName();
  981. if (!isset($this->classReflectionData[$interfaceName])) {
  982. $this->reflectClass($interfaceName);
  983. }
  984. $this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className] = TRUE;
  985. }
  986. }
  987. foreach ($this->annotationReader->getClassAnnotations($class) as $annotation) {
  988. $annotationClassName = get_class($annotation);
  989. $this->annotatedClasses[$annotationClassName][$className] = TRUE;
  990. $this->classReflectionData[$className][self::DATA_CLASS_ANNOTATIONS][] = $annotation;
  991. }
  992. foreach ($class->getProperties() as $property) {
  993. $propertyName = $property->getName();
  994. $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName] = array();
  995. $visibility = $property->isPublic() ? self::VISIBILITY_PUBLIC : ($property->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PRIVATE);
  996. $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_VISIBILITY] = $visibility;
  997. foreach ($property->getTagsValues() as $tag => $values) {
  998. if (array_search($tag, $this->settings['reflection']['ignoredTags']) === FALSE) {
  999. $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_TAGS_VALUES][$tag] = $values;
  1000. }
  1001. }
  1002. foreach ($this->annotationReader->getPropertyAnnotations($property, $propertyName) as $annotation) {
  1003. $this->classReflectionData[$className][self::DATA_CLASS_PROPERTIES][$propertyName][self::DATA_PROPERTY_ANNOTATIONS][get_class($annotation)][] = $annotation;
  1004. }
  1005. }
  1006. foreach ($class->getMethods() as $method) {
  1007. $methodName = $method->getName();
  1008. if ($method->isFinal()) {
  1009. $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_FINAL] = TRUE;
  1010. }
  1011. if ($method->isStatic()) {
  1012. $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_STATIC] = TRUE;
  1013. }
  1014. $visibility = $method->isPublic() ? self::VISIBILITY_PUBLIC : ($method->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PRIVATE);
  1015. $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_VISIBILITY] = $visibility;
  1016. foreach ($this->getMethodAnnotations($className, $methodName) as $methodAnnotation) {
  1017. $this->classesByMethodAnnotations[get_class($methodAnnotation)][$className] = $methodName;
  1018. }
  1019. $paramAnnotations = $method->isTaggedWith('param') ? $method->getTagValues('param') : array();
  1020. foreach ($method->getParameters() as $parameter) {
  1021. $this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_PARAMETERS][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $method);
  1022. if ($this->settings['reflection']['logIncorrectDocCommentHints'] === TRUE) {
  1023. if (isset($paramAnnotations[$parameter->getPosition()])) {
  1024. $parameterAnnotation = explode(' ', $paramAnnotations[$parameter->getPosition()], 3);
  1025. if (count($parameterAnnotation) < 2) {
  1026. $this->log(' Wrong @param use for "' . $method->getName() . '::' . $parameter->getName() . '": "' . implode(' ', $parameterAnnotation) . '"', LOG_DEBUG);
  1027. } else {
  1028. if (isset($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_PARAMETERS][$parameter->getName()][self::DATA_PARAMETER_TYPE]) && ($this->classReflectionData[$className][self::DATA_CLASS_METHODS][$methodName][self::DATA_METHOD_PARAMETERS][$parameter->getName()][self::DATA_PARAMETER_TYPE] !== ltrim($parameterAnnotation[0], '\\'))) {
  1029. $this->log(' Wrong type in @param for "' . $method->getName() . '::' . $parameter->getName() . '": "' . $parameterAnnotation[0] . '"', LOG_DEBUG);
  1030. }
  1031. if ($parameter->getName() !== ltrim($parameterAnnotation[1], '$&')) {
  1032. $this->log(' Wrong name in @param for "' . $method->getName() . '::$' . $parameter->getName() . '": "' . $parameterAnnotation[1] . '"', LOG_DEBUG);
  1033. }
  1034. }
  1035. } else {
  1036. $this->log(' Missing @param for "' . $method->getName() . '::$' . $parameter->getName(), LOG_DEBUG);
  1037. }
  1038. }
  1039. }
  1040. }
  1041. // Sort reflection data so that the cache data is deterministic. This is
  1042. // important for comparisons when checking if classes have changed in a
  1043. // Development context.
  1044. ksort($this->classReflectionData);
  1045. $this->updatedReflectionData[$className] = TRUE;
  1046. }
  1047. /**
  1048. * Finds all parent classes of the given class
  1049. *
  1050. * @param \TYPO3\FLOW3\Reflection\ClassReflection $class The class to reflect
  1051. * @param array $parentClasses Array of parent classes
  1052. * @return array<\TYPO3\FLOW3\Reflection\ClassReflection>
  1053. */
  1054. protected function getParentClasses(\TYPO3\FLOW3\Reflection\ClassReflection $class, array $parentClasses = array()) {
  1055. $parentClass = $class->getParentClass();
  1056. if ($parentClass !== FALSE) {
  1057. $parentClasses[] = $parentClass;
  1058. $parentClasses = $this->getParentClasses($parentClass, $parentClasses);
  1059. }
  1060. return $parentClasses;
  1061. }
  1062. /**
  1063. * Builds class schemata from classes annotated as entities or value objects
  1064. *
  1065. * @param array $classNames
  1066. * @return void
  1067. */
  1068. protected function buildClassSchemata(array $classNames) {
  1069. foreach ($classNames as $className) {
  1070. $classSchema = new \TYPO3\FLOW3\Reflection\ClassSchema($className);
  1071. if ($this->isClassAnnotatedWith($className, 'TYPO3\FLOW3\Annotations\Entity') || $this->isClassAnnotatedWith($className, 'Doctrine\ORM\Mapping\Entity')) {
  1072. $classSchema->setModelType(\TYPO3\FLOW3\Reflection\ClassSchema::MODELTYPE_ENTITY);
  1073. $classSchema->setLazyLoadableObject($this->isClassAnnotatedWith($className, 'TYPO3\FLOW3\Annotations\Lazy'));
  1074. $possibleRepositoryClassName = str_replace('\\Model\\', '\\Repository\\', $className) . 'Repository';
  1075. if ($this->isClassReflected($possibleRepositoryClassName) === TRUE) {
  1076. $classSchema->setRepositoryClassName($possibleRepositoryClassName);
  1077. }
  1078. } elseif ($this->isClassAnnotatedWith($className, 'TYPO3\FLOW3\Annotations\ValueObject')) {
  1079. $this->checkValueObjectRequirements($className);
  1080. $classSchema->setModelType(\TYPO3\FLOW3\Reflection\ClassSchema::MODELTYPE_VALUEOBJECT);
  1081. }
  1082. $this->addPropertiesToClassSchema($classSchema);
  1083. $this->classSchemata[$className] = $classSchema;
  1084. }
  1085. $this->completeRepositoryAssignments();
  1086. $this->ensureAggregateRootInheritanceChainConsistency();
  1087. }
  1088. /**
  1089. * Adds properties of the class at hand to the class schema.
  1090. *
  1091. * Only non-transient properties annotated with a var annotation will be added.
  1092. * Invalid annotations will cause an exception to be thrown. Properties pointing
  1093. * to existing classes will only be added if the target type is annotated as
  1094. * entity or valueobject.
  1095. *
  1096. * @param \TYPO3\FLOW3\Reflection\ClassSchema $classSchema
  1097. * @return void
  1098. * @throws \InvalidArgumentException
  1099. * @throws \TYPO3\FLOW3\Reflection\Exception\InvalidPropertyTypeException
  1100. */
  1101. protected function addPropertiesToClassSchema(\TYPO3\FLOW3\Reflection\ClassSchema $classSchema) {
  1102. // those are added as property even if not tagged with entity/valueobject
  1103. $propertyTypeWhiteList = array(
  1104. 'DateTime',
  1105. 'SplObjectStorage',
  1106. 'Doctrine\Common\Collections\Collection',
  1107. 'Doctrine\Common\Collections\ArrayCollection'
  1108. );
  1109. $className = $classSchema->getClassName();
  1110. $needsArtificialIdentity = TRUE;
  1111. foreach ($this->getClassPropertyNames($className) as $propertyName) {
  1112. if ($this->isPropertyTaggedWith($className, $propertyName, 'var') && !$this->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\FLOW3\Annotations\Transient')) {
  1113. $declaredType = trim(implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), ' \\');
  1114. if (preg_match('/\s/', $declaredType) === 1) {
  1115. throw new \TYPO3\FLOW3\Reflection\Exception\InvalidPropertyTypeException('The @var annotation for "' . $className . '::$' . $propertyName . '" seems to be invalid.', 1284132314);
  1116. }
  1117. if ($this->isPropertyAnnotatedWith($className, $propertyName, 'Doctrine\ORM\Mapping\Id')) {
  1118. $needsArtificialIdentity = FALSE;
  1119. }
  1120. try {
  1121. $parsedType = \TYPO3\FLOW3\Utility\TypeHandling::parseType($declaredType);
  1122. } catch (\TYPO3\FLOW3\Utility\Exception\InvalidTypeException $exception) {
  1123. throw new \InvalidArgumentException(sprintf($exception->getMessage(), 'class "' . $className . '" for property "' . $propertyName . '"'), 1315564475);
  1124. }
  1125. if (!in_array($parsedType['type'], $propertyTypeWhiteList)
  1126. && (class_exists($parsedType['type']) || interface_exists($parsedType['type']))
  1127. && !($this->isClassAnnotatedWith($parsedType['type'], 'TYPO3\FLOW3\Annotations\Entity') || $this->isClassAnnotatedWith($parsedType['type'], 'Doctrine\ORM\Mapping\Entity') || $this->isClassAnnotatedWith($parsedType['type'], 'TYPO3\FLOW3\Annotations\ValueObject'))) {
  1128. continue;
  1129. }
  1130. $classSchema->addProperty($propertyName, $declaredType, $this->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\FLOW3\Annotations\Lazy'));
  1131. if ($this->isPropertyAnnotatedWith($className, $propertyName, 'TYPO3\FLOW3\Annotations\Identity')) {
  1132. $classSchema->markAsIdentityProperty($propertyName);
  1133. }
  1134. }
  1135. }
  1136. if ($needsArtificialIdentity === TRUE) {
  1137. $classSchema->addProperty('FLOW3_Persistence_Identifier', 'string');
  1138. }
  1139. }
  1140. /**
  1141. * Complete repository-to-entity assignments.
  1142. *
  1143. * This method looks for repositories that declare themselves responsible
  1144. * for a specific model and sets a repository classname on the corresponding
  1145. * models.
  1146. *
  1147. * It then walks the inheritance chain for all aggregate roots and checks
  1148. * the subclasses for their aggregate root status - if no repository is
  1149. * assigned yet, that will be done.
  1150. *
  1151. * @return void
  1152. * @throws Exception\ClassSchemaConstraintViolationException
  1153. */
  1154. protected function completeRepositoryAssignments() {
  1155. foreach ($this->getAllImplementationClassNamesForInterface('TYPO3\FLOW3\Persistence\RepositoryInterface') as $repositoryClassName) {
  1156. // need to be extra careful because this code could be called
  1157. // during a cache:flush run with corrupted reflection cache
  1158. if (class_exists($repositoryClassName) && !$this->isClassAbstract($repositoryClassName)) {
  1159. if (!$this->isClassAnnotatedWith($repositoryClassName, 'TYPO3\FLOW3\Annotations\Scope') || $this->getClassAnnotation($repositoryClassName, 'TYPO3\FLOW3\Annotations\Scope')->value !== 'singleton') {
  1160. throw new \TYPO3\FLOW3\Reflection\Exception\ClassSchemaConstraintViolationException('The repository "' . $repositoryClassName . '" must be of scope singleton, but it is not.', 1335790707);
  1161. }
  1162. $claimedObjectType = $repositoryClassName::ENTITY_CLASSNAME;
  1163. if ($claimedObjectType !== NULL && isset($this->classSchemata[$claimedObjectType])) {
  1164. $this->classSchemata[$claimedObjectType]->setRepositoryClassName($repositoryClassName);
  1165. }
  1166. }
  1167. }
  1168. foreach (array_values($this->classSchemata) as $classSchema) {
  1169. if (class_exists($classSchema->getClassName()) && $classSchema->isAggregateRoot()) {
  1170. $this->makeChildClassesAggregateRoot($classSchema);
  1171. }
  1172. }
  1173. }
  1174. /**
  1175. * Assigns the repository of any aggregate root to all it's
  1176. * subclasses, unless they are aggregate root already.
  1177. *
  1178. * @param \TYPO3\FLOW3\Reflection\ClassSchema $classSchema
  1179. * @return void
  1180. */
  1181. protected function makeChildClassesAggregateRoot(\TYPO3\FLOW3\Reflection\ClassSchema $classSchema) {
  1182. foreach ($this->getAllSubClassNamesForClass($classSchema->getClassName()) as $childClassName) {
  1183. if ($this->classSchemata[$childClassName]->isAggregateRoot()) {
  1184. continue;
  1185. } else {
  1186. $this->classSchemata[$childClassName]->setRepositoryClassName($classSchema->getRepositoryClassName());
  1187. $this->makeChildClassesAggregateRoot($this->classSchemata[$childClassName]);
  1188. }
  1189. }
  1190. }
  1191. /**
  1192. * Checks whether all aggregate roots having superclasses
  1193. * have a repository assigned up to the tip of their hierarchy.
  1194. *
  1195. * @return void
  1196. * @throws \TYPO3\FLOW3\Reflection\Exception
  1197. */
  1198. protected function ensureAggregateRootInheritanceChainConsistency() {
  1199. foreach ($this->classSchemata as $className => $classSchema) {
  1200. if (!class_exists($className) || $classSchema->isAggregateRoot() === FALSE) {
  1201. continue;
  1202. }
  1203. foreach (class_parents($className) as $parentClassName) {
  1204. if ($this->isClassAbstract($parentClassName) === FALSE && $this->classSchemata[$parentClassName]->isAggregateRoot() === FALSE) {
  1205. throw new \TYPO3\FLOW3\Reflection\Exception('In a class hierarchy either all or no classes must be an aggregate root, "' . $className . '" is one but the parent class "' . $parentClassName . '" is not. You probably want to add a repository for "' . $parentClassName . '"', 1316009511);
  1206. }
  1207. }
  1208. }
  1209. }
  1210. /**
  1211. * Checks if the given class meets the requirements for a value object, i.e.
  1212. * does have a constructor and does not have any setter methods.
  1213. *
  1214. * @param string $className
  1215. * @return void
  1216. * @throws \TYPO3\FLOW3\Reflection\Exception\InvalidValueObjectException
  1217. */
  1218. protected function checkValueObjectRequirements($className) {
  1219. $methods = get_class_methods($className);
  1220. if (array_search('__construct', $methods) === FALSE) {
  1221. throw new \TYPO3\FLOW3\Reflection\Exception\InvalidValueObjectException('A value object must have a constructor, "' . $className . '" does not have one.', 1268740874);
  1222. }
  1223. foreach ($methods as $method) {
  1224. if (substr($method, 0, 3) === 'set') {
  1225. throw new \TYPO3\FLOW3\Reflection\Exception\InvalidValueObjectException('A value object must not have setters, "' . $className . '" does.', 1268740878);
  1226. }
  1227. }
  1228. }
  1229. /**
  1230. * Converts the internal, optimized data structure of parameter information into
  1231. * a human-friendly array with speaking indexes.
  1232. *
  1233. * @param array $parametersInformation Raw, internal parameter information
  1234. * @return array Developer friendly version
  1235. */
  1236. protected function convertParameterDataToArray(array $parametersInformation) {
  1237. $parameters = array();
  1238. foreach ($parametersInformation as $parameterName => $parameterData) {
  1239. $parameters[$parameterName] = array(
  1240. 'position' => $parameterData[self::DATA_PARAMETER_POSITION],
  1241. 'optional' => isset($parameterData[self::DATA_PARAMETER_OPTIONAL]),
  1242. 'type' => $parameterData[self::DATA_PARAMETER_TYPE],
  1243. 'class' => isset($parameterData[self::DATA_PARAMETER_CLASS]) ? $parameterData[self::DATA_PARAMETER_CLASS] : NULL,
  1244. 'array' => isset($parameterData[self::DATA_PARAMETER_ARRAY]),
  1245. 'byReference' => isset($parameterData[self::DATA_PARAMETER_BY_REFERENCE]),
  1246. 'allowsNull' => isset($parameterData[self::DATA_PARAMETER_ALLOWS_NULL]),
  1247. 'defaultValue' => isset($parameterData[self::DATA_PARAMETER_DEFAULT_VALUE]) ? $parameterData[self::DATA_PARAMETER_DEFAULT_VALUE] : NULL
  1248. );
  1249. }
  1250. return $parameters;
  1251. }
  1252. /**
  1253. * Converts the given parameter reflection into an information array
  1254. *
  1255. * @param \TYPO3\FLOW3\Reflection\ParameterReflection $parameter The parameter to reflect
  1256. * @param \TYPO3\FLOW3\Reflection\MethodReflection $method The parameter's method
  1257. * @return array Parameter information array
  1258. */
  1259. protected function convertParameterReflectionToArray(\TYPO3\FLOW3\Reflection\ParameterReflection $parameter, \TYPO3\FLOW3\Reflection\MethodReflection $method = NULL) {
  1260. $parameterInformation = array(
  1261. self::DATA_PARAMETER_POSITION => $parameter->getPosition()
  1262. );
  1263. if ($parameter->isPassedByReference()) {
  1264. $parameterInformation[self::DATA_PARAMETER_BY_REFERENCE] = TRUE;
  1265. }
  1266. if ($parameter->isArray()) {
  1267. $parameterInformation[self::DATA_PARAMETER_ARRAY] = TRUE;
  1268. }
  1269. if ($parameter->isOptional()) {
  1270. $parameterInformation[self::DATA_PARAMETER_OPTIONAL] = TRUE;
  1271. }
  1272. if ($parameter->allowsNull()) {
  1273. $parameterInformation[self::DATA_PARAMETER_ALLOWS_NULL] = TRUE;
  1274. }
  1275. $parameterClass = $parameter->getClass();
  1276. if ($parameterClass !== NULL) {
  1277. $parameterInformation[self::DATA_PARAMETER_CLASS] = $parameterClass->getName();
  1278. }
  1279. if ($parameter->isOptional() && $parameter->isDefaultValueAvailable()) {
  1280. $parameterInformation[self::DATA_PARAMETER_DEFAULT_VALUE] = $parameter->getDefaultValue();
  1281. }
  1282. if ($method !== NULL) {
  1283. $paramAnnotations = $method->isTaggedWith('param') ? $method->getTagValues('param') : array();
  1284. if (isset($paramAnnotations[$parameter->getPosition()])) {
  1285. $explodedParameters = explode(' ', $paramAnnotations[$parameter->getPosition()]);
  1286. if (count($explodedParameters) >= 2) {
  1287. $parameterInformation[self::DATA_PARAMETER_TYPE] = ltrim($explodedParameters[0], '\\');
  1288. }
  1289. }
  1290. }
  1291. if (!isset($parameterInformation[self::DATA_PARAMETER_TYPE]) && $parameterClass !== NULL) {
  1292. $parameterInformation[self::DATA_PARAMETER_TYPE] = ltrim($parameterClass->getName(), '\\');
  1293. } elseif (!isset($parameterInformation[self::DATA_PARAMETER_TYPE])) {
  1294. $parameterInformation[self::DATA_PARAMETER_TYPE] = 'mixed';
  1295. }
  1296. return $parameterInformation;
  1297. }
  1298. /**
  1299. * Checks which classes lack a cache entry and removes their reflection data
  1300. * accordingly.
  1301. *
  1302. * @return void
  1303. */
  1304. protected function forgetChangedClasses() {
  1305. $frozenNamespaces = array();
  1306. foreach ($this->packageManager->getAvailablePackages() as $packageKey => $package) {
  1307. if ($this->packageManager->isPackageFrozen($packageKey)) {
  1308. $frozenNamespaces[] = $package->getPackageNamespace();
  1309. }
  1310. }
  1311. $classNames = array_keys($this->classReflectionData);
  1312. foreach ($frozenNamespaces as $namespace) {
  1313. $namespace .= '\\';
  1314. $namespaceLength = strlen($namespace);
  1315. foreach ($classNames as $index => $className) {
  1316. if (substr($className, 0, $namespaceLength) === $namespace) {
  1317. unset($classNames[$index]);
  1318. }
  1319. }
  1320. }
  1321. foreach ($classNames as $className) {
  1322. if (!$this->statusCache->has(str_replace('\\', '_', $className))) {
  1323. $this->forgetClass($className);
  1324. }
  1325. }
  1326. }
  1327. /**
  1328. * Forgets all reflection data related to the specified class
  1329. *
  1330. * @param string $className Name of the class to forget
  1331. * @return void
  1332. */
  1333. protected function forgetClass($className) {
  1334. $this->systemLogger->log('Forget class ' . $className, LOG_DEBUG);
  1335. if (isset($this->classesCurrentlyBeingForgotten[$className])) {
  1336. $this->systemLogger->log('Detected recursion while forgetting class ' . $className, LOG_WARNING);
  1337. return;
  1338. }
  1339. $this->classesCurrentlyBeingForgotten[$className] = TRUE;
  1340. if (class_exists($className)) {
  1341. $interfaceNames = class_implements($className);
  1342. foreach ($interfaceNames as $interfaceName) {
  1343. if (isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className])) {
  1344. unset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className]);
  1345. }
  1346. }
  1347. } else {
  1348. foreach ($this->availableClassNames as $interfaceNames) {
  1349. foreach ($interfaceNames as $interfaceName) {
  1350. if (isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className])) {
  1351. unset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className]);
  1352. }
  1353. }
  1354. }
  1355. }
  1356. if (isset($this->classReflectionData[$className][self::DATA_CLASS_SUBCLASSES])) {
  1357. foreach (array_keys($this->classReflectionData[$className][self::DATA_CLASS_SUBCLASSES]) as $subClassName) {
  1358. $this->forgetClass($subClassName);
  1359. }
  1360. }
  1361. foreach (array_keys($this->annotatedClasses) as $annotationClassName) {
  1362. if (isset($this->annotatedClasses[$annotationClassName][$className])) {
  1363. unset($this->annotatedClasses[$annotationClassName][$className]);
  1364. }
  1365. }
  1366. if (isset($this->classSchemata[$className])) {
  1367. unset($this->classSchemata[$className]);
  1368. }
  1369. foreach (array_keys($this->classesByMethodAnnotations) as $annotationClassName) {
  1370. unset($this->classesByMethodAnnotations[$annotationClassName][$className]);
  1371. }
  1372. unset($this->classReflectionData[$className]);
  1373. unset($this->classesCurrentlyBeingForgotten[$className]);
  1374. }
  1375. /**
  1376. * Tries to load the reflection data from the compile time cache.
  1377. *
  1378. * The compile time cache is only supported for Development context and thus
  1379. * this function will return in any other context.
  1380. *
  1381. * If no reflection data was found, this method will at least load the precompiled
  1382. * reflection data of any possible frozen package. Even if precompiled reflection
  1383. * data could be loaded, FALSE will be returned in order to signal that other
  1384. * packages still need to be reflected.
  1385. *
  1386. * @return boolean TRUE if reflection data could be loaded, otherwise FALSE
  1387. */
  1388. protected function loadClassReflectionCompiletimeCache() {
  1389. $data = $this->reflectionDataCompiletimeCache->get('ReflectionData');
  1390. if ($data === FALSE) {
  1391. if ($this->context->isDevelopment()) {
  1392. $useIgBinary = extension_loaded('igbinary');
  1393. foreach ($this->packageManager->getActivePackages() as $packageKey => $package) {
  1394. if ($this->packageManager->isPackageFrozen($packageKey)) {
  1395. $pathAndFilename = $this->getPrecompiledReflectionStoragePath() . $packageKey . '.dat';
  1396. if (file_exists($pathAndFilename)) {
  1397. $data = ($useIgBinary ? igbinary_unserialize(file_get_contents($pathAndFilename)) : unserialize(file_get_contents($pathAndFilename)));
  1398. foreach ($data as $propertyName => $propertyValue) {
  1399. $this->$propertyName = \TYPO3\FLOW3\Utility\Arrays::arrayMergeRecursiveOverrule($this->$propertyName, $propertyValue);
  1400. }
  1401. }
  1402. }
  1403. }
  1404. }
  1405. return FALSE;
  1406. }
  1407. foreach ($data as $propertyName => $propertyValue) {
  1408. $this->$propertyName = $propertyValue;
  1409. }
  1410. return TRUE;
  1411. }
  1412. /**
  1413. * Loads reflection data from the cache or reflects the class if needed.
  1414. *
  1415. * If the class is completely unknown, this method won't try to load or reflect
  1416. * it. If it is known and reflection data has been loaded already, it won't be
  1417. * loaded again.
  1418. *
  1419. * In Production context, with frozen caches, this method will load reflection
  1420. * data for the specified class from the runtime cache.
  1421. *
  1422. * @param string $className Name of the class to load data for
  1423. * @return void
  1424. */
  1425. protected function loadOrReflectClassIfNecessary($className) {
  1426. if (!isset($this->classReflectionData[$className]) || is_array($this->classReflectionData[$className])) {
  1427. return;
  1428. }
  1429. if ($this->loadFromClassSchemaRuntimeCache === TRUE) {
  1430. $this->classReflectionData[$className] = $this->reflectionDataRuntimeCache->get(str_replace('\\', '_', $className));
  1431. } else {
  1432. $this->reflectClass($className);
  1433. }
  1434. }
  1435. /**
  1436. * Stores the current reflection data related to classes of the specified package
  1437. * in the PrecompiledReflectionData directory for the current context.
  1438. *
  1439. * This method is used by the package manager.
  1440. *
  1441. * @param string $packageKey
  1442. * @return void
  1443. */
  1444. public function freezePackageReflection($packageKey) {
  1445. $package = $this->packageManager->getPackage($packageKey);
  1446. $packageNamespace = $package->getPackageNamespace() . '\\';
  1447. $packageNamespaceLength = strlen($packageNamespace);
  1448. $reflectionData = array(
  1449. 'classReflectionData' => $this->classReflectionData,
  1450. 'classSchemata' => $this->classSchemata,
  1451. 'annotatedClasses' => $this->annotatedClasses,
  1452. 'classesByMethodAnnotations' => $this->classesByMethodAnnotations
  1453. );
  1454. foreach (array_keys($reflectionData['classReflectionData']) as $className) {
  1455. if (substr($className, 0, $packageNamespaceLength) !== $packageNamespace) {
  1456. unset($reflectionData['classReflectionData'][$className]);
  1457. }
  1458. }
  1459. foreach (array_keys($reflectionData['classSchemata']) as $className) {
  1460. if (substr($className, 0, $packageNamespaceLength) !== $packageNamespace) {
  1461. unset($reflectionData['classSchemata'][$className]);
  1462. }
  1463. }
  1464. foreach (array_keys($reflectionData['annotatedClasses']) as $className) {
  1465. if (substr($className, 0, $packageNamespaceLength) !== $packageNamespace) {
  1466. unset($reflectionData['annotatedClasses'][$className]);
  1467. }
  1468. }
  1469. if (isset($reflectionData['classesByMethodAnnotations'])) {
  1470. foreach ($reflectionData['classesByMethodAnnotations'] as $annotationClassName => $classNames) {
  1471. foreach ($classNames as $index => $className) {
  1472. if (substr($className, 0, $packageNamespaceLength) !== $packageNamespace) {
  1473. unset($reflectionData['classesByMethodAnnotations'][$annotationClassName][$index]);
  1474. }
  1475. }
  1476. }
  1477. }
  1478. $precompiledReflectionStoragePath = $this->getPrecompiledReflectionStoragePath();
  1479. if (!is_dir($precompiledReflectionStoragePath)) {
  1480. Files::createDirectoryRecursively($precompiledReflectionStoragePath);
  1481. }
  1482. $pathAndFilename = $precompiledReflectionStoragePath . $packageKey . '.dat';
  1483. file_put_contents($pathAndFilename, extension_loaded('igbinary') ? igbinary_serialize($reflectionData) : serialize($reflectionData));
  1484. }
  1485. /**
  1486. * Removes the precompiled reflection data of a frozen package
  1487. *
  1488. * This method is used by the package manager.
  1489. *
  1490. * @param string $packageKey The package to remove the data from
  1491. * @return void
  1492. */
  1493. public function unfreezePackageReflection($packageKey) {
  1494. $pathAndFilename = $this->getPrecompiledReflectionStoragePath() . $packageKey . '.dat';
  1495. if (file_exists($pathAndFilename)) {
  1496. unlink($pathAndFilename);
  1497. }
  1498. }
  1499. /**
  1500. * Exports the internal reflection data into the ReflectionData cache
  1501. *
  1502. * This method is triggered by a signal which is connected to the bootstrap's
  1503. * shutdown sequence.
  1504. *
  1505. * If the reflection data has previously been loaded from the runtime cache,
  1506. * saving it is omitted as changes are not expected.
  1507. *
  1508. * In Production context the whole cache is written at once and then frozen in
  1509. * order to be consistent. Frozen cache data in Development is only produced for
  1510. * classes contained in frozen packages.
  1511. *
  1512. * @return void
  1513. * @throws \TYPO3\FLOW3\Reflection\Exception if no cache has been injected
  1514. */
  1515. public function saveToCache() {
  1516. if ($this->loadFromClassSchemaRuntimeCache === TRUE) {
  1517. return;
  1518. }
  1519. if (!($this->reflectionDataCompiletimeCache instanceof \TYPO3\FLOW3\Cache\Frontend\FrontendInterface)) {
  1520. throw new \TYPO3\FLOW3\Reflection\Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
  1521. }
  1522. if (count($this->updatedReflectionData) > 0) {
  1523. $this->log(sprintf('Found %s classes whose reflection data was not cached previously.', count($this->updatedReflectionData)), LOG_DEBUG);
  1524. foreach (array_keys($this->updatedReflectionData) as $className) {
  1525. $this->statusCache->set(str_replace('\\', '_', $className), '');
  1526. }
  1527. $data = array();
  1528. $propertyNames = array(
  1529. 'classReflectionData',
  1530. 'classSchemata',
  1531. 'annotatedClasses',
  1532. 'classesByMethodAnnotations'
  1533. );
  1534. foreach ($propertyNames as $propertyName) {
  1535. $data[$propertyName] = $this->$propertyName;
  1536. }
  1537. $this->reflectionDataCompiletimeCache->set('ReflectionData', $data);
  1538. }
  1539. if ($this->context->isProduction()) {
  1540. $this->reflectionDataRuntimeCache->flush();
  1541. $classNames = array();
  1542. foreach ($this->classReflectionData as $className => $reflectionData) {
  1543. $classNames[$className] = TRUE;
  1544. $this->reflectionDataRuntimeCache->set(str_replace('\\', '_', $className), $reflectionData);
  1545. if (isset($this->classSchemata[$className])) {
  1546. $this->classSchemataRuntimeCache->set(str_replace('\\', '_', $className), $this->classSchemata[$className]);
  1547. }
  1548. }
  1549. $this->reflectionDataRuntimeCache->set('__classNames', $classNames);
  1550. $this->reflectionDataRuntimeCache->set('__annotatedClasses', $this->annotatedClasses);
  1551. $this->reflectionDataRuntimeCache->getBackend()->freeze();
  1552. $this->classSchemataRuntimeCache->getBackend()->freeze();
  1553. $this->log(sprintf('Built and froze reflection runtime caches (%s classes).', count($this->classReflectionData)), LOG_INFO);
  1554. } elseif ($this->context->isDevelopment()) {
  1555. foreach (array_keys($this->packageManager->getFrozenPackages()) as $packageKey) {
  1556. $pathAndFilename = $this->getPrecompiledReflectionStoragePath() . $packageKey . '.dat';
  1557. if (!file_exists($pathAndFilename)) {
  1558. $this->log(sprintf('Rebuilding precompiled reflection data for frozen package %s.', $packageKey), LOG_INFO);
  1559. $this->freezePackageReflection($packageKey);
  1560. }
  1561. }
  1562. }
  1563. }
  1564. /**
  1565. * Writes the given message along with the additional information into the log.
  1566. *
  1567. * @param string $message The message to log
  1568. * @param integer $severity An integer value, one of the LOG_* constants
  1569. * @param mixed $additionalData A variable containing more information about the event to be logged
  1570. * @return void
  1571. */
  1572. protected function log($message, $severity = LOG_INFO, $additionalData = NULL) {
  1573. if (is_object($this->systemLogger)) {
  1574. $this->systemLogger->log($message, $severity, $additionalData);
  1575. }
  1576. }
  1577. /**
  1578. * Determines the path to the precompiled reflection data.
  1579. *
  1580. * @return string
  1581. */
  1582. protected function getPrecompiledReflectionStoragePath() {
  1583. return Files::concatenatePaths(array($this->environment->getPathToTemporaryDirectory(), 'PrecompiledReflectionData/')) . '/';
  1584. }
  1585. }
  1586. ?>