TYPO3 CMS  TYPO3_7-6
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 
177  public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache)
178  {
179  $this->dataCache = $dataCache;
180  }
181 
188  public function initialize()
189  {
190  if ($this->initialized) {
191  throw new Exception('The Reflection Service can only be initialized once.', 1232044696);
192  }
193  $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
194  $this->cacheIdentifier = 'ReflectionData_' . $frameworkConfiguration['extensionName'];
195  $this->loadFromCache();
196  $this->initialized = true;
197  }
198 
204  public function isInitialized()
205  {
206  return $this->initialized;
207  }
208 
214  public function shutdown()
215  {
216  if ($this->dataCacheNeedsUpdate) {
217  $this->saveToCache();
218  }
219  $this->initialized = false;
220  }
221 
228  public function getClassTagsValues($className)
229  {
230  if (!isset($this->reflectedClassNames[$className])) {
231  $this->reflectClass($className);
232  }
233  if (!isset($this->classTagsValues[$className])) {
234  return [];
235  }
236  return isset($this->classTagsValues[$className]) ? $this->classTagsValues[$className] : [];
237  }
238 
246  public function getClassTagValues($className, $tag)
247  {
248  if (!isset($this->reflectedClassNames[$className])) {
249  $this->reflectClass($className);
250  }
251  if (!isset($this->classTagsValues[$className])) {
252  return [];
253  }
254  return isset($this->classTagsValues[$className][$tag]) ? $this->classTagsValues[$className][$tag] : [];
255  }
256 
263  public function getClassPropertyNames($className)
264  {
265  if (!isset($this->reflectedClassNames[$className])) {
266  $this->reflectClass($className);
267  }
268  return isset($this->classPropertyNames[$className]) ? $this->classPropertyNames[$className] : [];
269  }
270 
277  public function getClassSchema($classNameOrObject)
278  {
279  $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
280  if (isset($this->classSchemata[$className])) {
281  return $this->classSchemata[$className];
282  } else {
283  return $this->buildClassSchema($className);
284  }
285  }
286 
294  public function hasMethod($className, $methodName)
295  {
296  try {
297  if (!array_key_exists($className, $this->classMethodNames) || !array_key_exists($methodName, $this->classMethodNames[$className])) {
298  $this->getMethodReflection($className, $methodName);
299  $this->classMethodNames[$className][$methodName] = true;
300  }
301  } catch (\ReflectionException $e) {
302  // Method does not exist. Store this information in cache.
303  $this->classMethodNames[$className][$methodName] = null;
304  }
305  return isset($this->classMethodNames[$className][$methodName]);
306  }
307 
315  public function getMethodTagsValues($className, $methodName)
316  {
317  if (!isset($this->methodTagsValues[$className][$methodName])) {
318  $method = $this->getMethodReflection($className, $methodName);
319  $this->methodTagsValues[$className][$methodName] = [];
320  foreach ($method->getTagsValues() as $tag => $values) {
321  if (array_search($tag, $this->ignoredTags) === false) {
322  $this->methodTagsValues[$className][$methodName][$tag] = $values;
323  }
324  }
325  }
326  return $this->methodTagsValues[$className][$methodName];
327  }
328 
337  public function getMethodParameters($className, $methodName)
338  {
339  if (!isset($this->methodParameters[$className][$methodName])) {
340  $method = $this->getMethodReflection($className, $methodName);
341  $this->methodParameters[$className][$methodName] = [];
342  foreach ($method->getParameters() as $parameterPosition => $parameter) {
343  $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
344  }
345  }
346  return $this->methodParameters[$className][$methodName];
347  }
348 
356  public function getPropertyTagsValues($className, $propertyName)
357  {
358  if (!isset($this->reflectedClassNames[$className])) {
359  $this->reflectClass($className);
360  }
361  if (!isset($this->propertyTagsValues[$className])) {
362  return [];
363  }
364  return isset($this->propertyTagsValues[$className][$propertyName]) ? $this->propertyTagsValues[$className][$propertyName] : [];
365  }
366 
375  public function getPropertyTagValues($className, $propertyName, $tag)
376  {
377  if (!isset($this->reflectedClassNames[$className])) {
378  $this->reflectClass($className);
379  }
380  if (!isset($this->propertyTagsValues[$className][$propertyName])) {
381  return [];
382  }
383  return isset($this->propertyTagsValues[$className][$propertyName][$tag]) ? $this->propertyTagsValues[$className][$propertyName][$tag] : [];
384  }
385 
393  public function isClassReflected($className)
394  {
395  return isset($this->reflectedClassNames[$className]);
396  }
397 
405  public function isClassTaggedWith($className, $tag)
406  {
407  if ($this->initialized === false) {
408  return false;
409  }
410  if (!isset($this->reflectedClassNames[$className])) {
411  $this->reflectClass($className);
412  }
413  if (!isset($this->classTagsValues[$className])) {
414  return false;
415  }
416  return isset($this->classTagsValues[$className][$tag]);
417  }
418 
427  public function isPropertyTaggedWith($className, $propertyName, $tag)
428  {
429  if (!isset($this->reflectedClassNames[$className])) {
430  $this->reflectClass($className);
431  }
432  if (!isset($this->propertyTagsValues[$className])) {
433  return false;
434  }
435  if (!isset($this->propertyTagsValues[$className][$propertyName])) {
436  return false;
437  }
438  return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
439  }
440 
447  protected function reflectClass($className)
448  {
449  $class = new ClassReflection($className);
450  $this->reflectedClassNames[$className] = time();
451  foreach ($class->getTagsValues() as $tag => $values) {
452  if (array_search($tag, $this->ignoredTags) === false) {
453  $this->taggedClasses[$tag][] = $className;
454  $this->classTagsValues[$className][$tag] = $values;
455  }
456  }
457  foreach ($class->getProperties() as $property) {
458  $propertyName = $property->getName();
459  $this->classPropertyNames[$className][] = $propertyName;
460  foreach ($property->getTagsValues() as $tag => $values) {
461  if (array_search($tag, $this->ignoredTags) === false) {
462  $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
463  }
464  }
465  }
466  foreach ($class->getMethods() as $method) {
467  $methodName = $method->getName();
468  foreach ($method->getTagsValues() as $tag => $values) {
469  if (array_search($tag, $this->ignoredTags) === false) {
470  $this->methodTagsValues[$className][$methodName][$tag] = $values;
471  }
472  }
473  foreach ($method->getParameters() as $parameterPosition => $parameter) {
474  $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
475  }
476  }
477  ksort($this->reflectedClassNames);
478  $this->dataCacheNeedsUpdate = true;
479  }
480 
488  protected function buildClassSchema($className)
489  {
490  if (!class_exists($className)) {
491  throw new Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
492  }
493  $classSchema = $this->objectManager->get(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, $className);
494  if (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class)) {
495  $classSchema->setModelType(ClassSchema::MODELTYPE_ENTITY);
496  $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
497  if (class_exists($possibleRepositoryClassName)) {
498  $classSchema->setAggregateRoot(true);
499  }
500  } elseif (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject::class)) {
501  $classSchema->setModelType(ClassSchema::MODELTYPE_VALUEOBJECT);
502  }
503  foreach ($this->getClassPropertyNames($className) as $propertyName) {
504  if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
505  $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
506  $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
507  }
508  if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
509  $classSchema->setUuidPropertyName($propertyName);
510  }
511  if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
512  $classSchema->markAsIdentityProperty($propertyName);
513  }
514  }
515  $this->classSchemata[$className] = $classSchema;
516  $this->dataCacheNeedsUpdate = true;
517  return $classSchema;
518  }
519 
528  protected function convertParameterReflectionToArray(ParameterReflection $parameter, $parameterPosition, MethodReflection $method = null)
529  {
530  $parameterInformation = [
531  'position' => $parameterPosition,
532  'byReference' => $parameter->isPassedByReference(),
533  'array' => $parameter->isArray(),
534  'optional' => $parameter->isOptional(),
535  'allowsNull' => $parameter->allowsNull()
536  ];
537  $parameterClass = $parameter->getClass();
538  $parameterInformation['class'] = $parameterClass !== null ? $parameterClass->getName() : null;
539  if ($parameter->isDefaultValueAvailable()) {
540  $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
541  }
542  if ($parameterClass !== null) {
543  $parameterInformation['type'] = $parameterClass->getName();
544  } elseif ($method !== null) {
545  $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
546  if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
547  $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
548  if (count($explodedParameters) >= 2) {
549  if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
550  // ensure that short names of simple types are resolved correctly to the long form
551  // this is important for all kinds of type checks later on
552  $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
553  $parameterInformation['type'] = $typeInfo['type'];
554  } else {
555  $parameterInformation['type'] = $explodedParameters[0];
556  }
557  }
558  }
559  }
560  if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
561  $parameterInformation['type'] = substr($parameterInformation['type'], 1);
562  }
563  return $parameterInformation;
564  }
565 
573  protected function getMethodReflection($className, $methodName)
574  {
575  $this->dataCacheNeedsUpdate = true;
576  if (!isset($this->methodReflections[$className][$methodName])) {
577  $this->methodReflections[$className][$methodName] = new MethodReflection($className, $methodName);
578  }
579  return $this->methodReflections[$className][$methodName];
580  }
581 
587  protected function loadFromCache()
588  {
589  $data = $this->dataCache->get($this->cacheIdentifier);
590  if ($data !== false) {
591  foreach ($data as $propertyName => $propertyValue) {
592  $this->{$propertyName} = $propertyValue;
593  }
594  }
595  }
596 
603  protected function saveToCache()
604  {
605  if (!is_object($this->dataCache)) {
606  throw new Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
607  }
608  $data = [];
609  $propertyNames = [
610  'reflectedClassNames',
611  'classPropertyNames',
612  'classMethodNames',
613  'classTagsValues',
614  'methodTagsValues',
615  'methodParameters',
616  'propertyTagsValues',
617  'taggedClasses',
618  'classSchemata'
619  ];
620  foreach ($propertyNames as $propertyName) {
621  $data[$propertyName] = $this->{$propertyName};
622  }
623  $this->dataCache->set($this->cacheIdentifier, $data);
624  $this->dataCacheNeedsUpdate = false;
625  }
626 }
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)