[TASK] Streamline phpdoc annotations in EXT:extbase
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Reflection / ClassSchema.php
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
17 use Doctrine\Common\Annotations\AnnotationReader;
18 use TYPO3\CMS\Core\SingletonInterface;
19 use TYPO3\CMS\Core\Utility\ClassNamingUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
22 use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;
23 use TYPO3\CMS\Extbase\Annotation\Inject;
24 use TYPO3\CMS\Extbase\Annotation\ORM\Cascade;
25 use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
26 use TYPO3\CMS\Extbase\Annotation\ORM\Transient;
27 use TYPO3\CMS\Extbase\Annotation\Validate;
28 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
29 use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
30 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface;
31 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
32 use TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException;
33 use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException;
34 use TYPO3\CMS\Extbase\Validation\ValidatorResolver;
35
36 /**
37 * A class schema
38 * @internal only to be used within Extbase, not part of TYPO3 Core API.
39 */
40 class ClassSchema
41 {
42 /**
43 * Available model types
44 */
45 const MODELTYPE_ENTITY = 1;
46 const MODELTYPE_VALUEOBJECT = 2;
47
48 /**
49 * Name of the class this schema is referring to
50 *
51 * @var string
52 */
53 protected $className;
54
55 /**
56 * Model type of the class this schema is referring to
57 *
58 * @var int
59 */
60 protected $modelType = self::MODELTYPE_ENTITY;
61
62 /**
63 * Whether a repository exists for the class this schema is referring to
64 *
65 * @var bool
66 */
67 protected $aggregateRoot = false;
68
69 /**
70 * The name of the property holding the uuid of an entity, if any.
71 *
72 * @var string
73 */
74 protected $uuidPropertyName;
75
76 /**
77 * Properties of the class which need to be persisted
78 *
79 * @var array
80 */
81 protected $properties = [];
82
83 /**
84 * The properties forming the identity of an object
85 *
86 * @var array
87 */
88 protected $identityProperties = [];
89
90 /**
91 * Indicates if the class is a singleton or not.
92 *
93 * @var bool
94 */
95 private $isSingleton;
96
97 /**
98 * @var bool
99 */
100 private $isController;
101
102 /**
103 * @var array
104 */
105 private $methods;
106
107 /**
108 * @var array
109 */
110 private $tags;
111
112 /**
113 * @var array
114 */
115 private $injectProperties = [];
116
117 /**
118 * @var array
119 */
120 private $injectMethods = [];
121
122 /**
123 * Constructs this class schema
124 *
125 * @param string $className Name of the class this schema is referring to
126 * @throws \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
127 * @throws \ReflectionException
128 */
129 public function __construct($className)
130 {
131 $this->className = $className;
132
133 $reflectionClass = new \ReflectionClass($className);
134
135 $this->isSingleton = $reflectionClass->implementsInterface(SingletonInterface::class);
136 $this->isController = $reflectionClass->implementsInterface(ControllerInterface::class);
137
138 if ($reflectionClass->isSubclassOf(AbstractEntity::class)) {
139 $this->modelType = static::MODELTYPE_ENTITY;
140
141 $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
142 if (class_exists($possibleRepositoryClassName)) {
143 $this->setAggregateRoot(true);
144 }
145 }
146
147 if ($reflectionClass->isSubclassOf(AbstractValueObject::class)) {
148 $this->modelType = static::MODELTYPE_VALUEOBJECT;
149 }
150
151 $docCommentParser = new DocCommentParser(true);
152 $docCommentParser->parseDocComment($reflectionClass->getDocComment());
153 $this->tags = $docCommentParser->getTagsValues();
154
155 $this->reflectProperties($reflectionClass);
156 $this->reflectMethods($reflectionClass);
157 }
158
159 /**
160 * @param \ReflectionClass $reflectionClass
161 */
162 protected function reflectProperties(\ReflectionClass $reflectionClass)
163 {
164 $annotationReader = new AnnotationReader();
165
166 foreach ($reflectionClass->getProperties() as $reflectionProperty) {
167 $propertyName = $reflectionProperty->getName();
168
169 $this->properties[$propertyName] = [
170 'default' => $reflectionProperty->isDefault(),
171 'private' => $reflectionProperty->isPrivate(),
172 'protected' => $reflectionProperty->isProtected(),
173 'public' => $reflectionProperty->isPublic(),
174 'static' => $reflectionProperty->isStatic(),
175 'type' => null, // Extbase
176 'elementType' => null, // Extbase
177 'annotations' => [],
178 'tags' => [],
179 'validators' => []
180 ];
181
182 $docCommentParser = new DocCommentParser(true);
183 $docCommentParser->parseDocComment($reflectionProperty->getDocComment());
184 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
185 $this->properties[$propertyName]['tags'][strtolower($tag)] = $values;
186 }
187
188 $this->properties[$propertyName]['annotations']['inject'] = false;
189 $this->properties[$propertyName]['annotations']['lazy'] = false;
190 $this->properties[$propertyName]['annotations']['transient'] = false;
191 $this->properties[$propertyName]['annotations']['type'] = null;
192 $this->properties[$propertyName]['annotations']['cascade'] = null;
193 $this->properties[$propertyName]['annotations']['dependency'] = null;
194
195 $annotations = $annotationReader->getPropertyAnnotations($reflectionProperty);
196
197 /** @var array|Validate[] $validateAnnotations */
198 $validateAnnotations = array_filter($annotations, function ($annotation) {
199 return $annotation instanceof Validate;
200 });
201
202 if (count($validateAnnotations) > 0) {
203 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
204
205 foreach ($validateAnnotations as $validateAnnotation) {
206 $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validateAnnotation->validator);
207
208 $this->properties[$propertyName]['validators'][] = [
209 'name' => $validateAnnotation->validator,
210 'options' => $validateAnnotation->options,
211 'className' => $validatorObjectName,
212 ];
213 }
214 }
215
216 if ($docCommentParser->isTaggedWith('validate')) {
217 trigger_error(
218 sprintf(
219 'Property %s::%s is tagged with @validate which is deprecated and will be removed in TYPO3 v10.0.',
220 $reflectionClass->getName(),
221 $reflectionProperty->getName()
222 ),
223 E_USER_DEPRECATED
224 );
225
226 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
227
228 $validateValues = $docCommentParser->getTagValues('validate');
229 foreach ($validateValues as $validateValue) {
230 $validatorConfiguration = $validatorResolver->parseValidatorAnnotation($validateValue);
231
232 foreach ($validatorConfiguration['validators'] ?? [] as $validator) {
233 $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validator['validatorName']);
234
235 $this->properties[$propertyName]['validators'][] = [
236 'name' => $validator['validatorName'],
237 'options' => $validator['validatorOptions'],
238 'className' => $validatorObjectName,
239 ];
240 }
241 }
242 }
243
244 if ($annotationReader->getPropertyAnnotation($reflectionProperty, Lazy::class) instanceof Lazy) {
245 $this->properties[$propertyName]['annotations']['lazy'] = true;
246 }
247
248 if ($docCommentParser->isTaggedWith('lazy')) {
249 $this->properties[$propertyName]['annotations']['lazy'] = true;
250 trigger_error(
251 sprintf(
252 'Property %s::%s is tagged with @lazy which is deprecated and will be removed in TYPO3 v10.0.',
253 $reflectionClass->getName(),
254 $reflectionProperty->getName()
255 ),
256 E_USER_DEPRECATED
257 );
258 }
259
260 if ($annotationReader->getPropertyAnnotation($reflectionProperty, Transient::class) instanceof Transient) {
261 $this->properties[$propertyName]['annotations']['transient'] = true;
262 }
263
264 if ($docCommentParser->isTaggedWith('transient')) {
265 $this->properties[$propertyName]['annotations']['transient'] = true;
266 trigger_error(
267 sprintf(
268 'Property %s::%s is tagged with @transient which is deprecated and will be removed in TYPO3 v10.0.',
269 $reflectionClass->getName(),
270 $reflectionProperty->getName()
271 ),
272 E_USER_DEPRECATED
273 );
274 }
275
276 if ($propertyName !== 'settings'
277 && ($annotationReader->getPropertyAnnotation($reflectionProperty, Inject::class) instanceof Inject)
278 ) {
279 try {
280 $varValue = ltrim($docCommentParser->getTagValues('var')[0], '\\');
281 $this->properties[$propertyName]['annotations']['inject'] = true;
282 $this->properties[$propertyName]['annotations']['type'] = $varValue;
283 $this->properties[$propertyName]['annotations']['dependency'] = $varValue;
284
285 $this->injectProperties[] = $propertyName;
286 } catch (\Exception $e) {
287 }
288 }
289
290 if ($propertyName !== 'settings' && $docCommentParser->isTaggedWith('inject')) {
291 trigger_error(
292 sprintf(
293 'Property %s::%s is tagged with @inject which is deprecated and will be removed in TYPO3 v10.0.',
294 $reflectionClass->getName(),
295 $reflectionProperty->getName()
296 ),
297 E_USER_DEPRECATED
298 );
299 try {
300 $varValues = $docCommentParser->getTagValues('var');
301 $this->properties[$propertyName]['annotations']['inject'] = true;
302 $this->properties[$propertyName]['annotations']['type'] = ltrim($varValues[0], '\\');
303 $this->properties[$propertyName]['annotations']['dependency'] = ltrim($varValues[0], '\\');
304
305 if (!$reflectionProperty->isPublic()) {
306 trigger_error(
307 sprintf(
308 'Property %s::%s is not public and tagged with @inject which is deprecated and will stop working in TYPO3 v10.0.',
309 $reflectionClass->getName(),
310 $reflectionProperty->getName()
311 ),
312 E_USER_DEPRECATED
313 );
314 }
315
316 $this->injectProperties[] = $propertyName;
317 } catch (\Exception $e) {
318 }
319 }
320
321 if ($docCommentParser->isTaggedWith('var') && !$docCommentParser->isTaggedWith('transient')) {
322 try {
323 $cascadeAnnotationValues = $docCommentParser->getTagValues('cascade');
324 $this->properties[$propertyName]['annotations']['cascade'] = $cascadeAnnotationValues[0];
325 } catch (\Exception $e) {
326 }
327
328 if ($this->properties[$propertyName]['annotations']['cascade'] !== null) {
329 trigger_error(
330 sprintf(
331 'Property %s::%s is tagged with @cascade which is deprecated and will be removed in TYPO3 v10.0.',
332 $reflectionClass->getName(),
333 $reflectionProperty->getName()
334 ),
335 E_USER_DEPRECATED
336 );
337 }
338
339 if (($annotation = $annotationReader->getPropertyAnnotation($reflectionProperty, Cascade::class)) instanceof Cascade) {
340 /** @var Cascade $annotation */
341 $this->properties[$propertyName]['annotations']['cascade'] = $annotation->value;
342 }
343
344 try {
345 $type = TypeHandlingUtility::parseType(implode(' ', $docCommentParser->getTagValues('var')));
346 } catch (\Exception $e) {
347 $type = [
348 'type' => null,
349 'elementType' => null
350 ];
351 }
352
353 $this->properties[$propertyName]['type'] = $type['type'] ? ltrim($type['type'], '\\') : null;
354 $this->properties[$propertyName]['elementType'] = $type['elementType'] ? ltrim($type['elementType'], '\\') : null;
355 }
356
357 if ($docCommentParser->isTaggedWith('uuid')) {
358 $this->setUuidPropertyName($propertyName);
359 }
360
361 if ($docCommentParser->isTaggedWith('identity')) {
362 $this->markAsIdentityProperty($propertyName);
363 }
364 }
365 }
366
367 /**
368 * @param \ReflectionClass $reflectionClass
369 */
370 protected function reflectMethods(\ReflectionClass $reflectionClass)
371 {
372 $annotationReader = new AnnotationReader();
373
374 foreach ($reflectionClass->getMethods() as $reflectionMethod) {
375 $methodName = $reflectionMethod->getName();
376
377 $this->methods[$methodName] = [];
378 $this->methods[$methodName]['private'] = $reflectionMethod->isPrivate();
379 $this->methods[$methodName]['protected'] = $reflectionMethod->isProtected();
380 $this->methods[$methodName]['public'] = $reflectionMethod->isPublic();
381 $this->methods[$methodName]['static'] = $reflectionMethod->isStatic();
382 $this->methods[$methodName]['abstract'] = $reflectionMethod->isAbstract();
383 $this->methods[$methodName]['params'] = [];
384 $this->methods[$methodName]['tags'] = [];
385 $this->methods[$methodName]['annotations'] = [];
386 $this->methods[$methodName]['isAction'] = StringUtility::endsWith($methodName, 'Action');
387
388 $docCommentParser = new DocCommentParser(true);
389 $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
390
391 $argumentValidators = [];
392
393 $annotations = $annotationReader->getMethodAnnotations($reflectionMethod);
394
395 /** @var array|Validate[] $validateAnnotations */
396 $validateAnnotations = array_filter($annotations, function ($annotation) {
397 return $annotation instanceof Validate;
398 });
399
400 if ($this->isController && $this->methods[$methodName]['isAction'] && count($validateAnnotations) > 0) {
401 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
402
403 foreach ($validateAnnotations as $validateAnnotation) {
404 $validatorName = $validateAnnotation->validator;
405 $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validatorName);
406
407 $argumentValidators[$validateAnnotation->param][] = [
408 'name' => $validatorName,
409 'options' => $validateAnnotation->options,
410 'className' => $validatorObjectName,
411 ];
412 }
413 }
414
415 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
416 if ($tag === 'cli') {
417 trigger_error(
418 sprintf(
419 'Method %s::%s is tagged with @cli which is deprecated and will be removed in TYPO3 v10.0.',
420 $reflectionClass->getName(),
421 $reflectionMethod->getName()
422 ),
423 E_USER_DEPRECATED
424 );
425 }
426 if ($tag === 'internal' && $reflectionClass->isSubclassOf(\TYPO3\CMS\Extbase\Mvc\Controller\CommandController::class)) {
427 trigger_error(
428 sprintf(
429 'Command method %s::%s is tagged with @internal which is deprecated and will be removed in TYPO3 v10.0.',
430 $reflectionClass->getName(),
431 $reflectionMethod->getName()
432 ),
433 E_USER_DEPRECATED
434 );
435 }
436 if ($tag === 'ignorevalidation') {
437 trigger_error(
438 sprintf(
439 'Method %s::%s is tagged with @ignorevalidation which is deprecated and will be removed in TYPO3 v10.0.',
440 $reflectionClass->getName(),
441 $reflectionMethod->getName()
442 ),
443 E_USER_DEPRECATED
444 );
445 }
446 if ($tag === 'flushesCaches') {
447 trigger_error(
448 sprintf(
449 'Method %s::%s is tagged with @flushesCaches which is deprecated and will be removed in TYPO3 v10.0.',
450 $reflectionClass->getName(),
451 $reflectionMethod->getName()
452 ),
453 E_USER_DEPRECATED
454 );
455 }
456 if ($tag === 'validate' && $this->isController && $this->methods[$methodName]['isAction']) {
457 trigger_error(
458 sprintf(
459 'Method %s::%s is tagged with @validate which is deprecated and will be removed in TYPO3 v10.0.',
460 $reflectionClass->getName(),
461 $reflectionMethod->getName()
462 ),
463 E_USER_DEPRECATED
464 );
465
466 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
467
468 foreach ($values as $validate) {
469 $methodValidatorDefinition = $validatorResolver->parseValidatorAnnotation($validate);
470
471 foreach ($methodValidatorDefinition['validators'] as $validator) {
472 $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validator['validatorName']);
473
474 $argumentValidators[$methodValidatorDefinition['argumentName']][] = [
475 'name' => $validator['validatorName'],
476 'options' => $validator['validatorOptions'],
477 'className' => $validatorObjectName,
478 ];
479 }
480 }
481 }
482 $this->methods[$methodName]['tags'][$tag] = array_map(function ($value) use ($tag) {
483 // not stripping the dollar sign for @validate annotations is just
484 // a quick fix for a regression introduced in 9.0.0.
485 // This exception to the rules will vanish once the resolving of
486 // validators will take place inside this class and not in the
487 // controller during runtime.
488 return $tag === 'validate' ? $value : ltrim($value, '$');
489 }, $values);
490 }
491 unset($methodValidatorDefinition);
492
493 foreach ($annotations as $annotation) {
494 if ($annotation instanceof IgnoreValidation) {
495 $this->methods[$methodName]['tags']['ignorevalidation'][] = $annotation->argumentName;
496 }
497 }
498
499 $this->methods[$methodName]['description'] = $docCommentParser->getDescription();
500
501 foreach ($reflectionMethod->getParameters() as $parameterPosition => $reflectionParameter) {
502 /* @var \ReflectionParameter $reflectionParameter */
503
504 $parameterName = $reflectionParameter->getName();
505
506 $this->methods[$methodName]['params'][$parameterName] = [];
507 $this->methods[$methodName]['params'][$parameterName]['position'] = $parameterPosition; // compat
508 $this->methods[$methodName]['params'][$parameterName]['byReference'] = $reflectionParameter->isPassedByReference(); // compat
509 $this->methods[$methodName]['params'][$parameterName]['array'] = $reflectionParameter->isArray(); // compat
510 $this->methods[$methodName]['params'][$parameterName]['optional'] = $reflectionParameter->isOptional();
511 $this->methods[$methodName]['params'][$parameterName]['allowsNull'] = $reflectionParameter->allowsNull(); // compat
512 $this->methods[$methodName]['params'][$parameterName]['class'] = null; // compat
513 $this->methods[$methodName]['params'][$parameterName]['type'] = null;
514 $this->methods[$methodName]['params'][$parameterName]['nullable'] = $reflectionParameter->allowsNull();
515 $this->methods[$methodName]['params'][$parameterName]['default'] = null;
516 $this->methods[$methodName]['params'][$parameterName]['hasDefaultValue'] = $reflectionParameter->isDefaultValueAvailable();
517 $this->methods[$methodName]['params'][$parameterName]['defaultValue'] = null; // compat
518 $this->methods[$methodName]['params'][$parameterName]['dependency'] = null; // Extbase DI
519 $this->methods[$methodName]['params'][$parameterName]['validators'] = [];
520
521 if ($reflectionParameter->isDefaultValueAvailable()) {
522 $this->methods[$methodName]['params'][$parameterName]['default'] = $reflectionParameter->getDefaultValue();
523 $this->methods[$methodName]['params'][$parameterName]['defaultValue'] = $reflectionParameter->getDefaultValue(); // compat
524 }
525
526 if (($reflectionType = $reflectionParameter->getType()) instanceof \ReflectionType) {
527 $this->methods[$methodName]['params'][$parameterName]['type'] = (string)$reflectionType;
528 $this->methods[$methodName]['params'][$parameterName]['nullable'] = $reflectionType->allowsNull();
529 }
530
531 if (($parameterClass = $reflectionParameter->getClass()) instanceof \ReflectionClass) {
532 $this->methods[$methodName]['params'][$parameterName]['class'] = $parameterClass->getName();
533 $this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($parameterClass->getName(), '\\');
534 } else {
535 $methodTagsAndValues = $this->methods[$methodName]['tags'];
536 if (isset($methodTagsAndValues['param'][$parameterPosition])) {
537 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
538 if (count($explodedParameters) >= 2) {
539 if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
540 // ensure that short names of simple types are resolved correctly to the long form
541 // this is important for all kinds of type checks later on
542 $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
543
544 $this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($typeInfo['type'], '\\');
545 } else {
546 $this->methods[$methodName]['params'][$parameterName]['type'] = ltrim($explodedParameters[0], '\\');
547 }
548 }
549 }
550 }
551
552 // Extbase DI
553 if ($reflectionParameter->getClass() instanceof \ReflectionClass
554 && ($reflectionMethod->isConstructor() || $this->hasInjectMethodName($reflectionMethod))
555 ) {
556 $this->methods[$methodName]['params'][$parameterName]['dependency'] = $reflectionParameter->getClass()->getName();
557 }
558
559 // Extbase Validation
560 if (isset($argumentValidators[$parameterName])) {
561 if ($this->methods[$methodName]['params'][$parameterName]['type'] === null) {
562 throw new InvalidTypeHintException(
563 'Missing type information for parameter "$' . $parameterName . '" in ' . $this->className . '->' . $methodName . '(): Either use an @param annotation or use a type hint.',
564 1515075192
565 );
566 }
567
568 $this->methods[$methodName]['params'][$parameterName]['validators'] = $argumentValidators[$parameterName];
569 unset($argumentValidators[$parameterName]);
570 }
571 }
572
573 // Extbase Validation
574 foreach ($argumentValidators as $parameterName => $validators) {
575 $validatorNames = array_column($validators, 'name');
576
577 throw new InvalidValidationConfigurationException(
578 'Invalid validate annotation in ' . $this->className . '->' . $methodName . '(): The following validators have been defined for missing param "$' . $parameterName . '": ' . implode(', ', $validatorNames),
579 1515073585
580 );
581 }
582
583 // Extbase
584 $this->methods[$methodName]['injectMethod'] = false;
585 if ($this->hasInjectMethodName($reflectionMethod)
586 && count($this->methods[$methodName]['params']) === 1
587 && reset($this->methods[$methodName]['params'])['dependency'] !== null
588 ) {
589 $this->methods[$methodName]['injectMethod'] = true;
590 $this->injectMethods[] = $methodName;
591 }
592 }
593 }
594
595 /**
596 * Returns the class name this schema is referring to
597 *
598 * @return string The class name
599 */
600 public function getClassName(): string
601 {
602 return $this->className;
603 }
604
605 /**
606 * Adds (defines) a specific property and its type.
607 *
608 * @param string $name Name of the property
609 * @param string $type Type of the property
610 * @param bool $lazy Whether the property should be lazy-loaded when reconstituting
611 * @param string $cascade Strategy to cascade the object graph.
612 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
613 */
614 public function addProperty($name, $type, $lazy = false, $cascade = '')
615 {
616 trigger_error(
617 'This method will be removed in TYPO3 v10.0, properties will be automatically added on ClassSchema construction.',
618 E_USER_DEPRECATED
619 );
620 $type = TypeHandlingUtility::parseType($type);
621 $this->properties[$name] = [
622 'type' => $type['type'],
623 'elementType' => $type['elementType'],
624 'lazy' => $lazy,
625 'cascade' => $cascade
626 ];
627 }
628
629 /**
630 * Returns the given property defined in this schema. Check with
631 * hasProperty($propertyName) before!
632 *
633 * @param string $propertyName
634 * @return array
635 */
636 public function getProperty($propertyName)
637 {
638 return isset($this->properties[$propertyName]) && is_array($this->properties[$propertyName])
639 ? $this->properties[$propertyName]
640 : [];
641 }
642
643 /**
644 * Returns all properties defined in this schema
645 *
646 * @return array
647 */
648 public function getProperties()
649 {
650 return $this->properties;
651 }
652
653 /**
654 * Sets the model type of the class this schema is referring to.
655 *
656 * @param int $modelType The model type, one of the MODELTYPE_* constants.
657 * @throws \InvalidArgumentException
658 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
659 */
660 public function setModelType($modelType)
661 {
662 trigger_error(
663 'This method will be removed in TYPO3 v10.0, modelType will be automatically set on ClassSchema construction.',
664 E_USER_DEPRECATED
665 );
666 if ($modelType < self::MODELTYPE_ENTITY || $modelType > self::MODELTYPE_VALUEOBJECT) {
667 throw new \InvalidArgumentException('"' . $modelType . '" is an invalid model type.', 1212519195);
668 }
669 $this->modelType = $modelType;
670 }
671
672 /**
673 * Returns the model type of the class this schema is referring to.
674 *
675 * @return int The model type, one of the MODELTYPE_* constants.
676 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
677 */
678 public function getModelType()
679 {
680 trigger_error(
681 'This method will be removed in TYPO3 v10.0.',
682 E_USER_DEPRECATED
683 );
684 return $this->modelType;
685 }
686
687 /**
688 * Marks the class if it is root of an aggregate and therefore accessible
689 * through a repository - or not.
690 *
691 * @param bool $isRoot TRUE if it is the root of an aggregate
692 */
693 public function setAggregateRoot($isRoot)
694 {
695 $this->aggregateRoot = $isRoot;
696 }
697
698 /**
699 * Whether the class is an aggregate root and therefore accessible through
700 * a repository.
701 *
702 * @return bool TRUE if it is managed
703 */
704 public function isAggregateRoot(): bool
705 {
706 return $this->aggregateRoot;
707 }
708
709 /**
710 * If the class schema has a certain property.
711 *
712 * @param string $propertyName Name of the property
713 * @return bool
714 */
715 public function hasProperty($propertyName): bool
716 {
717 return array_key_exists($propertyName, $this->properties);
718 }
719
720 /**
721 * Sets the property marked as uuid of an object with @uuid
722 *
723 * @param string $propertyName
724 * @throws \InvalidArgumentException
725 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
726 */
727 public function setUuidPropertyName($propertyName)
728 {
729 trigger_error(
730 'Tagging properties with @uuid is deprecated and will be removed in TYPO3 v10.0.',
731 E_USER_DEPRECATED
732 );
733 if (!array_key_exists($propertyName, $this->properties)) {
734 throw new \InvalidArgumentException('Property "' . $propertyName . '" must be added to the class schema before it can be marked as UUID property.', 1233863842);
735 }
736 $this->uuidPropertyName = $propertyName;
737 }
738
739 /**
740 * Gets the name of the property marked as uuid of an object
741 *
742 * @return string
743 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
744 */
745 public function getUuidPropertyName()
746 {
747 trigger_error(
748 'Tagging properties with @uuid is deprecated and will be removed in TYPO3 v10.0.',
749 E_USER_DEPRECATED
750 );
751 return $this->uuidPropertyName;
752 }
753
754 /**
755 * Marks the given property as one of properties forming the identity
756 * of an object. The property must already be registered in the class
757 * schema.
758 *
759 * @param string $propertyName
760 * @throws \InvalidArgumentException
761 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
762 */
763 public function markAsIdentityProperty($propertyName)
764 {
765 trigger_error(
766 'Tagging properties with @identity is deprecated and will be removed in TYPO3 v10.0.',
767 E_USER_DEPRECATED
768 );
769 if (!array_key_exists($propertyName, $this->properties)) {
770 throw new \InvalidArgumentException('Property "' . $propertyName . '" must be added to the class schema before it can be marked as identity property.', 1233775407);
771 }
772 if ($this->properties[$propertyName]['annotations']['lazy'] === true) {
773 throw new \InvalidArgumentException('Property "' . $propertyName . '" must not be makred for lazy loading to be marked as identity property.', 1239896904);
774 }
775 $this->identityProperties[$propertyName] = $this->properties[$propertyName]['type'];
776 }
777
778 /**
779 * Gets the properties (names and types) forming the identity of an object.
780 *
781 * @return array
782 * @see markAsIdentityProperty()
783 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
784 */
785 public function getIdentityProperties()
786 {
787 trigger_error(
788 'Tagging properties with @identity is deprecated and will be removed in TYPO3 v10.0.',
789 E_USER_DEPRECATED
790 );
791 return $this->identityProperties;
792 }
793
794 /**
795 * @return bool
796 */
797 public function hasConstructor(): bool
798 {
799 return isset($this->methods['__construct']);
800 }
801
802 /**
803 * @param string $name
804 * @return array
805 */
806 public function getMethod(string $name): array
807 {
808 return $this->methods[$name] ?? [];
809 }
810
811 /**
812 * @return array
813 */
814 public function getMethods(): array
815 {
816 return $this->methods;
817 }
818
819 /**
820 * @param \ReflectionMethod $reflectionMethod
821 * @return bool
822 */
823 protected function hasInjectMethodName(\ReflectionMethod $reflectionMethod): bool
824 {
825 $methodName = $reflectionMethod->getName();
826 if ($methodName === 'injectSettings' || !$reflectionMethod->isPublic()) {
827 return false;
828 }
829
830 if (
831 strpos($reflectionMethod->getName(), 'inject') === 0
832 ) {
833 return true;
834 }
835
836 return false;
837 }
838
839 /**
840 * @return bool
841 * @internal
842 */
843 public function isModel(): bool
844 {
845 return $this->isEntity() || $this->isValueObject();
846 }
847
848 /**
849 * @return bool
850 * @internal
851 */
852 public function isEntity(): bool
853 {
854 return $this->modelType === static::MODELTYPE_ENTITY;
855 }
856
857 /**
858 * @return bool
859 * @internal
860 */
861 public function isValueObject(): bool
862 {
863 return $this->modelType === static::MODELTYPE_VALUEOBJECT;
864 }
865
866 /**
867 * @return bool
868 */
869 public function isSingleton(): bool
870 {
871 return $this->isSingleton;
872 }
873
874 /**
875 * @param string $methodName
876 * @return bool
877 */
878 public function hasMethod(string $methodName): bool
879 {
880 return isset($this->methods[$methodName]);
881 }
882
883 /**
884 * @return array
885 */
886 public function getTags(): array
887 {
888 return $this->tags;
889 }
890
891 /**
892 * @return bool
893 */
894 public function hasInjectProperties(): bool
895 {
896 return count($this->injectProperties) > 0;
897 }
898
899 /**
900 * @return bool
901 */
902 public function hasInjectMethods(): bool
903 {
904 return count($this->injectMethods) > 0;
905 }
906
907 /**
908 * @return array
909 */
910 public function getInjectMethods(): array
911 {
912 $injectMethods = [];
913 foreach ($this->injectMethods as $injectMethodName) {
914 $injectMethods[$injectMethodName] = reset($this->methods[$injectMethodName]['params'])['dependency'];
915 }
916
917 return $injectMethods;
918 }
919
920 /**
921 * @return array
922 */
923 public function getInjectProperties(): array
924 {
925 $injectProperties = [];
926 foreach ($this->injectProperties as $injectPropertyName) {
927 $injectProperties[$injectPropertyName] = $this->properties[$injectPropertyName]['annotations']['dependency'];
928 }
929
930 return $injectProperties;
931 }
932
933 /**
934 * @return array
935 */
936 public function getConstructorArguments(): array
937 {
938 if (!$this->hasConstructor()) {
939 return [];
940 }
941
942 return $this->methods['__construct']['params'];
943 }
944 }