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