2 /***************************************************************
5 * (c) 2009 Christopher Hlubek <hlubek@networkteam.com>
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
26 * A backport of the FLOW3 reflection service for aquiring reflection based information.
27 * Most of the code is based on the FLOW3 reflection service.
30 * @subpackage Reflection
33 class Tx_Extbase_Reflection_Service
implements t3lib_Singleton
{
36 * Whether this service has been initialized.
40 protected $initialized = FALSE;
43 * @var t3lib_cache_frontend_VariableFrontend
48 * Whether class alterations should be detected on each initialization.
52 protected $detectClassChanges = FALSE;
55 * All available class names to consider. Class name = key, value is the
56 * UNIX timestamp the class was reflected.
60 protected $reflectedClassNames = array();
63 * Array of tags and the names of classes which are tagged with them.
67 protected $taggedClasses = array();
70 * Array of class names and their tags and values.
74 protected $classTagsValues = array();
77 * Array of class names, method names and their tags and values.
81 protected $methodTagsValues = array();
84 * Array of class names, method names, their parameters and additional
85 * information about the parameters.
89 protected $methodParameters = array();
92 * Array of class names and names of their properties.
96 protected $classPropertyNames = array();
99 * Array of class names, property names and their tags and values.
103 protected $propertyTagsValues = array();
106 * List of tags which are ignored while reflecting class and method annotations.
110 protected $ignoredTags = array('package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const');
113 * Indicates whether the Reflection cache needs to be updated.
115 * This flag needs to be set as soon as new Reflection information was
118 * @see reflectClass()
119 * @see getMethodReflection()
123 protected $cacheNeedsUpdate = FALSE;
128 * The cache must be set before initializing the Reflection Service.
130 * @param t3lib_cache_frontend_VariableFrontend $cache Cache for the Reflection service
133 public function setCache(t3lib_cache_frontend_VariableFrontend
$cache) {
134 $this->cache
= $cache;
138 * Initializes this service
140 * @param array $classNamesToReflect Names of available classes to consider in this reflection service
143 public function initialize() {
144 if ($this->initialized
) throw new Tx_Extbase_Reflection_Exception('The Reflection Service can only be initialized once.', 1232044696);
146 $this->loadFromCache();
148 $this->initialized
= TRUE;
150 $this->buildClassSchemata();
154 * Returns whether the Reflection Service is initialized.
156 * @return boolean true if the Reflection Service is initialized, otherwise false
158 public function isInitialized() {
159 return $this->initialized
;
163 * Shuts the Reflection Service down.
167 public function shutdown() {
168 if ($this->cacheNeedsUpdate
) {
169 $this->saveToCache();
174 * Searches for and returns all names of classes which are tagged by the specified tag.
175 * If no classes were found, an empty array is returned.
177 * @param string $tag Tag to search for
178 * @return array An array of class names tagged by the tag
179 * @author Robert Lemke <robert@typo3.org>
182 public function getClassNamesByTag($tag) {
183 if ($this->initialized
!== TRUE) throw new Tx_Extbase_Reflection_Exception('Reflection has not yet been initialized.', 1238667825);
184 return (isset($this->taggedClasses
[$tag])) ?
$this->taggedClasses
[$tag] : array();
188 * Returns the names of all properties of the specified class
190 * @param string $className Name of the class to return the property names of
191 * @return array An array of property names or an empty array if none exist
193 public function getClassPropertyNames($className) {
194 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
195 return (isset($this->classPropertyNames
[$className])) ?
$this->classPropertyNames
[$className] : array();
199 * Returns the class schema for the given class
201 * @param mixed $classNameOrObject The class name or an object
202 * @return Tx_Extbase_Reflection_ClassSchema
203 * @author Karsten Dambekalns <karsten@typo3.org>
205 public function getClassSchema($classNameOrObject) {
206 $className = is_object($classNameOrObject) ?
get_class($classNameOrObject) : $classNameOrObject;
207 return isset($this->classSchemata
[$className]) ?
$this->classSchemata
[$className] : NULL;
211 * Returns all tags and their values the specified method is tagged with
213 * @param string $className Name of the class containing the method
214 * @param string $methodName Name of the method to return the tags and values of
215 * @return array An array of tags and their values or an empty array of no tags were found
217 public function getMethodTagsValues($className, $methodName) {
218 if (!isset($this->methodTagsValues
[$className][$methodName])) {
219 $this->methodTagsValues
[$className][$methodName] = array();
220 $method = $this->getMethodReflection($className, $methodName);
221 foreach ($method->getTagsValues() as $tag => $values) {
222 if (array_search($tag, $this->ignoredTags
) === FALSE) {
223 $this->methodTagsValues
[$className][$methodName][$tag] = $values;
227 return $this->methodTagsValues
[$className][$methodName];
232 * Returns an array of parameters of the given method. Each entry contains
233 * additional information about the parameter position, type hint etc.
235 * @param string $className Name of the class containing the method
236 * @param string $methodName Name of the method to return parameter information of
237 * @return array An array of parameter names and additional information or an empty array of no parameters were found
239 public function getMethodParameters($className, $methodName) {
240 if (!isset($this->methodParameters
[$className][$methodName])) {
241 $method = $this->getMethodReflection($className, $methodName);
242 $this->methodParameters
[$className][$methodName] = array();
243 foreach($method->getParameters() as $parameterPosition => $parameter) {
244 $this->methodParameters
[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
247 return $this->methodParameters
[$className][$methodName];
251 * Returns all tags and their values the specified class property is tagged with
253 * @param string $className Name of the class containing the property
254 * @param string $propertyName Name of the property 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 getPropertyTagsValues($className, $propertyName) {
258 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
259 if (!isset($this->propertyTagsValues
[$className])) return array();
260 return (isset($this->propertyTagsValues
[$className][$propertyName])) ?
$this->propertyTagsValues
[$className][$propertyName] : array();
264 * Returns the values of the specified class property tag
266 * @param string $className Name of the class containing the property
267 * @param string $propertyName Name of the tagged property
268 * @param string $tag Tag to return the values of
269 if (!isset($this->propertyTagsValues[$className])) return array();
270 * @return array An array of values or an empty array if the tag was not found
271 * @author Robert Lemke <robert@typo3.org>
274 public function getPropertyTagValues($className, $propertyName, $tag) {
275 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
276 if (!isset($this->propertyTagsValues
[$className][$propertyName])) return array();
277 return (isset($this->propertyTagsValues
[$className][$propertyName][$tag])) ?
$this->propertyTagsValues
[$className][$propertyName][$tag] : array();
281 * Tells if the specified class is known to this reflection service and
282 * reflection information is available.
284 * @param string $className Name of the class
285 * @return boolean If the class is reflected by this service
286 * @author Robert Lemke <robert@typo3.org>
289 public function isClassReflected($className) {
290 return isset($this->reflectedClassNames
[$className]);
294 * Tells if the specified class is tagged with the given tag
296 * @param string $className Name of the class
297 * @param string $tag Tag to check for
298 * @return boolean TRUE if the class is tagged with $tag, otherwise FALSE
299 * @author Robert Lemke <robert@typo3.org>
302 public function isClassTaggedWith($className, $tag) {
303 if ($this->initialized
=== FALSE) return FALSE;
304 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
305 if (!isset($this->classTagsValues
[$className])) return FALSE;
306 return isset($this->classTagsValues
[$className][$tag]);
310 * Tells if the specified class property is tagged with the given tag
312 * @param string $className Name of the class
313 * @param string $propertyName Name of the property
314 * @param string $tag Tag to check for
315 * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
316 * @author Robert Lemke <robert@typo3.org>
319 public function isPropertyTaggedWith($className, $propertyName, $tag) {
320 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
321 if (!isset($this->propertyTagsValues
[$className])) return FALSE;
322 if (!isset($this->propertyTagsValues
[$className][$propertyName])) return FALSE;
323 return isset($this->propertyTagsValues
[$className][$propertyName][$tag]);
327 * Reflects the given class and stores the results in this service's properties.
329 * @param string $className Full qualified name of the class to reflect
332 protected function reflectClass($className) {
333 $class = new Tx_Extbase_Reflection_ClassReflection($className);
334 $this->reflectedClassNames
[$className] = time();
336 foreach ($class->getTagsValues() as $tag => $values) {
337 if (array_search($tag, $this->ignoredTags
) === FALSE) {
338 $this->taggedClasses
[$tag][] = $className;
339 $this->classTagsValues
[$className][$tag] = $values;
343 foreach ($class->getProperties() as $property) {
344 $propertyName = $property->getName();
345 $this->classPropertyNames
[$className][] = $propertyName;
347 foreach ($property->getTagsValues() as $tag => $values) {
348 if (array_search($tag, $this->ignoredTags
) === FALSE) {
349 $this->propertyTagsValues
[$className][$propertyName][$tag] = $values;
354 foreach ($class->getMethods() as $method) {
355 $methodName = $method->getName();
356 foreach ($method->getTagsValues() as $tag => $values) {
357 if (array_search($tag, $this->ignoredTags
) === FALSE) {
358 $this->methodTagsValues
[$className][$methodName][$tag] = $values;
362 foreach ($method->getParameters() as $parameter) {
363 $this->methodParameters
[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $method);
366 ksort($this->reflectedClassNames
);
368 $this->cacheNeedsUpdate
= TRUE;
372 * Builds class schemata from classes annotated as entities or value objects
375 * @author Robert Lemke <robert@typo3.org>
376 * @author Karsten Dambekalns <karsten@typo3.org>
378 protected function buildClassSchemata() {
379 $this->classSchemata
= array();
381 $classNames = array_merge($this->getClassNamesByTag('entity'), $this->getClassNamesByTag('valueobject'));
382 foreach ($classNames as $className) {
383 $classSchema = new Tx_Extbase_Reflection_ClassSchema($className);
384 if ($this->isClassTaggedWith($className, 'entity')) {
385 $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema
::MODELTYPE_ENTITY
);
387 $possibleRepositoryClassName = str_replace('_Model_', '_Repository_', $className) . 'Repository';
388 if ($this->isClassReflected($possibleRepositoryClassName)) {
389 $classSchema->setAggregateRoot(TRUE);
391 } elseif ($this->isClassTaggedWith($className, 'valueobject')) {
392 $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema
::MODELTYPE_VALUEOBJECT
);
395 foreach ($this->getClassPropertyNames($className) as $propertyName) {
396 if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
397 $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'));
399 if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
400 $classSchema->setUUIDPropertyName($propertyName);
402 if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
403 $classSchema->markAsIdentityProperty($propertyName);
406 $this->classSchemata
[$className] = $classSchema;
411 * Converts the given parameter reflection into an information array
413 * @param ReflectionParameter $parameter The parameter to reflect
414 * @return array Parameter information array
416 protected function convertParameterReflectionToArray(ReflectionParameter
$parameter, $parameterPosition, ReflectionMethod
$method = NULL) {
417 $parameterInformation = array(
418 'position' => $parameterPosition,
419 'byReference' => $parameter->isPassedByReference() ?
TRUE : FALSE,
420 'array' => $parameter->isArray() ?
TRUE : FALSE,
421 'optional' => $parameter->isOptional() ?
TRUE : FALSE,
422 'allowsNull' => $parameter->allowsNull() ?
TRUE : FALSE
425 $parameterClass = $parameter->getClass();
426 $parameterInformation['class'] = ($parameterClass !== NULL) ?
$parameterClass->getName() : NULL;
427 if ($parameter->isDefaultValueAvailable()) {
428 $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
430 if ($parameterClass !== NULL) {
431 $parameterInformation['type'] = $parameterClass->getName();
432 } elseif ($method !== NULL) {
433 $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
434 if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
435 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
436 if (count($explodedParameters) >= 2) {
437 $parameterInformation['type'] = $explodedParameters[0];
441 if (isset($parameterInformation['type']) && $parameterInformation['type']{0} === '\\') {
442 $parameterInformation['type'] = substr($parameterInformation['type'], 1);
444 return $parameterInformation;
448 * Returns the Reflection of a method.
450 * @param string $className Name of the class containing the method
451 * @param string $methodName Name of the method to return the Reflection for
452 * @return Tx_Extbase_Reflection_MethodReflection the method Reflection object
454 protected function getMethodReflection($className, $methodName) {
455 if (!isset($this->methodReflections
[$className][$methodName])) {
456 $this->methodReflections
[$className][$methodName] = new Tx_Extbase_Reflection_MethodReflection($className, $methodName);
457 $this->cacheNeedsUpdate
= TRUE;
459 return $this->methodReflections
[$className][$methodName];
463 * Tries to load the reflection data from this service's cache.
468 protected function loadFromCache() {
469 if ($this->cache
->has('ReflectionData')) {
470 $data = $this->cache
->get('ReflectionData');
471 foreach ($data as $propertyName => $propertyValue) {
472 $this->$propertyName = $propertyValue;
478 * Exports the internal reflection data into the ReflectionData cache.
483 protected function saveToCache() {
484 if (!is_object($this->cache
)) {
485 throw new Tx_Extbase_Reflection_Exception(
486 'A cache must be injected before initializing the Reflection Service.',
492 $propertyNames = array(
493 'reflectedClassNames',
494 'classPropertyNames',
498 'propertyTagsValues',
501 foreach ($propertyNames as $propertyName) {
502 $data[$propertyName] = $this->$propertyName;
504 $this->cache
->set('ReflectionData', $data);