Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ jobs:
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
ini-values: memory_limit=-1, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M
ini-values: memory_limit=-1, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M, extension=deepclone
tools: pie

- name: "Install symfony/deepclone"
run: |
sudo pie install symfony/deepclone

- name: "Checkout base"
uses: actions/checkout@v6
Expand Down
3 changes: 2 additions & 1 deletion src/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
namespace Patchlevel\Hydrator;

use Patchlevel\Hydrator\Guesser\BuiltInGuesser;
use Patchlevel\Hydrator\Middleware\SymfonyTransformMiddleware;
use Patchlevel\Hydrator\Middleware\TransformMiddleware;

/** @experimental */
final class CoreExtension implements Extension
{
public function configure(StackHydratorBuilder $builder): void
{
$builder->addMiddleware(new TransformMiddleware(), -64);
$builder->addMiddleware(new SymfonyTransformMiddleware(), -64);
$builder->addGuesser(new BuiltInGuesser(), -64);
}
}
149 changes: 149 additions & 0 deletions src/Middleware/SymfonyTransformMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Middleware;

use Patchlevel\Hydrator\CircularReference;
use Patchlevel\Hydrator\DenormalizationFailure;
use Patchlevel\Hydrator\HydratorWithContext;
use Patchlevel\Hydrator\Metadata\ClassMetadata;
use Patchlevel\Hydrator\NormalizationFailure;
use Patchlevel\Hydrator\Normalizer\NormalizerWithContext;
use Throwable;

use function array_key_exists;
use function array_values;
use function deepclone_hydrate;

Check failure on line 17 in src/Middleware/SymfonyTransformMiddleware.php

View workflow job for this annotation

GitHub Actions / Static Analysis by PHPStan (locked, 8.5, ubuntu-latest)

Used function deepclone_hydrate not found.
use function spl_object_id;

/** @experimental */
final class SymfonyTransformMiddleware implements Middleware
{
/** @var array<int, class-string> */
private array $callStack = [];

/**
* @param ClassMetadata<T> $metadata
* @param array<string, mixed> $data
* @param array<string, mixed> $context
*
* @return T
*
* @template T of object
*/
public function hydrate(ClassMetadata $metadata, array $data, array $context, Stack $stack): object
{
$constructorParameters = null;

$vars = [];

foreach ($metadata->properties() as $propertyMetadata) {
/*
if (!array_key_exists($propertyMetadata->fieldName(), $data)) {
if (!$propertyMetadata->reflection->isPromoted()) {
continue;
}

$constructorParameters ??= $metadata->promotedConstructorDefaults();

if (!array_key_exists($propertyMetadata->propertyName, $constructorParameters)) {
continue;
}

$vars[$propertyMetadata->propertyName] = $constructorParameters[$propertyMetadata->propertyName]->getDefaultValue();

$propertyMetadata->setValue(
$object,
$constructorParameters[$propertyMetadata->propertyName]->getDefaultValue(),
);

continue;
}
*/

if ($propertyMetadata->normalizer) {
try {
if ($propertyMetadata->normalizer instanceof NormalizerWithContext) {
/** @psalm-suppress MixedAssignment */
$vars[$propertyMetadata->propertyName] = $propertyMetadata->normalizer->denormalize($data[$propertyMetadata->fieldName], $context);
} else {
/** @psalm-suppress MixedAssignment */
$vars[$propertyMetadata->propertyName] = $propertyMetadata->normalizer->denormalize($data[$propertyMetadata->fieldName]);
}
} catch (Throwable $e) {
throw new DenormalizationFailure(
$metadata->className,
$propertyMetadata->propertyName,
$propertyMetadata->normalizer::class,
$e,
);
}
} else {
$vars[$propertyMetadata->propertyName] = $data[$propertyMetadata->fieldName];
}
}

return deepclone_hydrate(

Check failure on line 87 in src/Middleware/SymfonyTransformMiddleware.php

View workflow job for this annotation

GitHub Actions / Static Analysis by PHPStan (locked, 8.5, ubuntu-latest)

Method Patchlevel\Hydrator\Middleware\SymfonyTransformMiddleware::hydrate() should return T of object but returns mixed.

Check failure on line 87 in src/Middleware/SymfonyTransformMiddleware.php

View workflow job for this annotation

GitHub Actions / Static Analysis by PHPStan (locked, 8.5, ubuntu-latest)

Function deepclone_hydrate not found.
$context[HydratorWithContext::OBJECT_TO_POPULATE] ?? $metadata->className(),
$vars,
);
}

/**
* @param array<string, mixed> $context
*
* @return array<string, mixed>
*/
public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array
{
$objectId = spl_object_id($object);

if (array_key_exists($objectId, $this->callStack)) {
$references = array_values($this->callStack);
$references[] = $object::class;

throw new CircularReference($references);
}

$this->callStack[$objectId] = $object::class;

try {
$data = [];

foreach ($metadata->properties as $propertyMetadata) {
if ($propertyMetadata->normalizer) {
try {
if ($propertyMetadata->normalizer instanceof NormalizerWithContext) {
/** @psalm-suppress MixedAssignment */
$data[$propertyMetadata->fieldName] = $propertyMetadata->normalizer->normalize(
$propertyMetadata->getValue($object),
$context,
);
} else {
/** @psalm-suppress MixedAssignment */
$data[$propertyMetadata->fieldName] = $propertyMetadata->normalizer->normalize(
$propertyMetadata->getValue($object),
);
}
} catch (CircularReference $e) {
throw $e;
} catch (Throwable $e) {
throw new NormalizationFailure(
$object::class,
$propertyMetadata->propertyName,
$propertyMetadata->normalizer::class,
$e,
);
}
} else {
$data[$propertyMetadata->fieldName] = $propertyMetadata->getValue($object);
}
}
} finally {
unset($this->callStack[$objectId]);
}

return $data;
}
}
3 changes: 2 additions & 1 deletion src/StackHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Patchlevel\Hydrator\Metadata\MetadataFactory;
use Patchlevel\Hydrator\Middleware\Middleware;
use Patchlevel\Hydrator\Middleware\Stack;
use Patchlevel\Hydrator\Middleware\SymfonyTransformMiddleware;
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
use ReflectionClass;
Expand All @@ -27,7 +28,7 @@ final class StackHydrator implements HydratorWithContext
/** @param list<Middleware> $middlewares */
public function __construct(
private readonly MetadataFactory $metadataFactory = new AttributeMetadataFactory(),
private readonly array $middlewares = [new TransformMiddleware()],
private readonly array $middlewares = [new SymfonyTransformMiddleware()],
private readonly bool $defaultLazy = false,
) {
if ($middlewares === []) {
Expand Down
Loading