[FEATURE] Backport Object Type Converter from Flow
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Reflection / ReflectionService.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Reflection;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2013 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.
9 * All rights reserved
10 *
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.
16 *
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.
21 *
22 *
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.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30
31 use TYPO3\CMS\Core\Utility\ClassNamingUtility;
32
33 /**
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.
36 *
37 * @api
38 */
39 class ReflectionService implements \TYPO3\CMS\Core\SingletonInterface {
40
41 /**
42 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
43 */
44 protected $objectManager;
45
46 /**
47 * Whether this service has been initialized.
48 *
49 * @var boolean
50 */
51 protected $initialized = FALSE;
52
53 /**
54 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
55 */
56 protected $dataCache;
57
58 /**
59 * Whether class alterations should be detected on each initialization.
60 *
61 * @var boolean
62 */
63 protected $detectClassChanges = FALSE;
64
65 /**
66 * All available class names to consider. Class name = key, value is the
67 * UNIX timestamp the class was reflected.
68 *
69 * @var array
70 */
71 protected $reflectedClassNames = array();
72
73 /**
74 * Array of tags and the names of classes which are tagged with them.
75 *
76 * @var array
77 */
78 protected $taggedClasses = array();
79
80 /**
81 * Array of class names and their tags and values.
82 *
83 * @var array
84 */
85 protected $classTagsValues = array();
86
87 /**
88 * Array of class names, method names and their tags and values.
89 *
90 * @var array
91 */
92 protected $methodTagsValues = array();
93
94 /**
95 * Array of class names, method names, their parameters and additional
96 * information about the parameters.
97 *
98 * @var array
99 */
100 protected $methodParameters = array();
101
102 /**
103 * Array of class names and names of their properties.
104 *
105 * @var array
106 */
107 protected $classPropertyNames = array();
108
109 /**
110 * Array of class names, property names and their tags and values.
111 *
112 * @var array
113 */
114 protected $propertyTagsValues = array();
115
116 /**
117 * List of tags which are ignored while reflecting class and method annotations.
118 *
119 * @var array
120 */
121 protected $ignoredTags = array('package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const');
122
123 /**
124 * Indicates whether the Reflection cache needs to be updated.
125 *
126 * This flag needs to be set as soon as new Reflection information was
127 * created.
128 *
129 * @see reflectClass()
130 * @see getMethodReflection()
131 * @var boolean
132 */
133 protected $dataCacheNeedsUpdate = FALSE;
134
135 /**
136 * Local cache for Class schemata
137 *
138 * @var array
139 */
140 protected $classSchemata = array();
141
142 /**
143 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
144 */
145 protected $configurationManager;
146
147 /**
148 * @var string
149 */
150 protected $cacheIdentifier;
151
152 /**
153 * @var array
154 */
155 protected $methodReflections = array();
156
157 /**
158 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
159 * @return void
160 */
161 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager) {
162 $this->objectManager = $objectManager;
163 }
164
165 /**
166 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
167 * @return void
168 */
169 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager) {
170 $this->configurationManager = $configurationManager;
171 }
172
173 /**
174 * Sets the data cache.
175 *
176 * The cache must be set before initializing the Reflection Service.
177 *
178 * @param \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache Cache for the Reflection service
179 * @return void
180 */
181 public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache) {
182 $this->dataCache = $dataCache;
183 }
184
185 /**
186 * Initializes this service
187 *
188 * @throws Exception
189 * @return void
190 */
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);
194 }
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;
199 }
200
201 /**
202 * Returns whether the Reflection Service is initialized.
203 *
204 * @return boolean true if the Reflection Service is initialized, otherwise false
205 */
206 public function isInitialized() {
207 return $this->initialized;
208 }
209
210 /**
211 * Shuts the Reflection Service down.
212 *
213 * @return void
214 */
215 public function shutdown() {
216 if ($this->dataCacheNeedsUpdate) {
217 $this->saveToCache();
218 }
219 $this->initialized = FALSE;
220 }
221
222 /**
223 * Returns the names of all properties of the specified class
224 *
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
227 */
228 public function getClassPropertyNames($className) {
229 if (!isset($this->reflectedClassNames[$className])) {
230 $this->reflectClass($className);
231 }
232 return isset($this->classPropertyNames[$className]) ? $this->classPropertyNames[$className] : array();
233 }
234
235 /**
236 * Returns the class schema for the given class
237 *
238 * @param mixed $classNameOrObject The class name or an object
239 * @return \TYPO3\CMS\Extbase\Reflection\ClassSchema
240 */
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];
245 } else {
246 return $this->buildClassSchema($className);
247 }
248 }
249
250 /**
251 * Wrapper for method_exists() which tells if the given method exists.
252 *
253 * @param string $className Name of the class containing the method
254 * @param string $methodName Name of the method
255 * @return boolean
256 * @api
257 */
258 public function hasMethod($className, $methodName) {
259 try {
260 if (!array_key_exists($className, $this->methodReflections) || !array_key_exists($methodName, $this->methodReflections[$className])) {
261 $this->methodReflections[$className][$methodName] = new \TYPO3\CMS\Extbase\Reflection\MethodReflection($className, $methodName);
262 $this->dataCacheNeedsUpdate = TRUE;
263 }
264 } catch (\ReflectionException $e) {
265 // Method does not exist. Store this information in cache.
266 $this->methodReflections[$className][$methodName] = NULL;
267 }
268 return isset($this->methodReflections[$className][$methodName]);
269 }
270
271 /**
272 * Returns all tags and their values the specified method is tagged with
273 *
274 * @param string $className Name of the class containing the method
275 * @param string $methodName Name of the method to return the tags and values of
276 * @return array An array of tags and their values or an empty array of no tags were found
277 */
278 public function getMethodTagsValues($className, $methodName) {
279 if (!isset($this->methodTagsValues[$className][$methodName])) {
280 $this->methodTagsValues[$className][$methodName] = array();
281 $method = $this->getMethodReflection($className, $methodName);
282 foreach ($method->getTagsValues() as $tag => $values) {
283 if (array_search($tag, $this->ignoredTags) === FALSE) {
284 $this->methodTagsValues[$className][$methodName][$tag] = $values;
285 }
286 }
287 }
288 return $this->methodTagsValues[$className][$methodName];
289 }
290
291 /**
292 * Returns an array of parameters of the given method. Each entry contains
293 * additional information about the parameter position, type hint etc.
294 *
295 * @param string $className Name of the class containing the method
296 * @param string $methodName Name of the method to return parameter information of
297 * @return array An array of parameter names and additional information or an empty array of no parameters were found
298 */
299 public function getMethodParameters($className, $methodName) {
300 if (!isset($this->methodParameters[$className][$methodName])) {
301 $method = $this->getMethodReflection($className, $methodName);
302 $this->methodParameters[$className][$methodName] = array();
303 foreach ($method->getParameters() as $parameterPosition => $parameter) {
304 $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
305 }
306 }
307 return $this->methodParameters[$className][$methodName];
308 }
309
310 /**
311 * Returns all tags and their values the specified class property is tagged with
312 *
313 * @param string $className Name of the class containing the property
314 * @param string $propertyName Name of the property to return the tags and values of
315 * @return array An array of tags and their values or an empty array of no tags were found
316 */
317 public function getPropertyTagsValues($className, $propertyName) {
318 if (!isset($this->reflectedClassNames[$className])) {
319 $this->reflectClass($className);
320 }
321 if (!isset($this->propertyTagsValues[$className])) {
322 return array();
323 }
324 return isset($this->propertyTagsValues[$className][$propertyName]) ? $this->propertyTagsValues[$className][$propertyName] : array();
325 }
326
327 /**
328 * Returns the values of the specified class property tag
329 *
330 * @param string $className Name of the class containing the property
331 * @param string $propertyName Name of the tagged property
332 * @param string $tag Tag to return the values of
333 * @return array An array of values or an empty array if the tag was not found
334 * @api
335 */
336 public function getPropertyTagValues($className, $propertyName, $tag) {
337 if (!isset($this->reflectedClassNames[$className])) {
338 $this->reflectClass($className);
339 }
340 if (!isset($this->propertyTagsValues[$className][$propertyName])) {
341 return array();
342 }
343 return isset($this->propertyTagsValues[$className][$propertyName][$tag]) ? $this->propertyTagsValues[$className][$propertyName][$tag] : array();
344 }
345
346 /**
347 * Tells if the specified class is known to this reflection service and
348 * reflection information is available.
349 *
350 * @param string $className Name of the class
351 * @return boolean If the class is reflected by this service
352 * @api
353 */
354 public function isClassReflected($className) {
355 return isset($this->reflectedClassNames[$className]);
356 }
357
358 /**
359 * Tells if the specified class is tagged with the given tag
360 *
361 * @param string $className Name of the class
362 * @param string $tag Tag to check for
363 * @return boolean TRUE if the class is tagged with $tag, otherwise FALSE
364 * @api
365 */
366 public function isClassTaggedWith($className, $tag) {
367 if ($this->initialized === FALSE) {
368 return FALSE;
369 }
370 if (!isset($this->reflectedClassNames[$className])) {
371 $this->reflectClass($className);
372 }
373 if (!isset($this->classTagsValues[$className])) {
374 return FALSE;
375 }
376 return isset($this->classTagsValues[$className][$tag]);
377 }
378
379 /**
380 * Tells if the specified class property is tagged with the given tag
381 *
382 * @param string $className Name of the class
383 * @param string $propertyName Name of the property
384 * @param string $tag Tag to check for
385 * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
386 * @api
387 */
388 public function isPropertyTaggedWith($className, $propertyName, $tag) {
389 if (!isset($this->reflectedClassNames[$className])) {
390 $this->reflectClass($className);
391 }
392 if (!isset($this->propertyTagsValues[$className])) {
393 return FALSE;
394 }
395 if (!isset($this->propertyTagsValues[$className][$propertyName])) {
396 return FALSE;
397 }
398 return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
399 }
400
401 /**
402 * Reflects the given class and stores the results in this service's properties.
403 *
404 * @param string $className Full qualified name of the class to reflect
405 * @return void
406 */
407 protected function reflectClass($className) {
408 $class = new \TYPO3\CMS\Extbase\Reflection\ClassReflection($className);
409 $this->reflectedClassNames[$className] = time();
410 foreach ($class->getTagsValues() as $tag => $values) {
411 if (array_search($tag, $this->ignoredTags) === FALSE) {
412 $this->taggedClasses[$tag][] = $className;
413 $this->classTagsValues[$className][$tag] = $values;
414 }
415 }
416 foreach ($class->getProperties() as $property) {
417 $propertyName = $property->getName();
418 $this->classPropertyNames[$className][] = $propertyName;
419 foreach ($property->getTagsValues() as $tag => $values) {
420 if (array_search($tag, $this->ignoredTags) === FALSE) {
421 $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
422 }
423 }
424 }
425 foreach ($class->getMethods() as $method) {
426 $methodName = $method->getName();
427 foreach ($method->getTagsValues() as $tag => $values) {
428 if (array_search($tag, $this->ignoredTags) === FALSE) {
429 $this->methodTagsValues[$className][$methodName][$tag] = $values;
430 }
431 }
432 foreach ($method->getParameters() as $parameterPosition => $parameter) {
433 $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
434 }
435 }
436 ksort($this->reflectedClassNames);
437 $this->dataCacheNeedsUpdate = TRUE;
438 }
439
440 /**
441 * Builds class schemata from classes annotated as entities or value objects
442 *
443 * @param string $className
444 * @throws Exception\UnknownClassException
445 * @return \TYPO3\CMS\Extbase\Reflection\ClassSchema The class schema
446 */
447 protected function buildClassSchema($className) {
448 if (!class_exists($className)) {
449 throw new \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
450 }
451 $classSchema = $this->objectManager->get('TYPO3\\CMS\\Extbase\\Reflection\\ClassSchema', $className);
452 if (is_subclass_of($className, 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity')) {
453 $classSchema->setModelType(\TYPO3\CMS\Extbase\Reflection\ClassSchema::MODELTYPE_ENTITY);
454 $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
455 if (class_exists($possibleRepositoryClassName)) {
456 $classSchema->setAggregateRoot(TRUE);
457 }
458 } elseif (is_subclass_of($className, 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractValueObject')) {
459 $classSchema->setModelType(\TYPO3\CMS\Extbase\Reflection\ClassSchema::MODELTYPE_VALUEOBJECT);
460 }
461 foreach ($this->getClassPropertyNames($className) as $propertyName) {
462 if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
463 $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
464 $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
465 }
466 if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
467 $classSchema->setUuidPropertyName($propertyName);
468 }
469 if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
470 $classSchema->markAsIdentityProperty($propertyName);
471 }
472 }
473 $this->classSchemata[$className] = $classSchema;
474 $this->dataCacheNeedsUpdate = TRUE;
475 return $classSchema;
476 }
477
478 /**
479 * Converts the given parameter reflection into an information array
480 *
481 * @param \ReflectionParameter $parameter The parameter to reflect
482 * @param integer $parameterPosition
483 * @param \ReflectionMethod|NULL $method
484 * @return array Parameter information array
485 */
486 protected function convertParameterReflectionToArray(\ReflectionParameter $parameter, $parameterPosition, \ReflectionMethod $method = NULL) {
487 $parameterInformation = array(
488 'position' => $parameterPosition,
489 'byReference' => $parameter->isPassedByReference() ? TRUE : FALSE,
490 'array' => $parameter->isArray() ? TRUE : FALSE,
491 'optional' => $parameter->isOptional() ? TRUE : FALSE,
492 'allowsNull' => $parameter->allowsNull() ? TRUE : FALSE
493 );
494 $parameterClass = $parameter->getClass();
495 $parameterInformation['class'] = $parameterClass !== NULL ? $parameterClass->getName() : NULL;
496 if ($parameter->isDefaultValueAvailable()) {
497 $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
498 }
499 if ($parameterClass !== NULL) {
500 $parameterInformation['type'] = $parameterClass->getName();
501 } elseif ($method !== NULL) {
502 $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
503 if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
504 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
505 if (count($explodedParameters) >= 2) {
506 $parameterInformation['type'] = $explodedParameters[0];
507 }
508 }
509 }
510 if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
511 $parameterInformation['type'] = substr($parameterInformation['type'], 1);
512 }
513 return $parameterInformation;
514 }
515
516 /**
517 * Returns the Reflection of a method.
518 *
519 * @param string $className Name of the class containing the method
520 * @param string $methodName Name of the method to return the Reflection for
521 * @return \TYPO3\CMS\Extbase\Reflection\MethodReflection the method Reflection object
522 */
523 protected function getMethodReflection($className, $methodName) {
524 if (!isset($this->methodReflections[$className][$methodName])) {
525 $this->methodReflections[$className][$methodName] = new \TYPO3\CMS\Extbase\Reflection\MethodReflection($className, $methodName);
526 $this->dataCacheNeedsUpdate = TRUE;
527 }
528 return $this->methodReflections[$className][$methodName];
529 }
530
531 /**
532 * Tries to load the reflection data from this service's cache.
533 *
534 * @return void
535 */
536 protected function loadFromCache() {
537 $data = $this->dataCache->get($this->cacheIdentifier);
538 if ($data !== FALSE) {
539 foreach ($data as $propertyName => $propertyValue) {
540 $this->{$propertyName} = $propertyValue;
541 }
542 }
543 }
544
545 /**
546 * Exports the internal reflection data into the ReflectionData cache.
547 *
548 * @throws Exception
549 * @return void
550 */
551 protected function saveToCache() {
552 if (!is_object($this->dataCache)) {
553 throw new \TYPO3\CMS\Extbase\Reflection\Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
554 }
555 $data = array();
556 $propertyNames = array(
557 'reflectedClassNames',
558 'classPropertyNames',
559 'classTagsValues',
560 'methodTagsValues',
561 'methodParameters',
562 'propertyTagsValues',
563 'taggedClasses',
564 'classSchemata'
565 );
566 foreach ($propertyNames as $propertyName) {
567 $data[$propertyName] = $this->{$propertyName};
568 }
569 $this->dataCache->set($this->cacheIdentifier, $data);
570 }
571 }
572
573 ?>