TYPO3  7.6
ReflectionService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Extbase\Reflection;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 
27 {
31  protected $objectManager;
32 
38  protected $initialized = false;
39 
43  protected $dataCache;
44 
50  protected $detectClassChanges = false;
51 
58  protected $reflectedClassNames = array();
59 
65  protected $taggedClasses = array();
66 
72  protected $classTagsValues = array();
73 
79  protected $methodTagsValues = array();
80 
87  protected $methodParameters = array();
88 
94  protected $classPropertyNames = array();
95 
101  protected $propertyTagsValues = array();
102 
108  protected $ignoredTags = array('package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const');
109 
120  protected $dataCacheNeedsUpdate = false;
121 
127  protected $classSchemata = array();
128 
133 
137  protected $cacheIdentifier;
138 
142  protected $methodReflections = array();
143 
147  public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
148  {
149  $this->objectManager = $objectManager;
150  }
151 
155  public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
156  {
157  $this->configurationManager = $configurationManager;
158  }
159 
168  public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache)
169  {
170  $this->dataCache = $dataCache;
171  }
172 
179  public function initialize()
180  {
181  if ($this->initialized) {
182  throw new Exception('The Reflection Service can only be initialized once.', 1232044696);
183  }
184  $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
185  $this->cacheIdentifier = 'ReflectionData_' . $frameworkConfiguration['extensionName'];
186  $this->loadFromCache();
187  $this->initialized = true;
188  }
189 
195  public function isInitialized()
196  {
197  return $this->initialized;
198  }
199 
205  public function shutdown()
206  {
207  if ($this->dataCacheNeedsUpdate) {
208  $this->saveToCache();
209  }
210  $this->initialized = false;
211  }
212 
219  public function getClassTagsValues($className)
220  {
221  if (!isset($this->reflectedClassNames[$className])) {
222  $this->reflectClass($className);
223  }
224  if (!isset($this->classTagsValues[$className])) {
225  return array();
226  }
227  return isset($this->classTagsValues[$className]) ? $this->classTagsValues[$className] : array();
228  }
229 
237  public function getClassTagValues($className, $tag)
238  {
239  if (!isset($this->reflectedClassNames[$className])) {
240  $this->reflectClass($className);
241  }
242  if (!isset($this->classTagsValues[$className])) {
243  return array();
244  }
245  return isset($this->classTagsValues[$className][$tag]) ? $this->classTagsValues[$className][$tag] : array();
246  }
247 
254  public function getClassPropertyNames($className)
255  {
256  if (!isset($this->reflectedClassNames[$className])) {
257  $this->reflectClass($className);
258  }
259  return isset($this->classPropertyNames[$className]) ? $this->classPropertyNames[$className] : array();
260  }
261 
268  public function getClassSchema($classNameOrObject)
269  {
270  $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
271  if (isset($this->classSchemata[$className])) {
272  return $this->classSchemata[$className];
273  } else {
274  return $this->buildClassSchema($className);
275  }
276  }
277 
285  public function hasMethod($className, $methodName)
286  {
287  try {
288  if (!array_key_exists($className, $this->methodReflections) || !array_key_exists($methodName, $this->methodReflections[$className])) {
289  $this->methodReflections[$className][$methodName] = new MethodReflection($className, $methodName);
290  $this->dataCacheNeedsUpdate = true;
291  }
292  } catch (\ReflectionException $e) {
293  // Method does not exist. Store this information in cache.
294  $this->methodReflections[$className][$methodName] = null;
295  }
296  return isset($this->methodReflections[$className][$methodName]);
297  }
298 
306  public function getMethodTagsValues($className, $methodName)
307  {
308  if (!isset($this->methodTagsValues[$className][$methodName])) {
309  $this->methodTagsValues[$className][$methodName] = array();
310  $method = $this->getMethodReflection($className, $methodName);
311  foreach ($method->getTagsValues() as $tag => $values) {
312  if (array_search($tag, $this->ignoredTags) === false) {
313  $this->methodTagsValues[$className][$methodName][$tag] = $values;
314  }
315  }
316  }
317  return $this->methodTagsValues[$className][$methodName];
318  }
319 
328  public function getMethodParameters($className, $methodName)
329  {
330  if (!isset($this->methodParameters[$className][$methodName])) {
331  $method = $this->getMethodReflection($className, $methodName);
332  $this->methodParameters[$className][$methodName] = array();
333  foreach ($method->getParameters() as $parameterPosition => $parameter) {
334  $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
335  }
336  }
337  return $this->methodParameters[$className][$methodName];
338  }
339 
347  public function getPropertyTagsValues($className, $propertyName)
348  {
349  if (!isset($this->reflectedClassNames[$className])) {
350  $this->reflectClass($className);
351  }
352  if (!isset($this->propertyTagsValues[$className])) {
353  return array();
354  }
355  return isset($this->propertyTagsValues[$className][$propertyName]) ? $this->propertyTagsValues[$className][$propertyName] : array();
356  }
357 
366  public function getPropertyTagValues($className, $propertyName, $tag)
367  {
368  if (!isset($this->reflectedClassNames[$className])) {
369  $this->reflectClass($className);
370  }
371  if (!isset($this->propertyTagsValues[$className][$propertyName])) {
372  return array();
373  }
374  return isset($this->propertyTagsValues[$className][$propertyName][$tag]) ? $this->propertyTagsValues[$className][$propertyName][$tag] : array();
375  }
376 
384  public function isClassReflected($className)
385  {
386  return isset($this->reflectedClassNames[$className]);
387  }
388 
396  public function isClassTaggedWith($className, $tag)
397  {
398  if ($this->initialized === false) {
399  return false;
400  }
401  if (!isset($this->reflectedClassNames[$className])) {
402  $this->reflectClass($className);
403  }
404  if (!isset($this->classTagsValues[$className])) {
405  return false;
406  }
407  return isset($this->classTagsValues[$className][$tag]);
408  }
409 
418  public function isPropertyTaggedWith($className, $propertyName, $tag)
419  {
420  if (!isset($this->reflectedClassNames[$className])) {
421  $this->reflectClass($className);
422  }
423  if (!isset($this->propertyTagsValues[$className])) {
424  return false;
425  }
426  if (!isset($this->propertyTagsValues[$className][$propertyName])) {
427  return false;
428  }
429  return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
430  }
431 
438  protected function reflectClass($className)
439  {
440  $class = new ClassReflection($className);
441  $this->reflectedClassNames[$className] = time();
442  foreach ($class->getTagsValues() as $tag => $values) {
443  if (array_search($tag, $this->ignoredTags) === false) {
444  $this->taggedClasses[$tag][] = $className;
445  $this->classTagsValues[$className][$tag] = $values;
446  }
447  }
448  foreach ($class->getProperties() as $property) {
449  $propertyName = $property->getName();
450  $this->classPropertyNames[$className][] = $propertyName;
451  foreach ($property->getTagsValues() as $tag => $values) {
452  if (array_search($tag, $this->ignoredTags) === false) {
453  $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
454  }
455  }
456  }
457  foreach ($class->getMethods() as $method) {
458  $methodName = $method->getName();
459  foreach ($method->getTagsValues() as $tag => $values) {
460  if (array_search($tag, $this->ignoredTags) === false) {
461  $this->methodTagsValues[$className][$methodName][$tag] = $values;
462  }
463  }
464  foreach ($method->getParameters() as $parameterPosition => $parameter) {
465  $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
466  }
467  }
468  ksort($this->reflectedClassNames);
469  $this->dataCacheNeedsUpdate = true;
470  }
471 
479  protected function buildClassSchema($className)
480  {
481  if (!class_exists($className)) {
482  throw new Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
483  }
484  $classSchema = $this->objectManager->get(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, $className);
485  if (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class)) {
486  $classSchema->setModelType(ClassSchema::MODELTYPE_ENTITY);
487  $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
488  if (class_exists($possibleRepositoryClassName)) {
489  $classSchema->setAggregateRoot(true);
490  }
491  } elseif (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject::class)) {
492  $classSchema->setModelType(ClassSchema::MODELTYPE_VALUEOBJECT);
493  }
494  foreach ($this->getClassPropertyNames($className) as $propertyName) {
495  if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
496  $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
497  $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
498  }
499  if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
500  $classSchema->setUuidPropertyName($propertyName);
501  }
502  if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
503  $classSchema->markAsIdentityProperty($propertyName);
504  }
505  }
506  $this->classSchemata[$className] = $classSchema;
507  $this->dataCacheNeedsUpdate = true;
508  return $classSchema;
509  }
510 
519  protected function convertParameterReflectionToArray(ParameterReflection $parameter, $parameterPosition, MethodReflection $method = null)
520  {
521  $parameterInformation = array(
522  'position' => $parameterPosition,
523  'byReference' => $parameter->isPassedByReference(),
524  'array' => $parameter->isArray(),
525  'optional' => $parameter->isOptional(),
526  'allowsNull' => $parameter->allowsNull()
527  );
528  $parameterClass = $parameter->getClass();
529  $parameterInformation['class'] = $parameterClass !== null ? $parameterClass->getName() : null;
530  if ($parameter->isDefaultValueAvailable()) {
531  $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
532  }
533  if ($parameterClass !== null) {
534  $parameterInformation['type'] = $parameterClass->getName();
535  } elseif ($method !== null) {
536  $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
537  if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
538  $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
539  if (count($explodedParameters) >= 2) {
540  if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
541  // ensure that short names of simple types are resolved correctly to the long form
542  // this is important for all kinds of type checks later on
543  $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
544  $parameterInformation['type'] = $typeInfo['type'];
545  } else {
546  $parameterInformation['type'] = $explodedParameters[0];
547  }
548  }
549  }
550  }
551  if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
552  $parameterInformation['type'] = substr($parameterInformation['type'], 1);
553  }
554  return $parameterInformation;
555  }
556 
564  protected function getMethodReflection($className, $methodName)
565  {
566  if (!isset($this->methodReflections[$className][$methodName])) {
567  $this->methodReflections[$className][$methodName] = new MethodReflection($className, $methodName);
568  $this->dataCacheNeedsUpdate = true;
569  }
570  return $this->methodReflections[$className][$methodName];
571  }
572 
578  protected function loadFromCache()
579  {
580  $data = $this->dataCache->get($this->cacheIdentifier);
581  if ($data !== false) {
582  foreach ($data as $propertyName => $propertyValue) {
583  $this->{$propertyName} = $propertyValue;
584  }
585  }
586  }
587 
594  protected function saveToCache()
595  {
596  if (!is_object($this->dataCache)) {
597  throw new Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
598  }
599  $data = array();
600  $propertyNames = array(
601  'reflectedClassNames',
602  'classPropertyNames',
603  'classTagsValues',
604  'methodTagsValues',
605  'methodParameters',
606  'propertyTagsValues',
607  'taggedClasses',
608  'classSchemata'
609  );
610  foreach ($propertyNames as $propertyName) {
611  $data[$propertyName] = $this->{$propertyName};
612  }
613  $this->dataCache->set($this->cacheIdentifier, $data);
614  }
615 }