chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View File

@@ -0,0 +1,469 @@
<?php
namespace App\Framework\Database;
use App\Framework\Attributes\Singleton;
use App\Framework\Database\Metadata\EntityMetadata;
use App\Framework\Database\Metadata\MetadataRegistry;
#[Singleton]
final readonly class EntityManager
{
private Hydrator $hydrator;
public function __construct(
private DatabaseManager $databaseManager,
private MetadataRegistry $metadataRegistry,
private TypeConverter $typeConverter,
private IdentityMap $identityMap,
private LazyLoader $lazyLoader
) {
$this->hydrator = new Hydrator($this->typeConverter, $this);
}
/**
* Findet Entity - standardmäßig lazy loading
*/
public function find(string $entityClass, mixed $id): ?object
{
// Prüfe Identity Map zuerst
if ($this->identityMap->has($entityClass, $id)) {
return $this->identityMap->get($entityClass, $id);
}
return $this->findWithLazyLoading($entityClass, $id);
}
/**
* Findet Entity und lädt sie sofort (eager loading)
*/
public function findEager(string $entityClass, mixed $id): ?object
{
// Prüfe Identity Map zuerst
if ($this->identityMap->has($entityClass, $id)) {
$entity = $this->identityMap->get($entityClass, $id);
// Falls es ein Ghost ist, initialisiere es
if ($this->isLazyGhost($entity)) {
$this->initializeLazyObject($entity);
}
return $entity;
}
$metadata = $this->metadataRegistry->getMetadata($entityClass);
$query = "SELECT * FROM {$metadata->tableName} WHERE {$metadata->idColumn} = ?";
$result = $this->databaseManager->getConnection()->query($query, [$id]);
$data = $result->fetch();
if (!$data) {
return null;
}
$entity = $this->hydrator->hydrate($metadata, $data);
// In Identity Map speichern
$this->identityMap->set($entityClass, $id, $entity);
return $entity;
}
/**
* Interne Methode für Lazy Loading
*/
private function findWithLazyLoading(string $entityClass, mixed $id): ?object
{
// Prüfe ob Entity existiert (schneller Check)
$metadata = $this->metadataRegistry->getMetadata($entityClass);
$query = "SELECT {$metadata->idColumn} FROM {$metadata->tableName} WHERE {$metadata->idColumn} = ?";
$result = $this->databaseManager->getConnection()->query($query, [$id]);
if (!$result->fetch()) {
return null;
}
// Erstelle Lazy Ghost
return $this->lazyLoader->createLazyGhost($metadata, $id);
}
/**
* Referenz auf Entity (ohne Existenz-Check)
*/
public function getReference(string $entityClass, mixed $id): object
{
// Prüfe Identity Map
if ($this->identityMap->has($entityClass, $id)) {
return $this->identityMap->get($entityClass, $id);
}
$metadata = $this->metadataRegistry->getMetadata($entityClass);
return $this->lazyLoader->createLazyGhost($metadata, $id);
}
/**
* Findet alle Entities - standardmäßig lazy
*/
public function findAll(string $entityClass): array
{
return $this->findAllLazy($entityClass);
}
/**
* Findet alle Entities und lädt sie sofort
*/
public function findAllEager(string $entityClass): array
{
$metadata = $this->metadataRegistry->getMetadata($entityClass);
$query = "SELECT * FROM {$metadata->tableName}";
$result = $this->databaseManager->getConnection()->query($query);
$entities = [];
foreach ($result->fetchAll() as $data) {
// Prüfe Identity Map für jede Entity
$idValue = $data[$metadata->idColumn];
if ($this->identityMap->has($entityClass, $idValue)) {
$entity = $this->identityMap->get($entityClass, $idValue);
// Falls Ghost, initialisiere es für eager loading
if ($this->isLazyGhost($entity)) {
$this->initializeLazyObject($entity);
}
$entities[] = $entity;
} else {
$entity = $this->hydrator->hydrate($metadata, $data);
$this->identityMap->set($entityClass, $idValue, $entity);
$entities[] = $entity;
}
}
return $entities;
}
/**
* Interne Methode für lazy findAll
*/
private function findAllLazy(string $entityClass): array
{
$metadata = $this->metadataRegistry->getMetadata($entityClass);
// Nur IDs laden
$query = "SELECT {$metadata->idColumn} FROM {$metadata->tableName}";
$result = $this->databaseManager->getConnection()->query($query);
$entities = [];
foreach ($result->fetchAll() as $data) {
$idValue = $data[$metadata->idColumn];
if ($this->identityMap->has($entityClass, $idValue)) {
$entities[] = $this->identityMap->get($entityClass, $idValue);
} else {
$entities[] = $this->lazyLoader->createLazyGhost($metadata, $idValue);
}
}
return $entities;
}
/**
* Findet Entities nach Kriterien
*/
public function findBy(string $entityClass, array $criteria, ?array $orderBy = null, ?int $limit = null): array
{
$metadata = $this->metadataRegistry->getMetadata($entityClass);
$query = "SELECT {$metadata->idColumn} FROM {$metadata->tableName}";
$params = [];
if (!empty($criteria)) {
$conditions = [];
foreach ($criteria as $field => $value) {
$columnName = $metadata->getColumnName($field);
$conditions[] = "{$columnName} = ?";
$params[] = $value;
}
$query .= " WHERE " . implode(' AND ', $conditions);
}
if ($orderBy) {
$orderClauses = [];
foreach ($orderBy as $field => $direction) {
$columnName = $metadata->getColumnName($field);
$orderClauses[] = "{$columnName} " . strtoupper($direction);
}
$query .= " ORDER BY " . implode(', ', $orderClauses);
}
if ($limit) {
$query .= " LIMIT {$limit}";
}
$result = $this->databaseManager->getConnection()->query($query, $params);
$entities = [];
foreach ($result->fetchAll() as $data) {
$idValue = $data[$metadata->idColumn];
$entities[] = $this->find($entityClass, $idValue); // Nutzt lazy loading
}
return $entities;
}
/**
* Findet eine Entity nach Kriterien
*/
public function findOneBy(string $entityClass, array $criteria): ?object
{
$results = $this->findBy($entityClass, $criteria, limit: 1);
return $results[0] ?? null;
}
/**
* Utility Methods
*/
public function detach(object $entity): void
{
$metadata = $this->metadataRegistry->getMetadata($entity::class);
// ID der Entity ermitteln
$constructor = $metadata->reflection->getConstructor();
if ($constructor) {
foreach ($constructor->getParameters() as $param) {
$paramName = $param->getName();
$propertyMetadata = $metadata->getProperty($paramName);
if ($propertyMetadata && $propertyMetadata->columnName === $metadata->idColumn) {
try {
$property = $metadata->reflection->getProperty($paramName);
$id = $property->getValue($entity);
$this->identityMap->remove($entity::class, $id);
break;
} catch (\ReflectionException) {
// Property nicht gefunden
}
}
}
}
}
public function clear(): void
{
$this->identityMap->clear();
}
public function getIdentityMapStats(): array
{
return $this->identityMap->getStats();
}
public function isLazyGhost(object $entity): bool
{
return $this->lazyLoader->isLazyGhost($entity);
}
public function initializeLazyObject(object $entity): void
{
$this->lazyLoader->initializeLazyObject($entity);
}
public function getMetadata(string $entityClass): EntityMetadata
{
return $this->metadataRegistry->getMetadata($entityClass);
}
/**
* Generiert eine einzigartige ID für Entities
*/
public function generateId(): string
{
return IdGenerator::generate();
}
/**
* Speichert eine Entity (INSERT oder UPDATE)
*/
public function save(object $entity): object
{
$metadata = $this->metadataRegistry->getMetadata($entity::class);
$idProperty = $metadata->reflection->getProperty($metadata->idProperty);
$id = $idProperty->getValue($entity);
// Prüfe ob Entity bereits existiert
if ($this->exists($entity::class, $id)) {
return $this->update($entity);
} else {
return $this->insert($entity);
}
}
/**
* Prüft ob eine Entity mit der angegebenen ID existiert
*/
public function exists(string $entityClass, mixed $id): bool
{
$metadata = $this->metadataRegistry->getMetadata($entityClass);
$query = "SELECT 1 FROM {$metadata->tableName} WHERE {$metadata->idColumn} = ? LIMIT 1";
$result = $this->databaseManager->getConnection()->query($query, [$id]);
return (bool) $result->fetch();
}
/**
* Fügt eine neue Entity ein (INSERT)
*/
public function insert(object $entity): object
{
$metadata = $this->metadataRegistry->getMetadata($entity::class);
// Columnnamen und Values sammeln
$columns = [];
$values = [];
$params = [];
foreach ($metadata->properties as $propertyName => $propertyMetadata) {
// ID Property überspringen wenn auto-increment
if ($propertyName === $metadata->idProperty && $propertyMetadata->autoIncrement) {
continue;
}
// Property-Wert auslesen
$property = $metadata->reflection->getProperty($propertyName);
if(!$property->isInitialized($entity)) {
continue;
}
$columns[] = $propertyMetadata->columnName;
$values[] = '?';
$params[] = $property->getValue($entity);
}
// INSERT Query bauen
$query = "INSERT INTO {$metadata->tableName} (" . implode(', ', $columns) . ") "
. "VALUES (" . implode(', ', $values) . ")";
// Query ausführen
$result = $this->databaseManager->getConnection()->execute($query, $params);
// In Identity Map speichern
$idProperty = $metadata->reflection->getProperty($metadata->idProperty);
$id = $idProperty->getValue($entity);
$this->identityMap->set($entity::class, $id, $entity);
return $entity;
}
/**
* Aktualisiert eine vorhandene Entity (UPDATE)
*/
public function update(object $entity): object
{
$metadata = $this->metadataRegistry->getMetadata($entity::class);
// SET-Clause und Params aufbauen
$setClause = [];
$params = [];
foreach ($metadata->properties as $propertyName => $propertyMetadata) {
// Relations beim Update ignorieren
if($propertyMetadata->isRelation) {
continue;
}
// ID überspringen (wird nicht aktualisiert)
if ($propertyName === $metadata->idProperty) {
continue;
}
$setClause[] = "{$propertyMetadata->columnName} = ?";
// Property-Wert auslesen
$property = $metadata->reflection->getProperty($propertyName);
$params[] = $property->getValue($entity);
}
// ID für WHERE Clause hinzufügen
$idProperty = $metadata->reflection->getProperty($metadata->idProperty);
$id = $idProperty->getValue($entity);
$params[] = $id;
// UPDATE Query bauen
$query = "UPDATE {$metadata->tableName} SET " . implode(', ', $setClause)
. " WHERE {$metadata->idColumn} = ?";
// Query ausführen
$result = $this->databaseManager->getConnection()->query($query, $params);
// Identity Map aktualisieren
$this->identityMap->set($entity::class, $id, $entity);
return $entity;
}
/**
* Löscht eine Entity
*/
public function delete(object $entity): void
{
$metadata = $this->metadataRegistry->getMetadata($entity::class);
// ID auslesen
$idProperty = $metadata->reflection->getProperty($metadata->idProperty);
$id = $idProperty->getValue($entity);
// DELETE Query ausführen
$query = "DELETE FROM {$metadata->tableName} WHERE {$metadata->idColumn} = ?";
$this->databaseManager->getConnection()->query($query, [$id]);
// Aus Identity Map entfernen
$this->identityMap->remove($entity::class, $id);
}
/**
* Speichert mehrere Entities auf einmal
*/
public function saveAll(object ...$entities): array
{
$result = [];
foreach ($entities as $entity) {
$result[] = $this->save($entity);
}
return $result;
}
/**
* Führt eine Funktion in einer Transaktion aus
*/
public function transaction(callable $callback): mixed
{
$connection = $this->databaseManager->getConnection();
// Wenn bereits in einer Transaktion, führe Callback direkt aus
if ($connection->inTransaction()) {
return $callback($this);
}
// Neue Transaktion starten
$connection->beginTransaction();
try {
$result = $callback($this);
$connection->commit();
return $result;
} catch (\Throwable $e) {
$connection->rollback();
throw $e;
}
}
}