|
|
|
|
@@ -4,14 +4,15 @@ declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Framework\Database\Metadata;
|
|
|
|
|
|
|
|
|
|
use App\Framework\Database\Attributes\Entity;
|
|
|
|
|
use App\Framework\Core\ValueObjects\ClassName;
|
|
|
|
|
use App\Framework\Database\Attributes\Column;
|
|
|
|
|
use App\Framework\Database\Attributes\Entity;
|
|
|
|
|
use App\Framework\Database\Attributes\Type;
|
|
|
|
|
use App\Framework\Database\Exception\DatabaseException;
|
|
|
|
|
use ReflectionClass;
|
|
|
|
|
use ReflectionProperty;
|
|
|
|
|
use ReflectionParameter;
|
|
|
|
|
use ReflectionNamedType;
|
|
|
|
|
use ReflectionParameter;
|
|
|
|
|
use ReflectionProperty;
|
|
|
|
|
use ReflectionUnionType;
|
|
|
|
|
|
|
|
|
|
final class MetadataExtractor
|
|
|
|
|
@@ -22,7 +23,7 @@ final class MetadataExtractor
|
|
|
|
|
$reflection = new ReflectionClass($entityClass);
|
|
|
|
|
$entityAttribute = $this->getEntityAttribute($reflection);
|
|
|
|
|
|
|
|
|
|
if (!$entityAttribute) {
|
|
|
|
|
if (! $entityAttribute) {
|
|
|
|
|
throw new DatabaseException("Class {$entityClass} is not marked as Entity");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -52,7 +53,7 @@ final class MetadataExtractor
|
|
|
|
|
$properties = [];
|
|
|
|
|
$constructor = $reflection->getConstructor();
|
|
|
|
|
|
|
|
|
|
if (!$constructor) {
|
|
|
|
|
if (! $constructor) {
|
|
|
|
|
return $properties;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -82,6 +83,7 @@ final class MetadataExtractor
|
|
|
|
|
if ($typeAttribute) {
|
|
|
|
|
$relationMetadata = $this->extractRelationMetadata($property, $typeAttribute, $reflection);
|
|
|
|
|
$relations[$property->getName()] = $relationMetadata;
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -91,9 +93,10 @@ final class MetadataExtractor
|
|
|
|
|
$typeName = $propertyType->getName();
|
|
|
|
|
|
|
|
|
|
// Prüfe ob die Typ-Klasse eine Entity ist
|
|
|
|
|
if (class_exists($typeName) && $this->isEntityClass($typeName)) {
|
|
|
|
|
if (! empty($typeName) && ClassName::create($typeName)->exists() && $this->isEntityClass($typeName)) {
|
|
|
|
|
$relationMetadata = $this->createRelationMetadataForEntityProperty($property, $typeName, $reflection);
|
|
|
|
|
$relations[$property->getName()] = $relationMetadata;
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -106,6 +109,7 @@ final class MetadataExtractor
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$classReflection = new \ReflectionClass($className);
|
|
|
|
|
|
|
|
|
|
return $this->getEntityAttribute($classReflection) !== null;
|
|
|
|
|
} catch (\ReflectionException) {
|
|
|
|
|
return false;
|
|
|
|
|
@@ -121,62 +125,62 @@ final class MetadataExtractor
|
|
|
|
|
|
|
|
|
|
$relationType = $this->determineRelationType($property);
|
|
|
|
|
|
|
|
|
|
if($relationType === 'belongsTo') {
|
|
|
|
|
if ($relationType === 'belongsTo') {
|
|
|
|
|
return $this->createBelongsToRelationMetadata($property, $targetClass, $parentReflection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->createHasRelationMetadata($property, $targetClass, $parentReflection, $relationType);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// Foreign Key ist der Primary Key der Ziel-Entity
|
|
|
|
|
$targetReflection = new \ReflectionClass($targetClass);
|
|
|
|
|
$targetEntityAttribute = $this->getEntityAttribute($targetReflection);
|
|
|
|
|
$foreignKey = $targetEntityAttribute?->idColumn ?? 'id';
|
|
|
|
|
/*
|
|
|
|
|
// Foreign Key ist der Primary Key der Ziel-Entity
|
|
|
|
|
$targetReflection = new \ReflectionClass($targetClass);
|
|
|
|
|
$targetEntityAttribute = $this->getEntityAttribute($targetReflection);
|
|
|
|
|
$foreignKey = $targetEntityAttribute?->idColumn ?? 'id';
|
|
|
|
|
|
|
|
|
|
// Local Key ist die entsprechende Spalte in der aktuellen Entity
|
|
|
|
|
// Für "image" Property -> "image_id"
|
|
|
|
|
$localKey = $propertyName . '_id';
|
|
|
|
|
// Local Key ist die entsprechende Spalte in der aktuellen Entity
|
|
|
|
|
// Für "image" Property -> "image_id"
|
|
|
|
|
$localKey = $propertyName . '_id';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Typ-Analyse der Property
|
|
|
|
|
$type = $property->getType();
|
|
|
|
|
$typeInfo = $this->analyzeType($type);
|
|
|
|
|
// Typ-Analyse der Property
|
|
|
|
|
$type = $property->getType();
|
|
|
|
|
$typeInfo = $this->analyzeType($type);
|
|
|
|
|
|
|
|
|
|
return new PropertyMetadata(
|
|
|
|
|
name: $propertyName,
|
|
|
|
|
columnName: '',
|
|
|
|
|
type: $typeInfo['mainType'],
|
|
|
|
|
nullable: $typeInfo['nullable'],
|
|
|
|
|
hasDefault: true,
|
|
|
|
|
defaultValue: null,
|
|
|
|
|
allTypes: $typeInfo['allTypes'],
|
|
|
|
|
primary: false,
|
|
|
|
|
autoIncrement: false,
|
|
|
|
|
isRelation: true,
|
|
|
|
|
relationTargetClass: $targetClass,
|
|
|
|
|
relationForeignKey: $foreignKey,
|
|
|
|
|
relationLocalKey: $localKey,
|
|
|
|
|
relationType: 'one-to-one'
|
|
|
|
|
);*/
|
|
|
|
|
return new PropertyMetadata(
|
|
|
|
|
name: $propertyName,
|
|
|
|
|
columnName: '',
|
|
|
|
|
type: $typeInfo['mainType'],
|
|
|
|
|
nullable: $typeInfo['nullable'],
|
|
|
|
|
hasDefault: true,
|
|
|
|
|
defaultValue: null,
|
|
|
|
|
allTypes: $typeInfo['allTypes'],
|
|
|
|
|
primary: false,
|
|
|
|
|
autoIncrement: false,
|
|
|
|
|
isRelation: true,
|
|
|
|
|
relationTargetClass: $targetClass,
|
|
|
|
|
relationForeignKey: $foreignKey,
|
|
|
|
|
relationLocalKey: $localKey,
|
|
|
|
|
relationType: 'one-to-one'
|
|
|
|
|
);*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function determineRelationType(\ReflectionProperty $property): string
|
|
|
|
|
{
|
|
|
|
|
$propertyType = $property->getType();
|
|
|
|
|
|
|
|
|
|
if($propertyType instanceof \ReflectionNamedType && $propertyType->getName() === 'array') {
|
|
|
|
|
if ($propertyType instanceof \ReflectionNamedType && $propertyType->getName() === 'array') {
|
|
|
|
|
return 'hasMany';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$parentReflection = $property->getDeclaringClass();
|
|
|
|
|
$possibleForeignKeyProperty = $property->getName() . 'Id';
|
|
|
|
|
|
|
|
|
|
if($this->hasConstructorParameter($parentReflection, $possibleForeignKeyProperty)) {
|
|
|
|
|
|
|
|
|
|
if ($this->hasConstructorParameter($parentReflection, $possibleForeignKeyProperty)) {
|
|
|
|
|
return 'belongsTo';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$possibleForeignKeyProperty2 = $property->getName() . '_id';
|
|
|
|
|
if($this->hasConstructorParameter($parentReflection, $possibleForeignKeyProperty2)) {
|
|
|
|
|
if ($this->hasConstructorParameter($parentReflection, $possibleForeignKeyProperty2)) {
|
|
|
|
|
return 'belongsTo';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -186,15 +190,16 @@ final class MetadataExtractor
|
|
|
|
|
private function hasConstructorParameter(ReflectionClass $reflection, string $paramName): bool
|
|
|
|
|
{
|
|
|
|
|
$constructor = $reflection->getConstructor();
|
|
|
|
|
if(!$constructor) {
|
|
|
|
|
if (! $constructor) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach($constructor->getParameters() as $param) {
|
|
|
|
|
if($param->getName() === $paramName) {
|
|
|
|
|
foreach ($constructor->getParameters() as $param) {
|
|
|
|
|
if ($param->getName() === $paramName) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -204,7 +209,7 @@ final class MetadataExtractor
|
|
|
|
|
|
|
|
|
|
$foreignKeyProperty = $this->findForeignKeyProperty($propertyName, $parentReflection);
|
|
|
|
|
|
|
|
|
|
if(!$foreignKeyProperty) {
|
|
|
|
|
if (! $foreignKeyProperty) {
|
|
|
|
|
throw new DatabaseException("Could not find foreign key property for property {$propertyName} in class {$parentReflection->getName()}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -234,7 +239,7 @@ final class MetadataExtractor
|
|
|
|
|
private function findForeignKeyProperty(string $propertyName, ReflectionClass $parentReflection): ?string
|
|
|
|
|
{
|
|
|
|
|
$constructor = $parentReflection->getConstructor();
|
|
|
|
|
if (!$constructor) {
|
|
|
|
|
if (! $constructor) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -287,14 +292,13 @@ final class MetadataExtractor
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function extractRelationMetadata(ReflectionProperty $property, Type $typeAttribute, ReflectionClass $classReflection): PropertyMetadata
|
|
|
|
|
{
|
|
|
|
|
$propertyName = $property->getName();
|
|
|
|
|
|
|
|
|
|
// Ermittle foreign key automatisch falls nicht gesetzt
|
|
|
|
|
$foreignKey = $typeAttribute->foreignKey;
|
|
|
|
|
if (!$foreignKey) {
|
|
|
|
|
if (! $foreignKey) {
|
|
|
|
|
// Konvention: {parent_class}_id
|
|
|
|
|
$shortName = strtolower($classReflection->getShortName());
|
|
|
|
|
$foreignKey = $shortName . '_id';
|
|
|
|
|
@@ -302,7 +306,7 @@ final class MetadataExtractor
|
|
|
|
|
|
|
|
|
|
// Ermittle local key automatisch falls nicht gesetzt
|
|
|
|
|
$localKey = $typeAttribute->localKey;
|
|
|
|
|
if (!$localKey) {
|
|
|
|
|
if (! $localKey) {
|
|
|
|
|
// Verwende ID-Property der Entity
|
|
|
|
|
$entityAttribute = $this->getEntityAttribute($classReflection);
|
|
|
|
|
$localKey = $entityAttribute?->idColumn ?? 'id';
|
|
|
|
|
@@ -383,6 +387,7 @@ final class MetadataExtractor
|
|
|
|
|
try {
|
|
|
|
|
$property = $classReflection->getProperty($paramName);
|
|
|
|
|
$columnAttribute = $this->getColumnAttribute($property);
|
|
|
|
|
|
|
|
|
|
return $columnAttribute?->name ?? $paramName;
|
|
|
|
|
} catch (\ReflectionException) {
|
|
|
|
|
return $paramName;
|
|
|
|
|
@@ -391,11 +396,11 @@ final class MetadataExtractor
|
|
|
|
|
|
|
|
|
|
private function analyzeType(?\ReflectionType $type): array
|
|
|
|
|
{
|
|
|
|
|
if (!$type) {
|
|
|
|
|
if (! $type) {
|
|
|
|
|
return [
|
|
|
|
|
'mainType' => 'mixed',
|
|
|
|
|
'allTypes' => ['mixed'],
|
|
|
|
|
'nullable' => true
|
|
|
|
|
'nullable' => true,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -403,7 +408,7 @@ final class MetadataExtractor
|
|
|
|
|
return [
|
|
|
|
|
'mainType' => $type->getName(),
|
|
|
|
|
'allTypes' => [$type->getName()],
|
|
|
|
|
'nullable' => $type->allowsNull()
|
|
|
|
|
'nullable' => $type->allowsNull(),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -425,38 +430,42 @@ final class MetadataExtractor
|
|
|
|
|
return [
|
|
|
|
|
'mainType' => $types[0] ?? 'mixed',
|
|
|
|
|
'allTypes' => $types,
|
|
|
|
|
'nullable' => $nullable
|
|
|
|
|
'nullable' => $nullable,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'mainType' => 'mixed',
|
|
|
|
|
'allTypes' => ['mixed'],
|
|
|
|
|
'nullable' => true
|
|
|
|
|
'nullable' => true,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getEntityAttribute(ReflectionClass $reflection): ?Entity
|
|
|
|
|
{
|
|
|
|
|
$attributes = $reflection->getAttributes(Entity::class);
|
|
|
|
|
|
|
|
|
|
return $attributes ? $attributes[0]->newInstance() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getColumnAttribute(ReflectionProperty $property): ?Column
|
|
|
|
|
{
|
|
|
|
|
$attributes = $property->getAttributes(Column::class);
|
|
|
|
|
|
|
|
|
|
return $attributes ? $attributes[0]->newInstance() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getTypeAttribute(ReflectionProperty $property): ?Type
|
|
|
|
|
{
|
|
|
|
|
$attributes = $property->getAttributes(Type::class);
|
|
|
|
|
|
|
|
|
|
return $attributes ? $attributes[0]->newInstance() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getTableNameFromClass(string $className): string
|
|
|
|
|
{
|
|
|
|
|
$shortName = new ReflectionClass($className)->getShortName();
|
|
|
|
|
|
|
|
|
|
return strtolower($shortName) . 's';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -492,7 +501,7 @@ final class MetadataExtractor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Wenn nichts passt: Erste Property (nicht ideal, aber besser als nichts)
|
|
|
|
|
if (!empty($properties)) {
|
|
|
|
|
if (! empty($properties)) {
|
|
|
|
|
return array_key_first($properties);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|