PageRenderTime 57ms 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

Large files files are truncated, but you can click here to view the full file

  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

Large files files are truncated, but you can click here to view the full file