2 namespace TYPO3\CMS\Extbase\Reflection
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\SingletonInterface
;
18 use TYPO3\CMS\Core\Utility\ClassNamingUtility
;
19 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity
;
20 use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject
;
21 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility
;
27 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
32 * Available model types
34 const MODELTYPE_ENTITY
= 1;
35 const MODELTYPE_VALUEOBJECT
= 2;
38 * Name of the class this schema is referring to
45 * Model type of the class this schema is referring to
49 protected $modelType = self
::MODELTYPE_ENTITY
;
52 * Whether a repository exists for the class this schema is referring to
56 protected $aggregateRoot = false;
59 * The name of the property holding the uuid of an entity, if any.
63 protected $uuidPropertyName;
66 * Properties of the class which need to be persisted
70 protected $properties = [];
73 * The properties forming the identity of an object
77 protected $identityProperties = [];
80 * Indicates if the class is a singleton or not.
94 protected static $ignoredTags = ['package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const'];
104 private $injectProperties = [];
109 private $injectMethods = [];
112 * Constructs this class schema
114 * @param string $className Name of the class this schema is referring to
115 * @throws \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
116 * @throws \ReflectionException
118 public function __construct($className)
120 $this->className
= $className;
122 $reflectionClass = new \
ReflectionClass($className);
124 $this->isSingleton
= $reflectionClass->implementsInterface(SingletonInterface
::class);
126 if ($reflectionClass->isSubclassOf(AbstractEntity
::class)) {
127 $this->modelType
= static::MODELTYPE_ENTITY
;
129 $possibleRepositoryClassName = ClassNamingUtility
::translateModelNameToRepositoryName($className);
130 if (class_exists($possibleRepositoryClassName)) {
131 $this->setAggregateRoot(true);
135 if ($reflectionClass->isSubclassOf(AbstractValueObject
::class)) {
136 $this->modelType
= static::MODELTYPE_VALUEOBJECT
;
139 $docCommentParser = new DocCommentParser();
140 $docCommentParser->parseDocComment($reflectionClass->getDocComment());
141 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
142 if (in_array($tag, static::$ignoredTags, true)) {
146 $this->tags
[$tag] = $values;
149 $this->reflectProperties($reflectionClass);
150 $this->reflectMethods($reflectionClass);
154 * @param \ReflectionClass $reflectionClass
156 protected function reflectProperties(\ReflectionClass
$reflectionClass)
158 foreach ($reflectionClass->getProperties() as $reflectionProperty) {
159 $propertyName = $reflectionProperty->getName();
161 $this->properties
[$propertyName] = [
162 'default' => $reflectionProperty->isDefault(),
163 'private' => $reflectionProperty->isPrivate(),
164 'protected' => $reflectionProperty->isProtected(),
165 'public' => $reflectionProperty->isPublic(),
166 'static' => $reflectionProperty->isStatic(),
167 'type' => null, // Extbase
168 'elementType' => null, // Extbase
173 $docCommentParser = new DocCommentParser();
174 $docCommentParser->parseDocComment($reflectionProperty->getDocComment());
175 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
176 if (in_array($tag, static::$ignoredTags, true)) {
180 $this->properties
[$propertyName]['tags'][$tag] = $values;
183 $this->properties
[$propertyName]['annotations']['inject'] = false;
184 $this->properties
[$propertyName]['annotations']['lazy'] = $docCommentParser->isTaggedWith('lazy');
185 $this->properties
[$propertyName]['annotations']['transient'] = $docCommentParser->isTaggedWith('transient');
186 $this->properties
[$propertyName]['annotations']['type'] = null;
187 $this->properties
[$propertyName]['annotations']['cascade'] = null;
188 $this->properties
[$propertyName]['annotations']['dependency'] = null;
190 if ($propertyName !== 'settings' && $docCommentParser->isTaggedWith('inject')) {
192 $varValues = $docCommentParser->getTagValues('var');
193 $this->properties
[$propertyName]['annotations']['inject'] = true;
194 $this->properties
[$propertyName]['annotations']['type'] = ltrim($varValues[0], '\\');
195 $this->properties
[$propertyName]['annotations']['dependency'] = ltrim($varValues[0], '\\');
197 if (!$reflectionProperty->isPublic()) {
199 'Using @inject with non-public properties is deprecated since TYPO3 v9.0 and will stop working in TYPO3 v10.0.',
204 $this->injectProperties
[] = $propertyName;
205 } catch (\Exception
$e) {
209 if ($docCommentParser->isTaggedWith('var') && !$docCommentParser->isTaggedWith('transient')) {
211 $cascadeAnnotationValues = $docCommentParser->getTagValues('cascade');
212 $this->properties
[$propertyName]['annotations']['cascade'] = $cascadeAnnotationValues[0];
213 } catch (\Exception
$e) {
217 $type = TypeHandlingUtility
::parseType(implode(' ', $docCommentParser->getTagValues('var')));
218 } catch (\Exception
$e) {
221 'elementType' => null
225 $this->properties
[$propertyName]['type'] = $type['type'] ?
ltrim($type['type'], '\\') : null;
226 $this->properties
[$propertyName]['elementType'] = $type['elementType'] ?
ltrim($type['elementType'], '\\') : null;
229 if ($docCommentParser->isTaggedWith('uuid')) {
230 $this->setUuidPropertyName($propertyName);
233 if ($docCommentParser->isTaggedWith('identity')) {
234 $this->markAsIdentityProperty($propertyName);
240 * @param \ReflectionClass $reflectionClass
242 protected function reflectMethods(\ReflectionClass
$reflectionClass)
244 foreach ($reflectionClass->getMethods() as $reflectionMethod) {
245 $methodName = $reflectionMethod->getName();
247 $this->methods
[$methodName] = [];
248 $this->methods
[$methodName]['private'] = $reflectionMethod->isPrivate();
249 $this->methods
[$methodName]['protected'] = $reflectionMethod->isProtected();
250 $this->methods
[$methodName]['public'] = $reflectionMethod->isPublic();
251 $this->methods
[$methodName]['static'] = $reflectionMethod->isStatic();
252 $this->methods
[$methodName]['abstract'] = $reflectionMethod->isAbstract();
253 $this->methods
[$methodName]['params'] = [];
254 $this->methods
[$methodName]['tags'] = [];
256 $docCommentParser = new DocCommentParser();
257 $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
258 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
259 if (in_array($tag, static::$ignoredTags, true)) {
263 $this->methods
[$methodName]['tags'][$tag] = $values;
266 $this->methods
[$methodName]['description'] = $docCommentParser->getDescription();
268 foreach ($reflectionMethod->getParameters() as $parameterPosition => $reflectionParameter) {
269 /* @var $reflectionParameter \ReflectionParameter */
271 $parameterName = $reflectionParameter->getName();
273 $this->methods
[$methodName]['params'][$parameterName] = [];
274 $this->methods
[$methodName]['params'][$parameterName]['position'] = $parameterPosition; // compat
275 $this->methods
[$methodName]['params'][$parameterName]['byReference'] = $reflectionParameter->isPassedByReference(); // compat
276 $this->methods
[$methodName]['params'][$parameterName]['array'] = $reflectionParameter->isArray(); // compat
277 $this->methods
[$methodName]['params'][$parameterName]['optional'] = $reflectionParameter->isOptional();
278 $this->methods
[$methodName]['params'][$parameterName]['allowsNull'] = $reflectionParameter->allowsNull(); // compat
279 $this->methods
[$methodName]['params'][$parameterName]['class'] = null; // compat
280 $this->methods
[$methodName]['params'][$parameterName]['type'] = null;
281 $this->methods
[$methodName]['params'][$parameterName]['nullable'] = $reflectionParameter->allowsNull();
282 $this->methods
[$methodName]['params'][$parameterName]['default'] = null;
283 $this->methods
[$methodName]['params'][$parameterName]['hasDefaultValue'] = $reflectionParameter->isDefaultValueAvailable();
284 $this->methods
[$methodName]['params'][$parameterName]['defaultValue'] = null; // compat
285 $this->methods
[$methodName]['params'][$parameterName]['dependency'] = null; // Extbase DI
287 if ($reflectionParameter->isDefaultValueAvailable()) {
288 $this->methods
[$methodName]['params'][$parameterName]['default'] = $reflectionParameter->getDefaultValue();
289 $this->methods
[$methodName]['params'][$parameterName]['defaultValue'] = $reflectionParameter->getDefaultValue(); // compat
292 if (($reflectionType = $reflectionParameter->getType()) instanceof \ReflectionType
) {
293 $this->methods
[$methodName]['params'][$parameterName]['type'] = (string)$reflectionType;
294 $this->methods
[$methodName]['params'][$parameterName]['nullable'] = $reflectionType->allowsNull();
297 if (($parameterClass = $reflectionParameter->getClass()) instanceof \ReflectionClass
) {
298 $this->methods
[$methodName]['params'][$parameterName]['class'] = $parameterClass->getName();
299 $this->methods
[$methodName]['params'][$parameterName]['type'] = ltrim($parameterClass->getName(), '\\');
301 $methodTagsAndValues = $this->methods
[$methodName]['tags'];
302 if (isset($methodTagsAndValues['param'], $methodTagsAndValues['param'][$parameterPosition])) {
303 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
304 if (count($explodedParameters) >= 2) {
305 if (TypeHandlingUtility
::isSimpleType($explodedParameters[0])) {
306 // ensure that short names of simple types are resolved correctly to the long form
307 // this is important for all kinds of type checks later on
308 $typeInfo = TypeHandlingUtility
::parseType($explodedParameters[0]);
310 $this->methods
[$methodName]['params'][$parameterName]['type'] = ltrim($typeInfo['type'], '\\');
312 $this->methods
[$methodName]['params'][$parameterName]['type'] = ltrim($explodedParameters[0], '\\');
319 if ($reflectionParameter->getClass() instanceof \ReflectionClass
320 && ($reflectionMethod->isConstructor() ||
$this->hasInjectMethodName($reflectionMethod))
322 $this->methods
[$methodName]['params'][$parameterName]['dependency'] = $reflectionParameter->getClass()->getName();
327 $this->methods
[$methodName]['injectMethod'] = false;
328 if ($this->hasInjectMethodName($reflectionMethod)
329 && count($this->methods
[$methodName]['params']) === 1
330 && reset($this->methods
[$methodName]['params'])['dependency'] !== null
332 $this->methods
[$methodName]['injectMethod'] = true;
333 $this->injectMethods
[] = $methodName;
339 * Returns the class name this schema is referring to
341 * @return string The class name
343 public function getClassName(): string
345 return $this->className
;
349 * Adds (defines) a specific property and its type.
351 * @param string $name Name of the property
352 * @param string $type Type of the property
353 * @param bool $lazy Whether the property should be lazy-loaded when reconstituting
354 * @param string $cascade Strategy to cascade the object graph.
357 public function addProperty($name, $type, $lazy = false, $cascade = '')
360 'This method will be removed in TYPO3 v10.0, properties will be automatically added on ClassSchema construction.',
363 $type = TypeHandlingUtility
::parseType($type);
364 $this->properties
[$name] = [
365 'type' => $type['type'],
366 'elementType' => $type['elementType'],
368 'cascade' => $cascade
373 * Returns the given property defined in this schema. Check with
374 * hasProperty($propertyName) before!
376 * @param string $propertyName
379 public function getProperty($propertyName)
381 return is_array($this->properties
[$propertyName]) ?
$this->properties
[$propertyName] : [];
385 * Returns all properties defined in this schema
389 public function getProperties()
391 return $this->properties
;
395 * Sets the model type of the class this schema is referring to.
397 * @param int $modelType The model type, one of the MODELTYPE_* constants.
398 * @throws \InvalidArgumentException
401 public function setModelType($modelType)
404 'This method will be removed in TYPO3 v10.0, modelType will be automatically set on ClassSchema construction.',
407 if ($modelType < self
::MODELTYPE_ENTITY ||
$modelType > self
::MODELTYPE_VALUEOBJECT
) {
408 throw new \
InvalidArgumentException('"' . $modelType . '" is an invalid model type.', 1212519195);
410 $this->modelType
= $modelType;
414 * Returns the model type of the class this schema is referring to.
416 * @return int The model type, one of the MODELTYPE_* constants.
419 public function getModelType()
422 'This method will be removed in TYPO3 v10.0.',
425 return $this->modelType
;
429 * Marks the class if it is root of an aggregate and therefore accessible
430 * through a repository - or not.
432 * @param bool $isRoot TRUE if it is the root of an aggregate
434 public function setAggregateRoot($isRoot)
436 $this->aggregateRoot
= $isRoot;
440 * Whether the class is an aggregate root and therefore accessible through
443 * @return bool TRUE if it is managed
445 public function isAggregateRoot(): bool
447 return $this->aggregateRoot
;
451 * If the class schema has a certain property.
453 * @param string $propertyName Name of the property
456 public function hasProperty($propertyName): bool
458 return array_key_exists($propertyName, $this->properties
);
462 * Sets the property marked as uuid of an object with @uuid
464 * @param string $propertyName
465 * @throws \InvalidArgumentException
468 public function setUuidPropertyName($propertyName)
471 'Tagging properties with @uuid is deprecated and will be removed in TYPO3 v10.0.',
474 if (!array_key_exists($propertyName, $this->properties
)) {
475 throw new \
InvalidArgumentException('Property "' . $propertyName . '" must be added to the class schema before it can be marked as UUID property.', 1233863842);
477 $this->uuidPropertyName
= $propertyName;
481 * Gets the name of the property marked as uuid of an object
486 public function getUuidPropertyName()
489 'Tagging properties with @uuid is deprecated and will be removed in TYPO3 v10.0.',
492 return $this->uuidPropertyName
;
496 * Marks the given property as one of properties forming the identity
497 * of an object. The property must already be registered in the class
500 * @param string $propertyName
501 * @throws \InvalidArgumentException
504 public function markAsIdentityProperty($propertyName)
507 'Tagging properties with @identity is deprecated and will be removed in TYPO3 v10.0.',
510 if (!array_key_exists($propertyName, $this->properties
)) {
511 throw new \
InvalidArgumentException('Property "' . $propertyName . '" must be added to the class schema before it can be marked as identity property.', 1233775407);
513 if ($this->properties
[$propertyName]['annotations']['lazy'] === true) {
514 throw new \
InvalidArgumentException('Property "' . $propertyName . '" must not be makred for lazy loading to be marked as identity property.', 1239896904);
516 $this->identityProperties
[$propertyName] = $this->properties
[$propertyName]['type'];
520 * Gets the properties (names and types) forming the identity of an object.
523 * @see markAsIdentityProperty()
526 public function getIdentityProperties()
529 'Tagging properties with @identity is deprecated and will be removed in TYPO3 v10.0.',
532 return $this->identityProperties
;
538 public function hasConstructor(): bool
540 return isset($this->methods
['__construct']);
544 * @param string $name
547 public function getMethod(string $name): array
549 return $this->methods
[$name] ??
[];
555 public function getMethods(): array
557 return $this->methods
;
561 * @param \ReflectionMethod $reflectionMethod
564 protected function hasInjectMethodName(\ReflectionMethod
$reflectionMethod): bool
566 $methodName = $reflectionMethod->getName();
567 if ($methodName === 'injectSettings' ||
!$reflectionMethod->isPublic()) {
572 strpos($reflectionMethod->getName(), 'inject') === 0
584 public function isModel(): bool
586 return $this->isEntity() ||
$this->isValueObject();
593 public function isEntity(): bool
595 return $this->modelType
=== static::MODELTYPE_ENTITY
;
602 public function isValueObject(): bool
604 return $this->modelType
=== static::MODELTYPE_VALUEOBJECT
;
610 public function isSingleton(): bool
612 return $this->isSingleton
;
616 * @param string $methodName
619 public function hasMethod(string $methodName): bool
621 return isset($this->methods
[$methodName]);
627 public function getTags(): array
635 public function hasInjectProperties(): bool
637 return count($this->injectProperties
) > 0;
643 public function hasInjectMethods(): bool
645 return count($this->injectMethods
) > 0;
651 public function getInjectMethods(): array
654 foreach ($this->injectMethods
as $injectMethodName) {
655 $injectMethods[$injectMethodName] = reset($this->methods
[$injectMethodName]['params'])['dependency'];
658 return $injectMethods;
664 public function getInjectProperties(): array
666 $injectProperties = [];
667 foreach ($this->injectProperties
as $injectPropertyName) {
668 $injectProperties[$injectPropertyName] = $this->properties
[$injectPropertyName]['annotations']['dependency'];
671 return $injectProperties;
677 public function getConstructorArguments(): array
679 if (!$this->hasConstructor()) {
683 return $this->methods
['__construct']['params'];