17 use Doctrine\Common\Annotations\AnnotationReader;
133 $reflectionClass = new \ReflectionClass(
$className);
135 $this->
isSingleton = $reflectionClass->implementsInterface(SingletonInterface::class);
136 $this->isController = $reflectionClass->implementsInterface(ControllerInterface::class);
138 if ($reflectionClass->isSubclassOf(AbstractEntity::class)) {
139 $this->modelType = static::MODELTYPE_ENTITY;
142 if (class_exists($possibleRepositoryClassName)) {
147 if ($reflectionClass->isSubclassOf(AbstractValueObject::class)) {
148 $this->modelType = static::MODELTYPE_VALUEOBJECT;
152 $docCommentParser->parseDocComment($reflectionClass->getDocComment());
153 $this->tags = $docCommentParser->getTagsValues();
164 $annotationReader =
new AnnotationReader();
166 foreach ($reflectionClass->getProperties() as $reflectionProperty) {
167 $propertyName = $reflectionProperty->getName();
169 $this->properties[$propertyName] = [
170 'default' => $reflectionProperty->isDefault(),
171 'private' => $reflectionProperty->isPrivate(),
172 'protected' => $reflectionProperty->isProtected(),
173 'public' => $reflectionProperty->isPublic(),
174 'static' => $reflectionProperty->isStatic(),
176 'elementType' =>
null,
183 $docCommentParser->parseDocComment($reflectionProperty->getDocComment());
184 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
185 $this->properties[$propertyName][
'tags'][strtolower($tag)] = $values;
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;
195 $annotations = $annotationReader->getPropertyAnnotations($reflectionProperty);
198 $validateAnnotations = array_filter($annotations,
function ($annotation) {
199 return $annotation instanceof Validate;
202 if (count($validateAnnotations) > 0) {
203 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
205 foreach ($validateAnnotations as $validateAnnotation) {
206 $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validateAnnotation->validator);
208 $this->properties[$propertyName][
'validators'][] = [
209 'name' => $validateAnnotation->validator,
210 'options' => $validateAnnotation->options,
211 'className' => $validatorObjectName,
216 if ($docCommentParser->isTaggedWith(
'validate')) {
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()
226 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
228 $validateValues = $docCommentParser->getTagValues(
'validate');
229 foreach ($validateValues as $validateValue) {
230 $validatorConfiguration = $validatorResolver->parseValidatorAnnotation($validateValue);
232 foreach ($validatorConfiguration[
'validators'] ?? [] as
$validator) {
233 $validatorObjectName = $validatorResolver->resolveValidatorObjectName(
$validator[
'validatorName']);
235 $this->properties[$propertyName][
'validators'][] = [
238 'className' => $validatorObjectName,
244 if ($annotationReader->getPropertyAnnotation($reflectionProperty, Lazy::class) instanceof Lazy) {
245 $this->properties[$propertyName][
'annotations'][
'lazy'] =
true;
248 if ($docCommentParser->isTaggedWith(
'lazy')) {
249 $this->properties[$propertyName][
'annotations'][
'lazy'] =
true;
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()
260 if ($annotationReader->getPropertyAnnotation($reflectionProperty, Transient::class) instanceof Transient) {
261 $this->properties[$propertyName][
'annotations'][
'transient'] =
true;
264 if ($docCommentParser->isTaggedWith(
'transient')) {
265 $this->properties[$propertyName][
'annotations'][
'transient'] =
true;
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()
276 if ($propertyName !==
'settings'
277 && ($annotationReader->getPropertyAnnotation($reflectionProperty, Inject::class) instanceof Inject)
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;
285 $this->injectProperties[] = $propertyName;
286 }
catch (\Exception $e) {
290 if ($propertyName !==
'settings' && $docCommentParser->isTaggedWith(
'inject')) {
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()
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],
'\\');
305 if (!$reflectionProperty->isPublic()) {
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()
316 $this->injectProperties[] = $propertyName;
317 }
catch (\Exception $e) {
321 if ($docCommentParser->isTaggedWith(
'var') && !$docCommentParser->isTaggedWith(
'transient')) {
323 $cascadeAnnotationValues = $docCommentParser->getTagValues(
'cascade');
324 $this->properties[$propertyName][
'annotations'][
'cascade'] = $cascadeAnnotationValues[0];
325 }
catch (\Exception $e) {
328 if ($this->properties[$propertyName][
'annotations'][
'cascade'] !==
null) {
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()
339 if (($annotation = $annotationReader->getPropertyAnnotation($reflectionProperty, Cascade::class)) instanceof Cascade) {
341 $this->properties[$propertyName][
'annotations'][
'cascade'] = $annotation->value;
346 }
catch (\Exception $e) {
349 'elementType' => null
353 $this->properties[$propertyName][
'type'] = $type[
'type'] ? ltrim($type[
'type'],
'\\') : null;
354 $this->properties[$propertyName][
'elementType'] = $type[
'elementType'] ? ltrim($type[
'elementType'],
'\\') : null;
357 if ($docCommentParser->isTaggedWith(
'uuid')) {
361 if ($docCommentParser->isTaggedWith(
'identity')) {
372 $annotationReader =
new AnnotationReader();
374 foreach ($reflectionClass->getMethods() as $reflectionMethod) {
375 $methodName = $reflectionMethod->getName();
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'] = [];
388 $docCommentParser =
new DocCommentParser(
true);
389 $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
391 $argumentValidators = [];
393 $annotations = $annotationReader->getMethodAnnotations($reflectionMethod);
396 $validateAnnotations = array_filter($annotations,
function ($annotation) {
397 return $annotation instanceof Validate;
400 if ($this->isController && $this->methods[$methodName][
'isAction'] && count($validateAnnotations) > 0) {
401 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
403 foreach ($validateAnnotations as $validateAnnotation) {
404 $validatorName = $validateAnnotation->validator;
405 $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validatorName);
407 $argumentValidators[$validateAnnotation->param][] = [
408 'name' => $validatorName,
409 'options' => $validateAnnotation->options,
410 'className' => $validatorObjectName,
415 foreach ($docCommentParser->getTagsValues() as $tag => $values) {
416 if ($tag ===
'cli') {
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()
426 if ($tag ===
'internal' && $reflectionClass->isSubclassOf(\
TYPO3\CMS\
Extbase\Mvc\Controller\CommandController::class)) {
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()
436 if ($tag ===
'ignorevalidation') {
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()
446 if ($tag ===
'flushesCaches') {
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()
456 if ($tag ===
'validate' && $this->isController && $this->methods[$methodName][
'isAction']) {
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()
466 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
468 foreach ($values as $validate) {
469 $methodValidatorDefinition = $validatorResolver->parseValidatorAnnotation($validate);
471 foreach ($methodValidatorDefinition[
'validators'] as
$validator) {
472 $validatorObjectName = $validatorResolver->resolveValidatorObjectName(
$validator[
'validatorName']);
474 $argumentValidators[$methodValidatorDefinition[
'argumentName']][] = [
477 'className' => $validatorObjectName,
482 $this->methods[$methodName][
'tags'][$tag] = array_map(
function ($value) use ($tag) {
488 return $tag ===
'validate' ? $value : ltrim($value,
'$');
491 unset($methodValidatorDefinition);
493 foreach ($annotations as $annotation) {
494 if ($annotation instanceof IgnoreValidation) {
495 $this->methods[$methodName][
'tags'][
'ignorevalidation'][] = $annotation->argumentName;
499 $this->methods[$methodName][
'description'] = $docCommentParser->getDescription();
501 foreach ($reflectionMethod->getParameters() as $parameterPosition => $reflectionParameter) {
504 $parameterName = $reflectionParameter->getName();
506 $this->methods[$methodName][
'params'][$parameterName] = [];
507 $this->methods[$methodName][
'params'][$parameterName][
'position'] = $parameterPosition;
508 $this->methods[$methodName][
'params'][$parameterName][
'byReference'] = $reflectionParameter->isPassedByReference();
509 $this->methods[$methodName][
'params'][$parameterName][
'array'] = $reflectionParameter->isArray();
510 $this->methods[$methodName][
'params'][$parameterName][
'optional'] = $reflectionParameter->isOptional();
511 $this->methods[$methodName][
'params'][$parameterName][
'allowsNull'] = $reflectionParameter->allowsNull();
512 $this->methods[$methodName][
'params'][$parameterName][
'class'] =
null;
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;
518 $this->methods[$methodName][
'params'][$parameterName][
'dependency'] =
null;
519 $this->methods[$methodName][
'params'][$parameterName][
'validators'] = [];
521 if ($reflectionParameter->isDefaultValueAvailable()) {
522 $this->methods[$methodName][
'params'][$parameterName][
'default'] = $reflectionParameter->getDefaultValue();
523 $this->methods[$methodName][
'params'][$parameterName][
'defaultValue'] = $reflectionParameter->getDefaultValue();
526 if (($reflectionType = $reflectionParameter->getType()) instanceof \ReflectionNamedType) {
527 $this->methods[$methodName][
'params'][$parameterName][
'type'] = $reflectionType->getName();
528 $this->methods[$methodName][
'params'][$parameterName][
'nullable'] = $reflectionType->allowsNull();
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(),
'\\');
535 $methodTagsAndValues = $this->methods[$methodName][
'tags'];
536 if (isset($methodTagsAndValues[
'param'][$parameterPosition])) {
537 $explodedParameters = explode(
' ', $methodTagsAndValues[
'param'][$parameterPosition]);
538 if (count($explodedParameters) >= 2) {
544 $this->methods[$methodName][
'params'][$parameterName][
'type'] = ltrim($typeInfo[
'type'],
'\\');
546 $this->methods[$methodName][
'params'][$parameterName][
'type'] = ltrim($explodedParameters[0],
'\\');
553 if ($reflectionParameter->getClass() instanceof \ReflectionClass
554 && ($reflectionMethod->isConstructor() || $this->hasInjectMethodName($reflectionMethod))
556 $this->methods[$methodName][
'params'][$parameterName][
'dependency'] = $reflectionParameter->getClass()->getName();
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.',
568 $this->methods[$methodName][
'params'][$parameterName][
'validators'] = $argumentValidators[$parameterName];
569 unset($argumentValidators[$parameterName]);
574 foreach ($argumentValidators as $parameterName => $validators) {
575 $validatorNames = array_column($validators,
'name');
577 throw new InvalidValidationConfigurationException(
578 'Invalid validate annotation in ' . $this->className .
'->' . $methodName .
'(): The following validators have been defined for missing param "$' . $parameterName .
'": ' . implode(
', ', $validatorNames),
584 $this->methods[$methodName][
'injectMethod'] =
false;
586 && count($this->methods[$methodName][
'params']) === 1
587 && reset($this->methods[$methodName][
'params'])[
'dependency'] !==
null
589 $this->methods[$methodName][
'injectMethod'] =
true;
590 $this->injectMethods[] = $methodName;
614 public function addProperty($name, $type, $lazy =
false, $cascade =
'')
617 'This method will be removed in TYPO3 v10.0, properties will be automatically added on ClassSchema construction.',
621 $this->properties[$name] = [
622 'type' => $type[
'type'],
623 'elementType' => $type[
'elementType'],
625 'cascade' => $cascade
638 return isset($this->properties[$propertyName]) && is_array($this->properties[$propertyName])
639 ? $this->properties[$propertyName]
663 'This method will be removed in TYPO3 v10.0, modelType will be automatically set on ClassSchema construction.',
666 if ($modelType < self::MODELTYPE_ENTITY || $modelType > self::MODELTYPE_VALUEOBJECT) {
667 throw new \InvalidArgumentException(
'"' .
$modelType .
'" is an invalid model type.', 1212519195);
681 'This method will be removed in TYPO3 v10.0.',
695 $this->aggregateRoot = $isRoot;
717 return array_key_exists($propertyName, $this->properties);
730 'Tagging properties with @uuid is deprecated and will be removed in TYPO3 v10.0.',
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);
736 $this->uuidPropertyName = $propertyName;
748 'Tagging properties with @uuid is deprecated and will be removed in TYPO3 v10.0.',
766 'Tagging properties with @identity is deprecated and will be removed in TYPO3 v10.0.',
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);
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);
775 $this->identityProperties[$propertyName] = $this->properties[$propertyName][
'type'];
788 'Tagging properties with @identity is deprecated and will be removed in TYPO3 v10.0.',
799 return isset($this->methods[
'__construct']);
806 public function getMethod(
string $name): array
808 return $this->methods[$name] ?? [];
825 $methodName = $reflectionMethod->getName();
826 if ($methodName ===
'injectSettings' || !$reflectionMethod->isPublic()) {
831 strpos($reflectionMethod->getName(),
'inject') === 0
854 return $this->modelType === static::MODELTYPE_ENTITY;
863 return $this->modelType === static::MODELTYPE_VALUEOBJECT;
878 public function hasMethod(
string $methodName): bool
880 return isset($this->methods[$methodName]);
896 return count($this->injectProperties) > 0;
904 return count($this->injectMethods) > 0;
913 foreach ($this->injectMethods as $injectMethodName) {
914 $injectMethods[$injectMethodName] = reset($this->methods[$injectMethodName][
'params'])[
'dependency'];
926 foreach ($this->injectProperties as $injectPropertyName) {
927 $injectProperties[$injectPropertyName] = $this->properties[$injectPropertyName][
'annotations'][
'dependency'];
942 return $this->methods[
'__construct'][
'params'];