Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
153
src/Framework/Database/Serialization/EntitySerializer.php
Normal file
153
src/Framework/Database/Serialization/EntitySerializer.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database\Serialization;
|
||||
|
||||
use App\Framework\Database\Metadata\MetadataRegistry;
|
||||
use App\Framework\Database\TypeConverter;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Serializer for database entities with support for complex types
|
||||
*/
|
||||
final class EntitySerializer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MetadataRegistry $metadataRegistry,
|
||||
private readonly TypeConverter $typeConverter
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize entity to array format suitable for caching
|
||||
*/
|
||||
public function serialize(object $entity): array
|
||||
{
|
||||
$entityClass = $entity::class;
|
||||
$metadata = $this->metadataRegistry->getMetadata($entityClass);
|
||||
$reflection = new ReflectionClass($entityClass);
|
||||
|
||||
$data = [
|
||||
'__class' => $entityClass,
|
||||
'__properties' => [],
|
||||
];
|
||||
|
||||
foreach ($metadata->getProperties() as $propertyMetadata) {
|
||||
$property = $reflection->getProperty($propertyMetadata->name);
|
||||
|
||||
$value = $property->getValue($entity);
|
||||
|
||||
// Convert to database format for consistent serialization
|
||||
$convertedValue = $this->typeConverter->convertToDatabaseValue(
|
||||
$value,
|
||||
$propertyMetadata->type
|
||||
);
|
||||
|
||||
$data['__properties'][$propertyMetadata->name] = [
|
||||
'value' => $convertedValue,
|
||||
'type' => $propertyMetadata->type,
|
||||
'nullable' => $propertyMetadata->nullable,
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize array data back to entity object
|
||||
*/
|
||||
public function deserialize(array $data, string $expectedClass): object
|
||||
{
|
||||
if (! isset($data['__class']) || $data['__class'] !== $expectedClass) {
|
||||
throw new SerializationException("Serialized data does not match expected class {$expectedClass}");
|
||||
}
|
||||
|
||||
if (! isset($data['__properties'])) {
|
||||
throw new SerializationException("Invalid serialized entity data - missing properties");
|
||||
}
|
||||
|
||||
$reflection = new ReflectionClass($expectedClass);
|
||||
$entity = $reflection->newInstanceWithoutConstructor();
|
||||
|
||||
foreach ($data['__properties'] as $propertyName => $propertyData) {
|
||||
if (! $reflection->hasProperty($propertyName)) {
|
||||
continue; // Skip properties that no longer exist
|
||||
}
|
||||
|
||||
$property = $reflection->getProperty($propertyName);
|
||||
|
||||
// Convert from database format back to PHP value
|
||||
$value = $this->typeConverter->convertToPHPValue(
|
||||
$propertyData['value'],
|
||||
$propertyData['type']
|
||||
);
|
||||
|
||||
$property->setValue($entity, $value);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize array of entities
|
||||
*/
|
||||
public function serializeCollection(array $entities): array
|
||||
{
|
||||
return array_map([$this, 'serialize'], $entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize array of entities
|
||||
*/
|
||||
public function deserializeCollection(array $serializedEntities, string $entityClass): array
|
||||
{
|
||||
return array_map(
|
||||
fn (array $data) => $this->deserialize($data, $entityClass),
|
||||
$serializedEntities
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache-friendly hash of entity for versioning
|
||||
*/
|
||||
public function getEntityHash(object $entity): string
|
||||
{
|
||||
$serialized = $this->serialize($entity);
|
||||
|
||||
// Remove class info for hash calculation
|
||||
unset($serialized['__class']);
|
||||
|
||||
return md5(serialize($serialized));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if serialized data is compatible with current class structure
|
||||
*/
|
||||
public function isCompatible(array $data, string $entityClass): bool
|
||||
{
|
||||
if (! isset($data['__class']) || $data['__class'] !== $entityClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($data['__properties'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$metadata = $this->metadataRegistry->getMetadata($entityClass);
|
||||
$reflection = new ReflectionClass($entityClass);
|
||||
|
||||
// Check if all required properties exist
|
||||
foreach ($metadata->getProperties() as $propertyMetadata) {
|
||||
if (! $propertyMetadata->nullable && ! isset($data['__properties'][$propertyMetadata->name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $reflection->hasProperty($propertyMetadata->name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database\Serialization;
|
||||
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class SerializationException extends FrameworkException
|
||||
{
|
||||
public static function invalidData(string $reason): self
|
||||
{
|
||||
return new self(
|
||||
message: "Invalid serialized data: {$reason}",
|
||||
context: ExceptionContext::forOperation('serialization_decode', 'Database')
|
||||
->withData(['reason' => $reason])
|
||||
);
|
||||
}
|
||||
|
||||
public static function classMismatch(string $expected, string $actual): self
|
||||
{
|
||||
return new self(
|
||||
message: "Class mismatch: expected {$expected}, got {$actual}",
|
||||
context: ExceptionContext::forOperation('serialization_class_match', 'Database')
|
||||
->withData([
|
||||
'expected_class' => $expected,
|
||||
'actual_class' => $actual,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public static function missingProperty(string $class, string $property): self
|
||||
{
|
||||
return new self(
|
||||
message: "Missing required property {$property} in class {$class}",
|
||||
context: ExceptionContext::forOperation('serialization_property_check', 'Database')
|
||||
->withData([
|
||||
'class' => $class,
|
||||
'missing_property' => $property,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user