Raised DBAL version from 1.1.5 to 1.1.6
[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: Service.php 1789 2010-01-18 21:31:59Z jocrau $
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 $cache;
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 $cacheNeedsUpdate = FALSE;
125
126 /**
127 * Local cache for Class schemata
128 * @var array
129 */
130 protected $classSchemata = array();
131
132 /**
133 * Sets the cache.
134 *
135 * The cache must be set before initializing the Reflection Service.
136 *
137 * @param t3lib_cache_frontend_VariableFrontend $cache Cache for the Reflection service
138 * @return void
139 */
140 public function setCache(t3lib_cache_frontend_VariableFrontend $cache) {
141 $this->cache = $cache;
142 }
143
144 /**
145 * Initializes this service
146 *
147 * @param array $classNamesToReflect Names of available classes to consider in this reflection service
148 * @return void
149 */
150 public function initialize() {
151 if ($this->initialized) throw new Tx_Extbase_Reflection_Exception('The Reflection Service can only be initialized once.', 1232044696);
152
153 $this->loadFromCache();
154
155 $this->initialized = TRUE;
156 }
157
158 /**
159 * Returns whether the Reflection Service is initialized.
160 *
161 * @return boolean true if the Reflection Service is initialized, otherwise false
162 */
163 public function isInitialized() {
164 return $this->initialized;
165 }
166
167 /**
168 * Shuts the Reflection Service down.
169 *
170 * @return void
171 */
172 public function shutdown() {
173 if ($this->cacheNeedsUpdate) {
174 $this->saveToCache();
175 }
176 }
177
178 /**
179 * Returns the names of all properties of the specified class
180 *
181 * @param string $className Name of the class to return the property names of
182 * @return array An array of property names or an empty array if none exist
183 */
184 public function getClassPropertyNames($className) {
185 if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
186 return (isset($this->classPropertyNames[$className])) ? $this->classPropertyNames[$className] : array();
187 }
188
189 /**
190 * Returns the class schema for the given class
191 *
192 * @param mixed $classNameOrObject The class name or an object
193 * @return Tx_Extbase_Reflection_ClassSchema
194 * @author Karsten Dambekalns <karsten@typo3.org>
195 */
196 public function getClassSchema($classNameOrObject) {
197 $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
198 if (isset($this->classSchemata[$className])) {
199 return $this->classSchemata[$className];
200 } else {
201 return $this->buildClassSchema($className);
202 }
203
204 }
205
206 /**
207 * Returns all tags and their values the specified method is tagged with
208 *
209 * @param string $className Name of the class containing the method
210 * @param string $methodName Name of the method to return the tags and values of
211 * @return array An array of tags and their values or an empty array of no tags were found
212 */
213 public function getMethodTagsValues($className, $methodName) {
214 if (!isset($this->methodTagsValues[$className][$methodName])) {
215 $this->methodTagsValues[$className][$methodName] = array();
216 $method = $this->getMethodReflection($className, $methodName);
217 foreach ($method->getTagsValues() as $tag => $values) {
218 if (array_search($tag, $this->ignoredTags) === FALSE) {
219 $this->methodTagsValues[$className][$methodName][$tag] = $values;
220 }
221 }
222 }
223 return $this->methodTagsValues[$className][$methodName];
224 }
225
226
227 /**
228 * Returns an array of parameters of the given method. Each entry contains
229 * additional information about the parameter position, type hint etc.
230 *
231 * @param string $className Name of the class containing the method
232 * @param string $methodName Name of the method to return parameter information of
233 * @return array An array of parameter names and additional information or an empty array of no parameters were found
234 */
235 public function getMethodParameters($className, $methodName) {
236 if (!isset($this->methodParameters[$className][$methodName])) {
237 $method = $this->getMethodReflection($className, $methodName);
238 $this->methodParameters[$className][$methodName] = array();
239 foreach($method->getParameters() as $parameterPosition => $parameter) {
240 $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
241 }
242 }
243 return $this->methodParameters[$className][$methodName];
244 }
245
246 /**
247 * Returns all tags and their values the specified class property is tagged with
248 *
249 * @param string $className Name of the class containing the property
250 * @param string $propertyName Name of the property to return the tags and values of
251 * @return array An array of tags and their values or an empty array of no tags were found
252 */
253 public function getPropertyTagsValues($className, $propertyName) {
254 if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
255 if (!isset($this->propertyTagsValues[$className])) return array();
256 return (isset($this->propertyTagsValues[$className][$propertyName])) ? $this->propertyTagsValues[$className][$propertyName] : array();
257 }
258
259 /**
260 * Returns the values of the specified class property tag
261 *
262 * @param string $className Name of the class containing the property
263 * @param string $propertyName Name of the tagged property
264 * @param string $tag Tag to return the values of
265 * @return array An array of values or an empty array if the tag was not found
266 * @author Robert Lemke <robert@typo3.org>
267 * @api
268 */
269 public function getPropertyTagValues($className, $propertyName, $tag) {
270 if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
271 if (!isset($this->propertyTagsValues[$className][$propertyName])) return array();
272 return (isset($this->propertyTagsValues[$className][$propertyName][$tag])) ? $this->propertyTagsValues[$className][$propertyName][$tag] : array();
273 }
274
275 /**
276 * Tells if the specified class is known to this reflection service and
277 * reflection information is available.
278 *
279 * @param string $className Name of the class
280 * @return boolean If the class is reflected by this service
281 * @author Robert Lemke <robert@typo3.org>
282 * @api
283 */
284 public function isClassReflected($className) {
285 return isset($this->reflectedClassNames[$className]);
286 }
287
288 /**
289 * Tells if the specified class is tagged with the given tag
290 *
291 * @param string $className Name of the class
292 * @param string $tag Tag to check for
293 * @return boolean TRUE if the class is tagged with $tag, otherwise FALSE
294 * @author Robert Lemke <robert@typo3.org>
295 * @api
296 */
297 public function isClassTaggedWith($className, $tag) {
298 if ($this->initialized === FALSE) return FALSE;
299 if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
300 if (!isset($this->classTagsValues[$className])) return FALSE;
301 return isset($this->classTagsValues[$className][$tag]);
302 }
303
304 /**
305 * Tells if the specified class property is tagged with the given tag
306 *
307 * @param string $className Name of the class
308 * @param string $propertyName Name of the property
309 * @param string $tag Tag to check for
310 * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
311 * @author Robert Lemke <robert@typo3.org>
312 * @api
313 */
314 public function isPropertyTaggedWith($className, $propertyName, $tag) {
315 if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
316 if (!isset($this->propertyTagsValues[$className])) return FALSE;
317 if (!isset($this->propertyTagsValues[$className][$propertyName])) return FALSE;
318 return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
319 }
320
321 /**
322 * Reflects the given class and stores the results in this service's properties.
323 *
324 * @param string $className Full qualified name of the class to reflect
325 * @return void
326 */
327 protected function reflectClass($className) {
328 $class = new Tx_Extbase_Reflection_ClassReflection($className);
329 $this->reflectedClassNames[$className] = time();
330
331 foreach ($class->getTagsValues() as $tag => $values) {
332 if (array_search($tag, $this->ignoredTags) === FALSE) {
333 $this->taggedClasses[$tag][] = $className;
334 $this->classTagsValues[$className][$tag] = $values;
335 }
336 }
337
338 foreach ($class->getProperties() as $property) {
339 $propertyName = $property->getName();
340 $this->classPropertyNames[$className][] = $propertyName;
341
342 foreach ($property->getTagsValues() as $tag => $values) {
343 if (array_search($tag, $this->ignoredTags) === FALSE) {
344 $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
345 }
346 }
347 }
348
349 foreach ($class->getMethods() as $method) {
350 $methodName = $method->getName();
351 foreach ($method->getTagsValues() as $tag => $values) {
352 if (array_search($tag, $this->ignoredTags) === FALSE) {
353 $this->methodTagsValues[$className][$methodName][$tag] = $values;
354 }
355 }
356
357 foreach ($method->getParameters() as $parameterPosition => $parameter) {
358 $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
359 }
360 }
361 ksort($this->reflectedClassNames);
362
363 $this->cacheNeedsUpdate = TRUE;
364 }
365
366 /**
367 * Builds class schemata from classes annotated as entities or value objects
368 *
369 * @return Tx_Extbase_Reflection_ClassSchema The class schema
370 */
371 protected function buildClassSchema($className) {
372 if (!class_exists($className)) {
373 return NULL;
374 }
375 $classSchema = new Tx_Extbase_Reflection_ClassSchema($className);
376 if (is_subclass_of($className, 'Tx_Extbase_DomainObject_AbstractEntity')) {
377 $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema::MODELTYPE_ENTITY);
378
379 $possibleRepositoryClassName = str_replace('_Model_', '_Repository_', $className) . 'Repository';
380 if (class_exists($possibleRepositoryClassName)) {
381 $classSchema->setAggregateRoot(TRUE);
382 }
383 } elseif (is_subclass_of($className, 'Tx_Extbase_DomainObject_AbstractValueObject')) {
384 $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema::MODELTYPE_VALUEOBJECT);
385 } else {
386 return NULL;
387 }
388
389 foreach ($this->getClassPropertyNames($className) as $propertyName) {
390 if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
391 $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
392 $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
393 }
394 if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
395 $classSchema->setUUIDPropertyName($propertyName);
396 }
397 if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
398 $classSchema->markAsIdentityProperty($propertyName);
399 }
400 }
401 $this->classSchemata[$className] = $classSchema;
402 $this->cacheNeedsUpdate = TRUE;
403 return $classSchema;
404 }
405
406 /**
407 * Converts the given parameter reflection into an information array
408 *
409 * @param ReflectionParameter $parameter The parameter to reflect
410 * @return array Parameter information array
411 */
412 protected function convertParameterReflectionToArray(ReflectionParameter $parameter, $parameterPosition, ReflectionMethod $method = NULL) {
413 $parameterInformation = array(
414 'position' => $parameterPosition,
415 'byReference' => $parameter->isPassedByReference() ? TRUE : FALSE,
416 'array' => $parameter->isArray() ? TRUE : FALSE,
417 'optional' => $parameter->isOptional() ? TRUE : FALSE,
418 'allowsNull' => $parameter->allowsNull() ? TRUE : FALSE
419 );
420
421 $parameterClass = $parameter->getClass();
422 $parameterInformation['class'] = ($parameterClass !== NULL) ? $parameterClass->getName() : NULL;
423 if ($parameter->isDefaultValueAvailable()) {
424 $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
425 }
426 if ($parameterClass !== NULL) {
427 $parameterInformation['type'] = $parameterClass->getName();
428 } elseif ($method !== NULL) {
429 $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
430 if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
431 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
432 if (count($explodedParameters) >= 2) {
433 $parameterInformation['type'] = $explodedParameters[0];
434 }
435 }
436 }
437 if (isset($parameterInformation['type']) && $parameterInformation['type']{0} === '\\') {
438 $parameterInformation['type'] = substr($parameterInformation['type'], 1);
439 }
440 return $parameterInformation;
441 }
442
443 /**
444 * Returns the Reflection of a method.
445 *
446 * @param string $className Name of the class containing the method
447 * @param string $methodName Name of the method to return the Reflection for
448 * @return Tx_Extbase_Reflection_MethodReflection the method Reflection object
449 */
450 protected function getMethodReflection($className, $methodName) {
451 if (!isset($this->methodReflections[$className][$methodName])) {
452 $this->methodReflections[$className][$methodName] = new Tx_Extbase_Reflection_MethodReflection($className, $methodName);
453 $this->cacheNeedsUpdate = TRUE;
454 }
455 return $this->methodReflections[$className][$methodName];
456 }
457
458 /**
459 * Tries to load the reflection data from this service's cache.
460 *
461 * @return void
462 */
463 protected function loadFromCache() {
464 $cacheKey = $this->getCacheKey();
465 if ($this->cache->has($cacheKey)) {
466 $data = $this->cache->get($cacheKey);
467 foreach ($data as $propertyName => $propertyValue) {
468 $this->$propertyName = $propertyValue;
469 }
470 }
471 }
472
473 /**
474 * Exports the internal reflection data into the ReflectionData cache.
475 *
476 * @return void
477 */
478 protected function saveToCache() {
479 if (!is_object($this->cache)) {
480 throw new Tx_Extbase_Reflection_Exception(
481 'A cache must be injected before initializing the Reflection Service.',
482 1232044697
483 );
484 }
485
486 $data = array();
487 $propertyNames = array(
488 'reflectedClassNames',
489 'classPropertyNames',
490 'classTagsValues',
491 'methodTagsValues',
492 'methodParameters',
493 'propertyTagsValues',
494 'taggedClasses',
495 'classSchemata'
496 );
497 foreach ($propertyNames as $propertyName) {
498 $data[$propertyName] = $this->$propertyName;
499 }
500 $this->cache->set($this->getCacheKey(), $data);
501 }
502
503 /**
504 * Get the name of the cache row identifier. Incorporates the extension name
505 * and the plugin name so that all information which is needed for a single
506 * plugin can be found in one cache row.
507 *
508 * @return string
509 */
510 protected function getCacheKey() {
511 $frameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
512 return $frameworkConfiguration['extensionName'] . '_' . $frameworkConfiguration['pluginName'];
513 }
514 }
515 ?>