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
34 // FIXME This class should be a Prototype
35 class Tx_Extbase_Reflection_Service
implements t3lib_Singleton
{
38 * Whether this service has been initialized.
42 protected $initialized = FALSE;
45 * @var t3lib_cache_frontend_VariableFrontend
50 * Whether class alterations should be detected on each initialization.
54 protected $detectClassChanges = FALSE;
57 * All available class names to consider. Class name = key, value is the
58 * UNIX timestamp the class was reflected.
62 protected $reflectedClassNames = array();
65 * Array of tags and the names of classes which are tagged with them.
69 protected $taggedClasses = array();
72 * Array of class names and their tags and values.
76 protected $classTagsValues = array();
79 * Array of class names, method names and their tags and values.
83 protected $methodTagsValues = array();
86 * Array of class names, method names, their parameters and additional
87 * information about the parameters.
91 protected $methodParameters = array();
94 * Array of class names and names of their properties.
98 protected $classPropertyNames = array();
101 * Array of class names, property names and their tags and values.
105 protected $propertyTagsValues = array();
108 * List of tags which are ignored while reflecting class and method annotations.
112 protected $ignoredTags = array('package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const');
115 * Indicates whether the Reflection cache needs to be updated.
117 * This flag needs to be set as soon as new Reflection information was
120 * @see reflectClass()
121 * @see getMethodReflection()
125 protected $dataCacheNeedsUpdate = FALSE;
128 * Local cache for Class schemata
131 protected $classSchemata = array();
134 * Sets the data cache.
136 * The cache must be set before initializing the Reflection Service.
138 * @param t3lib_cache_frontend_VariableFrontend $dataCache Cache for the Reflection service
141 public function setDataCache(t3lib_cache_frontend_VariableFrontend
$dataCache) {
142 $this->dataCache
= $dataCache;
146 * Initializes this service
148 * @param array $classNamesToReflect Names of available classes to consider in this reflection service
151 public function initialize() {
152 if ($this->initialized
) throw new Tx_Extbase_Reflection_Exception('The Reflection Service can only be initialized once.', 1232044696);
154 $this->loadFromCache();
156 $this->initialized
= TRUE;
160 * Returns whether the Reflection Service is initialized.
162 * @return boolean true if the Reflection Service is initialized, otherwise false
164 public function isInitialized() {
165 return $this->initialized
;
169 * Shuts the Reflection Service down.
173 public function shutdown() {
174 if ($this->dataCacheNeedsUpdate
) {
175 $this->saveToCache();
180 * Returns the names of all properties of the specified class
182 * @param string $className Name of the class to return the property names of
183 * @return array An array of property names or an empty array if none exist
185 public function getClassPropertyNames($className) {
186 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
187 return (isset($this->classPropertyNames
[$className])) ?
$this->classPropertyNames
[$className] : array();
191 * Returns the class schema for the given class
193 * @param mixed $classNameOrObject The class name or an object
194 * @return Tx_Extbase_Reflection_ClassSchema
195 * @author Karsten Dambekalns <karsten@typo3.org>
197 public function getClassSchema($classNameOrObject) {
198 $className = is_object($classNameOrObject) ?
get_class($classNameOrObject) : $classNameOrObject;
199 if (isset($this->classSchemata
[$className])) {
200 return $this->classSchemata
[$className];
202 return $this->buildClassSchema($className);
208 * Returns all tags and their values the specified method is tagged with
210 * @param string $className Name of the class containing the method
211 * @param string $methodName Name of the method to return the tags and values of
212 * @return array An array of tags and their values or an empty array of no tags were found
214 public function getMethodTagsValues($className, $methodName) {
215 if (!isset($this->methodTagsValues
[$className][$methodName])) {
216 $this->methodTagsValues
[$className][$methodName] = array();
217 $method = $this->getMethodReflection($className, $methodName);
218 foreach ($method->getTagsValues() as $tag => $values) {
219 if (array_search($tag, $this->ignoredTags
) === FALSE) {
220 $this->methodTagsValues
[$className][$methodName][$tag] = $values;
224 return $this->methodTagsValues
[$className][$methodName];
229 * Returns an array of parameters of the given method. Each entry contains
230 * additional information about the parameter position, type hint etc.
232 * @param string $className Name of the class containing the method
233 * @param string $methodName Name of the method to return parameter information of
234 * @return array An array of parameter names and additional information or an empty array of no parameters were found
236 public function getMethodParameters($className, $methodName) {
237 if (!isset($this->methodParameters
[$className][$methodName])) {
238 $method = $this->getMethodReflection($className, $methodName);
239 $this->methodParameters
[$className][$methodName] = array();
240 foreach($method->getParameters() as $parameterPosition => $parameter) {
241 $this->methodParameters
[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
244 return $this->methodParameters
[$className][$methodName];
248 * Returns all tags and their values the specified class property is tagged with
250 * @param string $className Name of the class containing the property
251 * @param string $propertyName Name of the property to return the tags and values of
252 * @return array An array of tags and their values or an empty array of no tags were found
254 public function getPropertyTagsValues($className, $propertyName) {
255 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
256 if (!isset($this->propertyTagsValues
[$className])) return array();
257 return (isset($this->propertyTagsValues
[$className][$propertyName])) ?
$this->propertyTagsValues
[$className][$propertyName] : array();
261 * Returns the values of the specified class property tag
263 * @param string $className Name of the class containing the property
264 * @param string $propertyName Name of the tagged property
265 * @param string $tag Tag to return the values of
266 * @return array An array of values or an empty array if the tag was not found
267 * @author Robert Lemke <robert@typo3.org>
270 public function getPropertyTagValues($className, $propertyName, $tag) {
271 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
272 if (!isset($this->propertyTagsValues
[$className][$propertyName])) return array();
273 return (isset($this->propertyTagsValues
[$className][$propertyName][$tag])) ?
$this->propertyTagsValues
[$className][$propertyName][$tag] : array();
277 * Tells if the specified class is known to this reflection service and
278 * reflection information is available.
280 * @param string $className Name of the class
281 * @return boolean If the class is reflected by this service
282 * @author Robert Lemke <robert@typo3.org>
285 public function isClassReflected($className) {
286 return isset($this->reflectedClassNames
[$className]);
290 * Tells if the specified class is tagged with the given tag
292 * @param string $className Name of the class
293 * @param string $tag Tag to check for
294 * @return boolean TRUE if the class is tagged with $tag, otherwise FALSE
295 * @author Robert Lemke <robert@typo3.org>
298 public function isClassTaggedWith($className, $tag) {
299 if ($this->initialized
=== FALSE) return FALSE;
300 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
301 if (!isset($this->classTagsValues
[$className])) return FALSE;
302 return isset($this->classTagsValues
[$className][$tag]);
306 * Tells if the specified class property is tagged with the given tag
308 * @param string $className Name of the class
309 * @param string $propertyName Name of the property
310 * @param string $tag Tag to check for
311 * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
312 * @author Robert Lemke <robert@typo3.org>
315 public function isPropertyTaggedWith($className, $propertyName, $tag) {
316 if (!isset($this->reflectedClassNames
[$className])) $this->reflectClass($className);
317 if (!isset($this->propertyTagsValues
[$className])) return FALSE;
318 if (!isset($this->propertyTagsValues
[$className][$propertyName])) return FALSE;
319 return isset($this->propertyTagsValues
[$className][$propertyName][$tag]);
323 * Reflects the given class and stores the results in this service's properties.
325 * @param string $className Full qualified name of the class to reflect
328 protected function reflectClass($className) {
329 $class = new Tx_Extbase_Reflection_ClassReflection($className);
330 $this->reflectedClassNames
[$className] = time();
332 foreach ($class->getTagsValues() as $tag => $values) {
333 if (array_search($tag, $this->ignoredTags
) === FALSE) {
334 $this->taggedClasses
[$tag][] = $className;
335 $this->classTagsValues
[$className][$tag] = $values;
339 foreach ($class->getProperties() as $property) {
340 $propertyName = $property->getName();
341 $this->classPropertyNames
[$className][] = $propertyName;
343 foreach ($property->getTagsValues() as $tag => $values) {
344 if (array_search($tag, $this->ignoredTags
) === FALSE) {
345 $this->propertyTagsValues
[$className][$propertyName][$tag] = $values;
350 foreach ($class->getMethods() as $method) {
351 $methodName = $method->getName();
352 foreach ($method->getTagsValues() as $tag => $values) {
353 if (array_search($tag, $this->ignoredTags
) === FALSE) {
354 $this->methodTagsValues
[$className][$methodName][$tag] = $values;
358 foreach ($method->getParameters() as $parameterPosition => $parameter) {
359 $this->methodParameters
[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
362 ksort($this->reflectedClassNames
);
364 $this->dataCacheNeedsUpdate
= TRUE;
368 * Builds class schemata from classes annotated as entities or value objects
370 * @return Tx_Extbase_Reflection_ClassSchema The class schema
372 protected function buildClassSchema($className) {
373 if (!class_exists($className)) {
376 $classSchema = new Tx_Extbase_Reflection_ClassSchema($className);
377 if (is_subclass_of($className, 'Tx_Extbase_DomainObject_AbstractEntity')) {
378 $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema
::MODELTYPE_ENTITY
);
380 $possibleRepositoryClassName = str_replace('_Model_', '_Repository_', $className) . 'Repository';
381 if (class_exists($possibleRepositoryClassName)) {
382 $classSchema->setAggregateRoot(TRUE);
384 } elseif (is_subclass_of($className, 'Tx_Extbase_DomainObject_AbstractValueObject')) {
385 $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema
::MODELTYPE_VALUEOBJECT
);
390 foreach ($this->getClassPropertyNames($className) as $propertyName) {
391 if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
392 $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
393 $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
395 if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
396 $classSchema->setUUIDPropertyName($propertyName);
398 if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
399 $classSchema->markAsIdentityProperty($propertyName);
402 $this->classSchemata
[$className] = $classSchema;
403 $this->dataCacheNeedsUpdate
= TRUE;
408 * Converts the given parameter reflection into an information array
410 * @param ReflectionParameter $parameter The parameter to reflect
411 * @return array Parameter information array
413 protected function convertParameterReflectionToArray(ReflectionParameter
$parameter, $parameterPosition, ReflectionMethod
$method = NULL) {
414 $parameterInformation = array(
415 'position' => $parameterPosition,
416 'byReference' => $parameter->isPassedByReference() ?
TRUE : FALSE,
417 'array' => $parameter->isArray() ?
TRUE : FALSE,
418 'optional' => $parameter->isOptional() ?
TRUE : FALSE,
419 'allowsNull' => $parameter->allowsNull() ?
TRUE : FALSE
422 $parameterClass = $parameter->getClass();
423 $parameterInformation['class'] = ($parameterClass !== NULL) ?
$parameterClass->getName() : NULL;
424 if ($parameter->isDefaultValueAvailable()) {
425 $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
427 if ($parameterClass !== NULL) {
428 $parameterInformation['type'] = $parameterClass->getName();
429 } elseif ($method !== NULL) {
430 $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
431 if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
432 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
433 if (count($explodedParameters) >= 2) {
434 $parameterInformation['type'] = $explodedParameters[0];
438 if (isset($parameterInformation['type']) && $parameterInformation['type']{0} === '\\') {
439 $parameterInformation['type'] = substr($parameterInformation['type'], 1);
441 return $parameterInformation;
445 * Returns the Reflection of a method.
447 * @param string $className Name of the class containing the method
448 * @param string $methodName Name of the method to return the Reflection for
449 * @return Tx_Extbase_Reflection_MethodReflection the method Reflection object
451 protected function getMethodReflection($className, $methodName) {
452 if (!isset($this->methodReflections
[$className][$methodName])) {
453 $this->methodReflections
[$className][$methodName] = new Tx_Extbase_Reflection_MethodReflection($className, $methodName);
454 $this->dataCacheNeedsUpdate
= TRUE;
456 return $this->methodReflections
[$className][$methodName];
460 * Tries to load the reflection data from this service's cache.
464 protected function loadFromCache() {
465 if ($this->dataCache
->has('ReflectionData')) {
466 $data = $this->dataCache
->get('ReflectionData');
467 foreach ($data as $propertyName => $propertyValue) {
468 $this->$propertyName = $propertyValue;
474 * Exports the internal reflection data into the ReflectionData cache.
478 protected function saveToCache() {
479 if (!is_object($this->dataCache
)) {
480 throw new Tx_Extbase_Reflection_Exception(
481 'A cache must be injected before initializing the Reflection Service.',
487 $propertyNames = array(
488 'reflectedClassNames',
489 'classPropertyNames',
493 'propertyTagsValues',
497 foreach ($propertyNames as $propertyName) {
498 $data[$propertyName] = $this->$propertyName;
500 $this->dataCache
->set('ReflectionData', $data);