TYPO3 CMS  TYPO3_8-7
ReflectionService.php
Go to the documentation of this file.
1 <?php
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 = [];
59 
65  protected $taggedClasses = [];
66 
72  protected $classTagsValues = [];
73 
79  protected $methodTagsValues = [];
80 
87  protected $methodParameters = [];
88 
94  protected $classPropertyNames = [];
95 
101  protected $classMethodNames = [];
102 
108  protected $propertyTagsValues = [];
109 
115  protected $ignoredTags = ['package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const'];
116 
127  protected $dataCacheNeedsUpdate = false;
128 
134  protected $classSchemata = [];
135 
140 
144  protected $cacheIdentifier;
145 
151  protected $methodReflections = [];
152 
156  public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
157  {
158  $this->objectManager = $objectManager;
159  }
160 
164  public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
165  {
166  $this->configurationManager = $configurationManager;
167  }
168 
176  public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache)
177  {
178  $this->dataCache = $dataCache;
179  }
180 
186  public function initialize()
187  {
188  if ($this->initialized) {
189  throw new Exception('The Reflection Service can only be initialized once.', 1232044696);
190  }
191  $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
192  $this->cacheIdentifier = 'ReflectionData_' . $frameworkConfiguration['extensionName'];
193  $this->loadFromCache();
194  $this->initialized = true;
195  }
196 
202  public function isInitialized()
203  {
204  return $this->initialized;
205  }
206 
210  public function shutdown()
211  {
212  if ($this->dataCacheNeedsUpdate) {
213  $this->saveToCache();
214  }
215  $this->initialized = false;
216  }
217 
224  public function getClassTagsValues($className)
225  {
226  if (!isset($this->reflectedClassNames[$className])) {
227  $this->reflectClass($className);
228  }
229  if (!isset($this->classTagsValues[$className])) {
230  return [];
231  }
232  return isset($this->classTagsValues[$className]) ? $this->classTagsValues[$className] : [];
233  }
234 
242  public function getClassTagValues($className, $tag)
243  {
244  if (!isset($this->reflectedClassNames[$className])) {
245  $this->reflectClass($className);
246  }
247  if (!isset($this->classTagsValues[$className])) {
248  return [];
249  }
250  return isset($this->classTagsValues[$className][$tag]) ? $this->classTagsValues[$className][$tag] : [];
251  }
252 
259  public function getClassPropertyNames($className)
260  {
261  if (!isset($this->reflectedClassNames[$className])) {
262  $this->reflectClass($className);
263  }
264  return isset($this->classPropertyNames[$className]) ? $this->classPropertyNames[$className] : [];
265  }
266 
273  public function getClassSchema($classNameOrObject)
274  {
275  $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
276  if (isset($this->classSchemata[$className])) {
277  return $this->classSchemata[$className];
278  }
279  return $this->buildClassSchema($className);
280  }
281 
289  public function hasMethod($className, $methodName)
290  {
291  try {
292  if (!array_key_exists($className, $this->classMethodNames) || !array_key_exists($methodName, $this->classMethodNames[$className])) {
293  $this->getMethodReflection($className, $methodName);
294  $this->classMethodNames[$className][$methodName] = true;
295  }
296  } catch (\ReflectionException $e) {
297  // Method does not exist. Store this information in cache.
298  $this->classMethodNames[$className][$methodName] = null;
299  }
300  return isset($this->classMethodNames[$className][$methodName]);
301  }
302 
310  public function getMethodTagsValues($className, $methodName)
311  {
312  if (!isset($this->methodTagsValues[$className][$methodName])) {
313  $method = $this->getMethodReflection($className, $methodName);
314  $this->methodTagsValues[$className][$methodName] = [];
315  foreach ($method->getTagsValues() as $tag => $values) {
316  if (array_search($tag, $this->ignoredTags) === false) {
317  $this->methodTagsValues[$className][$methodName][$tag] = $values;
318  }
319  }
320  }
321  return $this->methodTagsValues[$className][$methodName];
322  }
323 
332  public function getMethodParameters($className, $methodName)
333  {
334  if (!isset($this->methodParameters[$className][$methodName])) {
335  $method = $this->getMethodReflection($className, $methodName);
336  $this->methodParameters[$className][$methodName] = [];
337  foreach ($method->getParameters() as $parameterPosition => $parameter) {
338  $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
339  }
340  }
341  return $this->methodParameters[$className][$methodName];
342  }
343 
351  public function getPropertyTagsValues($className, $propertyName)
352  {
353  if (!isset($this->reflectedClassNames[$className])) {
354  $this->reflectClass($className);
355  }
356  if (!isset($this->propertyTagsValues[$className])) {
357  return [];
358  }
359  return isset($this->propertyTagsValues[$className][$propertyName]) ? $this->propertyTagsValues[$className][$propertyName] : [];
360  }
361 
370  public function getPropertyTagValues($className, $propertyName, $tag)
371  {
372  if (!isset($this->reflectedClassNames[$className])) {
373  $this->reflectClass($className);
374  }
375  if (!isset($this->propertyTagsValues[$className][$propertyName])) {
376  return [];
377  }
378  return isset($this->propertyTagsValues[$className][$propertyName][$tag]) ? $this->propertyTagsValues[$className][$propertyName][$tag] : [];
379  }
380 
388  public function isClassReflected($className)
389  {
390  return isset($this->reflectedClassNames[$className]);
391  }
392 
400  public function isClassTaggedWith($className, $tag)
401  {
402  if ($this->initialized === false) {
403  return false;
404  }
405  if (!isset($this->reflectedClassNames[$className])) {
406  $this->reflectClass($className);
407  }
408  if (!isset($this->classTagsValues[$className])) {
409  return false;
410  }
411  return isset($this->classTagsValues[$className][$tag]);
412  }
413 
422  public function isPropertyTaggedWith($className, $propertyName, $tag)
423  {
424  if (!isset($this->reflectedClassNames[$className])) {
425  $this->reflectClass($className);
426  }
427  if (!isset($this->propertyTagsValues[$className])) {
428  return false;
429  }
430  if (!isset($this->propertyTagsValues[$className][$propertyName])) {
431  return false;
432  }
433  return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
434  }
435 
441  protected function reflectClass($className)
442  {
443  $class = new ClassReflection($className);
444  $this->reflectedClassNames[$className] = time();
445  foreach ($class->getTagsValues() as $tag => $values) {
446  if (array_search($tag, $this->ignoredTags) === false) {
447  $this->taggedClasses[$tag][] = $className;
448  $this->classTagsValues[$className][$tag] = $values;
449  }
450  }
451  foreach ($class->getProperties() as $property) {
452  $propertyName = $property->getName();
453  $this->classPropertyNames[$className][] = $propertyName;
454  foreach ($property->getTagsValues() as $tag => $values) {
455  if (array_search($tag, $this->ignoredTags) === false) {
456  $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
457  }
458  }
459  }
460  foreach ($class->getMethods() as $method) {
461  $methodName = $method->getName();
462  foreach ($method->getTagsValues() as $tag => $values) {
463  if (array_search($tag, $this->ignoredTags) === false) {
464  $this->methodTagsValues[$className][$methodName][$tag] = $values;
465  }
466  }
467  foreach ($method->getParameters() as $parameterPosition => $parameter) {
468  $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
469  }
470  }
471  ksort($this->reflectedClassNames);
472  $this->dataCacheNeedsUpdate = true;
473  }
474 
482  protected function buildClassSchema($className)
483  {
484  if (!class_exists($className)) {
485  throw new Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
486  }
487  $classSchema = $this->objectManager->get(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, $className);
488  if (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class)) {
489  $classSchema->setModelType(ClassSchema::MODELTYPE_ENTITY);
490  $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
491  if (class_exists($possibleRepositoryClassName)) {
492  $classSchema->setAggregateRoot(true);
493  }
494  } elseif (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject::class)) {
495  $classSchema->setModelType(ClassSchema::MODELTYPE_VALUEOBJECT);
496  }
497  foreach ($this->getClassPropertyNames($className) as $propertyName) {
498  if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
499  $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
500  $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
501  }
502  if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
503  $classSchema->setUuidPropertyName($propertyName);
504  }
505  if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
506  $classSchema->markAsIdentityProperty($propertyName);
507  }
508  }
509  $this->classSchemata[$className] = $classSchema;
510  $this->dataCacheNeedsUpdate = true;
511  return $classSchema;
512  }
513 
522  protected function convertParameterReflectionToArray(ParameterReflection $parameter, $parameterPosition, MethodReflection $method = null)
523  {
524  $parameterInformation = [
525  'position' => $parameterPosition,
526  'byReference' => $parameter->isPassedByReference(),
527  'array' => $parameter->isArray(),
528  'optional' => $parameter->isOptional(),
529  'allowsNull' => $parameter->allowsNull()
530  ];
531  $parameterClass = $parameter->getClass();
532  $parameterInformation['class'] = $parameterClass !== null ? $parameterClass->getName() : null;
533  if ($parameter->isDefaultValueAvailable()) {
534  $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
535  }
536  if ($parameterClass !== null) {
537  $parameterInformation['type'] = $parameterClass->getName();
538  } elseif ($method !== null) {
539  $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
540  if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
541  $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
542  if (count($explodedParameters) >= 2) {
543  if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
544  // ensure that short names of simple types are resolved correctly to the long form
545  // this is important for all kinds of type checks later on
546  $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
547  $parameterInformation['type'] = $typeInfo['type'];
548  } else {
549  $parameterInformation['type'] = $explodedParameters[0];
550  }
551  }
552  }
553  }
554  if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
555  $parameterInformation['type'] = substr($parameterInformation['type'], 1);
556  }
557  return $parameterInformation;
558  }
559 
567  protected function getMethodReflection($className, $methodName)
568  {
569  $this->dataCacheNeedsUpdate = true;
570  if (!isset($this->methodReflections[$className][$methodName])) {
571  $this->methodReflections[$className][$methodName] = new MethodReflection($className, $methodName);
572  }
573  return $this->methodReflections[$className][$methodName];
574  }
575 
579  protected function loadFromCache()
580  {
581  $data = $this->dataCache->get($this->cacheIdentifier);
582  if ($data !== false) {
583  foreach ($data as $propertyName => $propertyValue) {
584  $this->{$propertyName} = $propertyValue;
585  }
586  }
587  }
588 
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 = [];
600  $propertyNames = [
601  'reflectedClassNames',
602  'classPropertyNames',
603  'classMethodNames',
604  'classTagsValues',
605  'methodTagsValues',
606  'methodParameters',
607  'propertyTagsValues',
608  'taggedClasses',
609  'classSchemata'
610  ];
611  foreach ($propertyNames as $propertyName) {
612  $data[$propertyName] = $this->{$propertyName};
613  }
614  $this->dataCache->set($this->cacheIdentifier, $data);
615  $this->dataCacheNeedsUpdate = false;
616  }
617 }
getPropertyTagValues($className, $propertyName, $tag)
setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache)
isPropertyTaggedWith($className, $propertyName, $tag)
injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
convertParameterReflectionToArray(ParameterReflection $parameter, $parameterPosition, MethodReflection $method=null)