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