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