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