2 namespace TYPO3\CMS\Extbase\Reflection
;
4 /***************************************************************
7 * (c) 2010-2012 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
31 use TYPO3\CMS\Core\Utility\ClassNamingUtility
;
34 * A backport of the FLOW3 reflection service for aquiring reflection based information.
35 * Most of the code is based on the FLOW3 reflection service.
39 class ReflectionService
implements \TYPO3\CMS\Core\SingletonInterface
{
42 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
44 protected $objectManager;
47 * Whether this service has been initialized.
51 protected $initialized = FALSE;
54 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
59 * Whether class alterations should be detected on each initialization.
63 protected $detectClassChanges = FALSE;
66 * All available class names to consider. Class name = key, value is the
67 * UNIX timestamp the class was reflected.
71 protected $reflectedClassNames = array();
74 * Array of tags and the names of classes which are tagged with them.
78 protected $taggedClasses = array();
81 * Array of class names and their tags and values.
85 protected $classTagsValues = array();
88 * Array of class names, method names and their tags and values.
92 protected $methodTagsValues = array();
95 * Array of class names, method names, their parameters and additional
96 * information about the parameters.
100 protected $methodParameters = array();
103 * Array of class names and names of their properties.
107 protected $classPropertyNames = array();
110 * Array of class names, property names and their tags and values.
114 protected $propertyTagsValues = array();
117 * List of tags which are ignored while reflecting class and method annotations.
121 protected $ignoredTags = array('package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const');
124 * Indicates whether the Reflection cache needs to be updated.
126 * This flag needs to be set as soon as new Reflection information was
129 * @see reflectClass()
130 * @see getMethodReflection()
133 protected $dataCacheNeedsUpdate = FALSE;
136 * Local cache for Class schemata
140 protected $classSchemata = array();
143 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
145 protected $configurationManager;
150 protected $cacheIdentifier;
155 protected $methodReflections;
158 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
161 public function injectObjectManager(\TYPO3\CMS\Extbase\
Object\ObjectManagerInterface
$objectManager) {
162 $this->objectManager
= $objectManager;
166 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
169 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
$configurationManager) {
170 $this->configurationManager
= $configurationManager;
174 * Sets the data cache.
176 * The cache must be set before initializing the Reflection Service.
178 * @param \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache Cache for the Reflection service
181 public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
$dataCache) {
182 $this->dataCache
= $dataCache;
186 * Initializes this service
191 public function initialize() {
192 if ($this->initialized
) {
193 throw new \TYPO3\CMS\Extbase\Reflection\
Exception('The Reflection Service can only be initialized once.', 1232044696);
195 $frameworkConfiguration = $this->configurationManager
->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
196 $this->cacheIdentifier
= 'ReflectionData_' . $frameworkConfiguration['extensionName'];
197 $this->loadFromCache();
198 $this->initialized
= TRUE;
202 * Returns whether the Reflection Service is initialized.
204 * @return boolean true if the Reflection Service is initialized, otherwise false
206 public function isInitialized() {
207 return $this->initialized
;
211 * Shuts the Reflection Service down.
215 public function shutdown() {
216 if ($this->dataCacheNeedsUpdate
) {
217 $this->saveToCache();
219 $this->initialized
= FALSE;
223 * Returns the names of all properties of the specified class
225 * @param string $className Name of the class to return the property names of
226 * @return array An array of property names or an empty array if none exist
228 public function getClassPropertyNames($className) {
229 if (!isset($this->reflectedClassNames
[$className])) {
230 $this->reflectClass($className);
232 return isset($this->classPropertyNames
[$className]) ?
$this->classPropertyNames
[$className] : array();
236 * Returns the class schema for the given class
238 * @param mixed $classNameOrObject The class name or an object
239 * @return \TYPO3\CMS\Extbase\Reflection\ClassSchema
241 public function getClassSchema($classNameOrObject) {
242 $className = is_object($classNameOrObject) ?
get_class($classNameOrObject) : $classNameOrObject;
243 if (isset($this->classSchemata
[$className])) {
244 return $this->classSchemata
[$className];
246 return $this->buildClassSchema($className);
251 * Returns all tags and their values the specified method is tagged with
253 * @param string $className Name of the class containing the method
254 * @param string $methodName Name of the method to return the tags and values of
255 * @return array An array of tags and their values or an empty array of no tags were found
257 public function getMethodTagsValues($className, $methodName) {
258 if (!isset($this->methodTagsValues
[$className][$methodName])) {
259 $this->methodTagsValues
[$className][$methodName] = array();
260 $method = $this->getMethodReflection($className, $methodName);
261 foreach ($method->getTagsValues() as $tag => $values) {
262 if (array_search($tag, $this->ignoredTags
) === FALSE) {
263 $this->methodTagsValues
[$className][$methodName][$tag] = $values;
267 return $this->methodTagsValues
[$className][$methodName];
271 * Returns an array of parameters of the given method. Each entry contains
272 * additional information about the parameter position, type hint etc.
274 * @param string $className Name of the class containing the method
275 * @param string $methodName Name of the method to return parameter information of
276 * @return array An array of parameter names and additional information or an empty array of no parameters were found
278 public function getMethodParameters($className, $methodName) {
279 if (!isset($this->methodParameters
[$className][$methodName])) {
280 $method = $this->getMethodReflection($className, $methodName);
281 $this->methodParameters
[$className][$methodName] = array();
282 foreach ($method->getParameters() as $parameterPosition => $parameter) {
283 $this->methodParameters
[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
286 return $this->methodParameters
[$className][$methodName];
290 * Returns all tags and their values the specified class property is tagged with
292 * @param string $className Name of the class containing the property
293 * @param string $propertyName Name of the property to return the tags and values of
294 * @return array An array of tags and their values or an empty array of no tags were found
296 public function getPropertyTagsValues($className, $propertyName) {
297 if (!isset($this->reflectedClassNames
[$className])) {
298 $this->reflectClass($className);
300 if (!isset($this->propertyTagsValues
[$className])) {
303 return isset($this->propertyTagsValues
[$className][$propertyName]) ?
$this->propertyTagsValues
[$className][$propertyName] : array();
307 * Returns the values of the specified class property tag
309 * @param string $className Name of the class containing the property
310 * @param string $propertyName Name of the tagged property
311 * @param string $tag Tag to return the values of
312 * @return array An array of values or an empty array if the tag was not found
315 public function getPropertyTagValues($className, $propertyName, $tag) {
316 if (!isset($this->reflectedClassNames
[$className])) {
317 $this->reflectClass($className);
319 if (!isset($this->propertyTagsValues
[$className][$propertyName])) {
322 return isset($this->propertyTagsValues
[$className][$propertyName][$tag]) ?
$this->propertyTagsValues
[$className][$propertyName][$tag] : array();
326 * Tells if the specified class is known to this reflection service and
327 * reflection information is available.
329 * @param string $className Name of the class
330 * @return boolean If the class is reflected by this service
333 public function isClassReflected($className) {
334 return isset($this->reflectedClassNames
[$className]);
338 * Tells if the specified class is tagged with the given tag
340 * @param string $className Name of the class
341 * @param string $tag Tag to check for
342 * @return boolean TRUE if the class is tagged with $tag, otherwise FALSE
345 public function isClassTaggedWith($className, $tag) {
346 if ($this->initialized
=== FALSE) {
349 if (!isset($this->reflectedClassNames
[$className])) {
350 $this->reflectClass($className);
352 if (!isset($this->classTagsValues
[$className])) {
355 return isset($this->classTagsValues
[$className][$tag]);
359 * Tells if the specified class property is tagged with the given tag
361 * @param string $className Name of the class
362 * @param string $propertyName Name of the property
363 * @param string $tag Tag to check for
364 * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
367 public function isPropertyTaggedWith($className, $propertyName, $tag) {
368 if (!isset($this->reflectedClassNames
[$className])) {
369 $this->reflectClass($className);
371 if (!isset($this->propertyTagsValues
[$className])) {
374 if (!isset($this->propertyTagsValues
[$className][$propertyName])) {
377 return isset($this->propertyTagsValues
[$className][$propertyName][$tag]);
381 * Reflects the given class and stores the results in this service's properties.
383 * @param string $className Full qualified name of the class to reflect
386 protected function reflectClass($className) {
387 $class = new \TYPO3\CMS\Extbase\Reflection\
ClassReflection($className);
388 $this->reflectedClassNames
[$className] = time();
389 foreach ($class->getTagsValues() as $tag => $values) {
390 if (array_search($tag, $this->ignoredTags
) === FALSE) {
391 $this->taggedClasses
[$tag][] = $className;
392 $this->classTagsValues
[$className][$tag] = $values;
395 foreach ($class->getProperties() as $property) {
396 $propertyName = $property->getName();
397 $this->classPropertyNames
[$className][] = $propertyName;
398 foreach ($property->getTagsValues() as $tag => $values) {
399 if (array_search($tag, $this->ignoredTags
) === FALSE) {
400 $this->propertyTagsValues
[$className][$propertyName][$tag] = $values;
404 foreach ($class->getMethods() as $method) {
405 $methodName = $method->getName();
406 foreach ($method->getTagsValues() as $tag => $values) {
407 if (array_search($tag, $this->ignoredTags
) === FALSE) {
408 $this->methodTagsValues
[$className][$methodName][$tag] = $values;
411 foreach ($method->getParameters() as $parameterPosition => $parameter) {
412 $this->methodParameters
[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
415 ksort($this->reflectedClassNames
);
416 $this->dataCacheNeedsUpdate
= TRUE;
420 * Builds class schemata from classes annotated as entities or value objects
422 * @param string $className
423 * @throws Exception\UnknownClassException
424 * @return \TYPO3\CMS\Extbase\Reflection\ClassSchema The class schema
426 protected function buildClassSchema($className) {
427 if (!class_exists($className)) {
428 throw new \TYPO3\CMS\Extbase\Reflection\Exception\
UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
430 $classSchema = $this->objectManager
->get('TYPO3\\CMS\\Extbase\\Reflection\\ClassSchema', $className);
431 if (is_subclass_of($className, 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity')) {
432 $classSchema->setModelType(\TYPO3\CMS\Extbase\Reflection\ClassSchema
::MODELTYPE_ENTITY
);
433 $possibleRepositoryClassName = ClassNamingUtility
::translateModelNameToRepositoryName($className);
434 if (class_exists($possibleRepositoryClassName)) {
435 $classSchema->setAggregateRoot(TRUE);
437 } elseif (is_subclass_of($className, 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractValueObject')) {
438 $classSchema->setModelType(\TYPO3\CMS\Extbase\Reflection\ClassSchema
::MODELTYPE_VALUEOBJECT
);
442 foreach ($this->getClassPropertyNames($className) as $propertyName) {
443 if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
444 $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
445 $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
447 if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
448 $classSchema->setUuidPropertyName($propertyName);
450 if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
451 $classSchema->markAsIdentityProperty($propertyName);
454 $this->classSchemata
[$className] = $classSchema;
455 $this->dataCacheNeedsUpdate
= TRUE;
460 * Converts the given parameter reflection into an information array
462 * @param \ReflectionParameter $parameter The parameter to reflect
463 * @param integer $parameterPosition
464 * @param \ReflectionMethod|NULL $method
465 * @return array Parameter information array
467 protected function convertParameterReflectionToArray(\ReflectionParameter
$parameter, $parameterPosition, \ReflectionMethod
$method = NULL) {
468 $parameterInformation = array(
469 'position' => $parameterPosition,
470 'byReference' => $parameter->isPassedByReference() ?
TRUE : FALSE,
471 'array' => $parameter->isArray() ?
TRUE : FALSE,
472 'optional' => $parameter->isOptional() ?
TRUE : FALSE,
473 'allowsNull' => $parameter->allowsNull() ?
TRUE : FALSE
475 $parameterClass = $parameter->getClass();
476 $parameterInformation['class'] = $parameterClass !== NULL ?
$parameterClass->getName() : NULL;
477 if ($parameter->isDefaultValueAvailable()) {
478 $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
480 if ($parameterClass !== NULL) {
481 $parameterInformation['type'] = $parameterClass->getName();
482 } elseif ($method !== NULL) {
483 $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
484 if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
485 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
486 if (count($explodedParameters) >= 2) {
487 $parameterInformation['type'] = $explodedParameters[0];
491 if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
492 $parameterInformation['type'] = substr($parameterInformation['type'], 1);
494 return $parameterInformation;
498 * Returns the Reflection of a method.
500 * @param string $className Name of the class containing the method
501 * @param string $methodName Name of the method to return the Reflection for
502 * @return \TYPO3\CMS\Extbase\Reflection\MethodReflection the method Reflection object
504 protected function getMethodReflection($className, $methodName) {
505 if (!isset($this->methodReflections
[$className][$methodName])) {
506 $this->methodReflections
[$className][$methodName] = new \TYPO3\CMS\Extbase\Reflection\
MethodReflection($className, $methodName);
507 $this->dataCacheNeedsUpdate
= TRUE;
509 return $this->methodReflections
[$className][$methodName];
513 * Tries to load the reflection data from this service's cache.
517 protected function loadFromCache() {
518 $data = $this->dataCache
->get($this->cacheIdentifier
);
519 if ($data !== FALSE) {
520 foreach ($data as $propertyName => $propertyValue) {
521 $this->{$propertyName} = $propertyValue;
527 * Exports the internal reflection data into the ReflectionData cache.
532 protected function saveToCache() {
533 if (!is_object($this->dataCache
)) {
534 throw new \TYPO3\CMS\Extbase\Reflection\
Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
537 $propertyNames = array(
538 'reflectedClassNames',
539 'classPropertyNames',
543 'propertyTagsValues',
547 foreach ($propertyNames as $propertyName) {
548 $data[$propertyName] = $this->{$propertyName};
550 $this->dataCache
->set($this->cacheIdentifier
, $data);