TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:15 +02:00
commit 6830982e7d
295 changed files with 31995 additions and 0 deletions
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception;
/**
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class NoPropertyTypesException extends \LogicException
{
public static function create(string $className, string $propertyName): NoPropertyTypesException
{
return new self(
'Property ' . $className . '::$' . $propertyName . ' does not have any types defined',
1660215606
);
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception;
class NoSuchMethodException extends \Exception
{
public static function create(string $className, string $methodName): NoSuchMethodException
{
return new self(
'Method ' . $className . '::' . $methodName . ' does not exist',
1547373924
);
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception;
class NoSuchMethodParameterException extends \Exception
{
/**
* @param string $parameterName
*/
public static function createForParameterName(string $className, string $methodName, $parameterName): NoSuchMethodParameterException
{
return new self(
'Method parameter ' . $className . '::' . $methodName . '($' . $parameterName . ') does not exist',
1547375654
);
}
public static function createForParameterPosition(string $className, string $methodName, int $position): NoSuchMethodParameterException
{
return new self(
'Method parameter #' . $position . ' of method ' . $className . '::' . $methodName . ' does not exist',
1547459332
);
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception;
class NoSuchPropertyException extends \Exception
{
public static function create(string $className, string $propertyName): NoSuchPropertyException
{
return new self(
'Property ' . $className . '::$' . $propertyName . ' does not exist',
1546975326
);
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema;
use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchMethodParameterException;
/**
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class Method
{
private array $definition;
private array $parameters = [];
public function __construct(
private readonly string $name,
array $definition,
private readonly string $className,
) {
$defaults = [
'params' => [],
'public' => false,
'protected' => false,
'private' => false,
];
foreach ($defaults as $key => $defaultValue) {
if (!isset($definition[$key])) {
$definition[$key] = $defaultValue;
}
}
$this->definition = $definition;
foreach ($this->definition['params'] as $parameterName => $parameterDefinition) {
$this->parameters[$parameterName] = new MethodParameter($parameterName, $parameterDefinition);
}
}
public function getName(): string
{
return $this->name;
}
/**
* @return array|MethodParameter[]
*/
public function getParameters(): array
{
return $this->parameters;
}
/**
* @throws NoSuchMethodParameterException
*/
public function getParameter(string $parameterName): MethodParameter
{
if (!isset($this->parameters[$parameterName])) {
throw NoSuchMethodParameterException::createForParameterName(
$this->className,
$this->name,
$parameterName
);
}
return $this->parameters[$parameterName];
}
public function isPublic(): bool
{
return $this->definition['public'];
}
public function isProtected(): bool
{
return $this->definition['protected'];
}
public function isPrivate(): bool
{
return $this->definition['private'];
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema;
/**
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class MethodParameter
{
private array $definition;
public function __construct(
private readonly string $name,
array $definition,
) {
$defaults = [
'type' => null,
'array' => false,
'optional' => false,
'hasDefaultValue' => false,
'defaultValue' => null,
'ignoreValidation' => false,
'validators' => [],
];
foreach ($defaults as $key => $defaultValue) {
if (!isset($definition[$key])) {
$definition[$key] = $defaultValue;
}
}
$this->definition = $definition;
}
public function getName(): string
{
return $this->name;
}
public function getType(): ?string
{
return $this->definition['type'];
}
public function isArray(): bool
{
return $this->definition['array'];
}
public function hasDefaultValue(): bool
{
return $this->definition['hasDefaultValue'];
}
/**
* @return mixed
*/
public function getDefaultValue()
{
return $this->definition['defaultValue'];
}
public function getValidators(): array
{
return $this->definition['validators'];
}
public function ignoreValidation(): bool
{
return $this->definition['ignoreValidation'];
}
public function isOptional(): bool
{
return $this->definition['optional'];
}
}
+228
View File
@@ -0,0 +1,228 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\IntersectionType;
use Symfony\Component\TypeInfo\Type\NullableType;
use Symfony\Component\TypeInfo\Type\UnionType;
use Symfony\Component\TypeInfo\TypeIdentifier;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* @phpstan-type PropertyDefinitionSpec array{
* 'c': null|string,
* 'f': null|array<string, mixed>,
* 't': null|Type,
* 'v': list<array>,
* 'propertyCharacteristicsBit'?: int
* }
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class Property
{
/**
* @var PropertyDefinitionSpec
*/
private array $definition;
private PropertyCharacteristics $characteristics;
/**
* @param PropertyDefinitionSpec $definition
*/
public function __construct(
private readonly string $name,
array $definition
) {
$this->characteristics = new PropertyCharacteristics($definition['propertyCharacteristicsBit']);
unset($definition['propertyCharacteristicsBit']);
$defaults = [
'c' => null, // cascade
'f' => null, // file upload
't' => null, // type
'v' => [], // validators
];
foreach ($defaults as $key => $defaultValue) {
if (!isset($definition[$key])) {
$definition[$key] = $defaultValue;
}
}
$this->definition = $definition;
}
public function getName(): string
{
return $this->name;
}
/**
* Returns the types (string, integer, ...) set by the `@var` doc comment and php property type declarations
*
* Returns empty array if types could not be evaluated
*
* @return list<TypeAdapter>
*/
public function getTypes(): array
{
$type = $this->getType();
if ($type === null) {
return [];
}
if ($type instanceof BuiltinType && $type->getTypeIdentifier() === TypeIdentifier::MIXED) {
return [];
}
// NullableType extends UnionType, check first
if ($type instanceof NullableType) {
$inner = $type->getWrappedType();
if ($inner instanceof UnionType) {
return array_map(
static fn(Type $t) => new TypeAdapter($t, forceNullable: true),
$inner->getTypes()
);
}
if ($inner instanceof IntersectionType) {
return array_map(
static fn(Type $t) => new TypeAdapter($t, forceNullable: true),
$inner->getTypes()
);
}
return [new TypeAdapter($inner, forceNullable: true)];
}
if ($type instanceof UnionType) {
$members = array_filter(
$type->getTypes(),
static fn(Type $t) => !($t instanceof BuiltinType && $t->getTypeIdentifier() === TypeIdentifier::NULL)
);
return array_values(array_map(
static fn(Type $t) => new TypeAdapter($t),
$members
));
}
if ($type instanceof IntersectionType) {
return array_map(
static fn(Type $t) => new TypeAdapter($t),
$type->getTypes()
);
}
return [new TypeAdapter($type)];
}
/**
* Gets the native `symfony/type-info` type.
*/
public function getType(): ?Type
{
return $this->definition['t'];
}
/**
* Returns the primary type found in a list of types except LazyLoadingProxy
*/
public function getPrimaryType(): ?TypeAdapter
{
$types = $this->getTypes();
$filtered = array_values(array_filter(
$types,
static fn(TypeAdapter $t) => $t->getClassName() !== LazyLoadingProxy::class
));
return $filtered[0] ?? null;
}
public function getPrimaryCollectionValueType(): ?TypeAdapter
{
$primaryType = $this->getPrimaryType();
if ($primaryType === null || !$primaryType->isCollection()) {
return null;
}
return $primaryType->getCollectionValueTypes()[0] ?? null;
}
/**
* @return list<TypeAdapter>
*/
public function getFilteredTypes(callable $callback): array
{
return array_values(array_filter($this->getTypes(), $callback));
}
public function filterLazyLoadingProxyAndLazyObjectStorage(TypeAdapter $type): bool
{
return !in_array((string)$type->getClassName(), [LazyLoadingProxy::class, LazyObjectStorage::class], true);
}
public function isObjectStorageType(): bool
{
$filteredTypes = $this->getFilteredTypes(
static fn(TypeAdapter $type) => in_array((string)$type->getClassName(), [ObjectStorage::class, LazyObjectStorage::class], true)
);
return $filteredTypes !== [];
}
public function isPublic(): bool
{
return $this->characteristics->get(PropertyCharacteristics::VISIBILITY_PUBLIC);
}
public function isProtected(): bool
{
return $this->characteristics->get(PropertyCharacteristics::VISIBILITY_PROTECTED);
}
public function isPrivate(): bool
{
return $this->characteristics->get(PropertyCharacteristics::VISIBILITY_PRIVATE);
}
public function isLazy(): bool
{
return $this->characteristics->get(PropertyCharacteristics::ANNOTATED_LAZY);
}
public function isTransient(): bool
{
return $this->characteristics->get(PropertyCharacteristics::ANNOTATED_TRANSIENT);
}
public function isNullable(): bool
{
$primaryType = $this->getPrimaryType();
return $primaryType === null || $primaryType->isNullable();
}
public function getValidators(): array
{
return $this->definition['v'];
}
public function getFileUpload(): ?array
{
return $this->definition['f'];
}
public function getCascadeValue(): ?string
{
return $this->definition['c'];
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema;
use TYPO3\CMS\Core\Type\BitSet;
/**
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
final class PropertyCharacteristics extends BitSet
{
public const VISIBILITY_PRIVATE = 1 << 0;
public const VISIBILITY_PROTECTED = 1 << 1;
public const VISIBILITY_PUBLIC = 1 << 2;
public const ANNOTATED_LAZY = 1 << 4;
public const ANNOTATED_TRANSIENT = 1 << 5;
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Reflection\ClassSchema;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\CollectionType;
use Symfony\Component\TypeInfo\Type\NullableType;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\Type\UnionType;
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
/**
* Adapter providing the old Symfony PropertyInfo\Type API on top of the new TypeInfo\Type.
*
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
final readonly class TypeAdapter
{
public function __construct(private Type $type, private bool $forceNullable = false) {}
public function getBuiltinType(): string
{
return $this->resolveBuiltinType($this->type);
}
public function getClassName(): ?string
{
return $this->resolveClassName($this->type);
}
public function isCollection(): bool
{
$type = $this->type instanceof NullableType ? $this->type->getWrappedType() : $this->type;
return $type instanceof CollectionType;
}
/**
* @return list<TypeAdapter>
*/
public function getCollectionKeyTypes(): array
{
$type = $this->type instanceof NullableType ? $this->type->getWrappedType() : $this->type;
if ($type instanceof CollectionType) {
return $this->decomposeType($type->getCollectionKeyType());
}
return [];
}
/**
* @return list<TypeAdapter>
*/
public function getCollectionValueTypes(): array
{
$type = $this->type instanceof NullableType ? $this->type->getWrappedType() : $this->type;
if ($type instanceof CollectionType) {
return $this->decomposeType($type->getCollectionValueType());
}
return [];
}
public function isNullable(): bool
{
return $this->forceNullable || $this->type->isNullable();
}
/**
* @return list<TypeAdapter>
*/
private function decomposeType(Type $type): array
{
if ($type instanceof UnionType) {
return array_map(
static fn(Type $t) => new self($t),
$type->getTypes()
);
}
return [new self($type)];
}
private function resolveBuiltinType(Type $type): string
{
if ($type instanceof BuiltinType) {
return $type->getTypeIdentifier()->value;
}
if ($type instanceof ObjectType) {
return 'object';
}
if ($type instanceof WrappingTypeInterface) {
return $this->resolveBuiltinType($type->getWrappedType());
}
return 'object';
}
private function resolveClassName(Type $type): ?string
{
if ($type instanceof ObjectType) {
return $type->getClassName();
}
if ($type instanceof WrappingTypeInterface) {
return $this->resolveClassName($type->getWrappedType());
}
return null;
}
}