Skip to content

Commit 2d010fe

Browse files
committed
more performance improvements
1 parent ee1b94a commit 2d010fe

File tree

5 files changed

+181
-76
lines changed

5 files changed

+181
-76
lines changed

src/Cryptography/SensitiveDataPayloadCryptographer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,13 @@ public function decrypt(ClassMetadata $metadata, array $data): array
120120
private function subjectId(PropertyMetadata $propertyMetadata, ClassMetadata $metadata, array $data): string
121121
{
122122
if (!$propertyMetadata->isSensitiveData()) {
123-
throw new NotSensitiveData($metadata->className(), $propertyMetadata->propertyName());
123+
throw new NotSensitiveData($metadata->className(), $propertyMetadata->propertyName);
124124
}
125125

126126
$sensitiveDataSubjectIdName = $propertyMetadata->sensitiveDataSubjectIdName;
127127

128128
if (!$metadata->hasSubjectIdIdentifier($sensitiveDataSubjectIdName)) {
129-
throw new MissingSubjectId($metadata->className(), $propertyMetadata->propertyName());
129+
throw new MissingSubjectId($metadata->className(), $propertyMetadata->propertyName);
130130
}
131131

132132
$fieldName = $metadata->getSubjectIdFieldName($sensitiveDataSubjectIdName);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
namespace Patchlevel\Hydrator\Generator;
4+
5+
use Patchlevel\Hydrator\Metadata\ClassMetadata;
6+
7+
class GenerateClassTransformer
8+
{
9+
public function __construct(
10+
private readonly string $namespace
11+
) {
12+
}
13+
14+
/**
15+
* @return string
16+
*/
17+
public function generate(ClassMetadata $metadata): string
18+
{
19+
return <<<PHP
20+
<?php
21+
22+
declare(strict_types=1);
23+
24+
namespace {$this->namespace};
25+
26+
use Patchlevel\Hydrator\Metadata\ClassMetadata;
27+
28+
final class {$metadata->className()}Transformer
29+
{
30+
{$this->generateProperties($metadata)}
31+
32+
public function __construct(
33+
private readonly ClassMetadata \$metadata
34+
) {
35+
{$this->generateConstructor($metadata)}
36+
}
37+
38+
public function extract(): array
39+
{
40+
return [
41+
42+
];
43+
}
44+
}
45+
PHP;
46+
}
47+
48+
private function generateProperties(ClassMetadata $metadata): string
49+
{
50+
$properties = [];
51+
52+
foreach ($metadata->properties() as $property) {
53+
if ($property->normalizer() === null) {
54+
continue;
55+
}
56+
57+
$properties[] = <<<PHP
58+
private readonly {$property->normalizer()::class} \${$property->propertyName()};
59+
PHP;
60+
}
61+
62+
return implode("\n", $properties);
63+
}
64+
65+
private function generateConstructor(ClassMetadata $metadata): string
66+
{
67+
$constructor = [];
68+
69+
foreach ($metadata->properties() as $property) {
70+
if ($property->normalizer() === null) {
71+
continue;
72+
}
73+
74+
$constructor[] = "\$this->{$property->propertyName()} = \$metadata->propertyForField({$property->fieldName()})->normalizer();";
75+
}
76+
77+
return implode("\n", $constructor);
78+
}
79+
80+
private function generateExtract(ClassMetadata $metadata): string
81+
{
82+
$map = [];
83+
84+
foreach ($metadata->properties() as $property) {
85+
if ($property->normalizer() === null) {
86+
$constructor[] = "'{$property->fieldName()}' => \$metadata->propertyForField({$property->fieldName()})->normalizer();";
87+
}
88+
89+
$constructor[] = "\$this->{$property->propertyName()} = \$metadata->propertyForField({$property->fieldName()})->normalizer();";
90+
}
91+
92+
return implode("\n", $constructor);
93+
}
94+
}

src/Metadata/PropertyMetadata.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
* className: class-string,
1717
* property: string,
1818
* fieldName: string,
19+
* propertyName: string,
20+
* directRead: bool,
21+
* directWrite: bool,
1922
* normalizer: Normalizer|null,
2023
* subjectIdName: string|null,
2124
* sensitiveDataSubjectIdName: string|null,
@@ -26,6 +29,12 @@ final class PropertyMetadata
2629
{
2730
private const ENCRYPTED_PREFIX = '!';
2831

32+
public readonly string $propertyName;
33+
34+
private readonly bool $directRead;
35+
36+
private readonly bool $directWrite;
37+
2938
/** @param (callable(string, mixed):mixed)|null $sensitiveDataFallbackCallable */
3039
public function __construct(
3140
public readonly ReflectionProperty $reflection,
@@ -39,11 +48,15 @@ public function __construct(
3948
if (str_starts_with($fieldName, self::ENCRYPTED_PREFIX)) {
4049
throw new InvalidArgumentException('fieldName must not start with !');
4150
}
51+
52+
$this->propertyName = $reflection->getName();
53+
$this->directRead = $reflection->isPublic();
54+
$this->directWrite = $reflection->isPublic();
4255
}
4356

4457
public function propertyName(): string
4558
{
46-
return $this->reflection->getName();
59+
return $this->propertyName;
4760
}
4861

4962
public function encryptedFieldName(): string
@@ -53,11 +66,21 @@ public function encryptedFieldName(): string
5366

5467
public function setValue(object $object, mixed $value): void
5568
{
69+
if ($this->directWrite) {
70+
$object->{$this->propertyName} = $value;
71+
72+
return;
73+
}
74+
5675
$this->reflection->setValue($object, $value);
5776
}
5877

5978
public function getValue(object $object): mixed
6079
{
80+
if ($this->directRead) {
81+
return $object->{$this->propertyName};
82+
}
83+
6184
return $this->reflection->getValue($object);
6285
}
6386

@@ -90,6 +113,9 @@ public function __serialize(): array
90113
'className' => $this->reflection->getDeclaringClass()->getName(),
91114
'property' => $this->reflection->getName(),
92115
'fieldName' => $this->fieldName,
116+
'propertyName' => $this->propertyName,
117+
'directRead' => $this->directRead,
118+
'directWrite' => $this->directWrite,
93119
'normalizer' => $this->normalizer,
94120
'subjectIdName' => $this->subjectIdName,
95121
'sensitiveDataSubjectIdName' => $this->sensitiveDataSubjectIdName,
@@ -102,6 +128,9 @@ public function __unserialize(array $data): void
102128
{
103129
$this->reflection = new ReflectionProperty($data['className'], $data['property']);
104130
$this->fieldName = $data['fieldName'];
131+
$this->propertyName = $data['propertyName'];
132+
$this->directRead = $data['directRead'];
133+
$this->directWrite = $data['directWrite'];
105134
$this->normalizer = $data['normalizer'];
106135
$this->subjectIdName = $data['subjectIdName'];
107136
$this->sensitiveDataSubjectIdName = $data['sensitiveDataSubjectIdName'];

src/Middleware/TransformMiddleware.php

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@
88
use Patchlevel\Hydrator\DenormalizationFailure;
99
use Patchlevel\Hydrator\Metadata\ClassMetadata;
1010
use Patchlevel\Hydrator\NormalizationFailure;
11-
use Patchlevel\Hydrator\NormalizationMissing;
12-
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
1311
use Patchlevel\Hydrator\TypeMismatch;
1412
use ReflectionParameter;
1513
use Throwable;
1614
use TypeError;
1715

1816
use function array_key_exists;
19-
use function is_object;
17+
use function array_values;
18+
use function spl_object_id;
2019

2120
final class TransformMiddleware implements Middleware
2221
{
22+
/** @var array<int, class-string> */
23+
private array $callStack = [];
24+
2325
/**
2426
* @param ClassMetadata<T> $metadata
2527
* @param array<string, mixed> $data
@@ -34,43 +36,44 @@ public function hydrate(ClassMetadata $metadata, array $data, Stack $stack): obj
3436

3537
$constructorParameters = null;
3638

37-
foreach ($metadata->properties() as $propertyMetadata) {
38-
if (!array_key_exists($propertyMetadata->fieldName(), $data)) {
39-
if (!$propertyMetadata->reflection()->isPromoted()) {
39+
foreach ($metadata->properties as $propertyMetadata) {
40+
if (!array_key_exists($propertyMetadata->fieldName, $data)) {
41+
if (!$propertyMetadata->reflection->isPromoted()) {
4042
continue;
4143
}
4244

4345
if ($constructorParameters === null) {
4446
$constructorParameters = $this->promotedConstructorParametersWithDefaultValue($metadata);
4547
}
4648

47-
if (!array_key_exists($propertyMetadata->propertyName(), $constructorParameters)) {
49+
if (!array_key_exists($propertyMetadata->propertyName, $constructorParameters)) {
4850
continue;
4951
}
5052

51-
/** @psalm-suppress MixedAssignment */
52-
$defaultValue = $constructorParameters[$propertyMetadata->propertyName()]->getDefaultValue();
53-
$propertyMetadata->setValue($object, $defaultValue);
53+
$propertyMetadata->setValue(
54+
$object,
55+
$constructorParameters[$propertyMetadata->propertyName]->getDefaultValue(),
56+
);
5457

5558
continue;
5659
}
5760

58-
$normalizer = $propertyMetadata->normalizer();
61+
$normalizer = $propertyMetadata->normalizer;
5962

6063
if ($normalizer) {
6164
try {
6265
/** @psalm-suppress MixedAssignment */
63-
$value = $normalizer->denormalize($data[$propertyMetadata->fieldName()]);
66+
$value = $normalizer->denormalize($data[$propertyMetadata->fieldName]);
6467
} catch (Throwable $e) {
6568
throw new DenormalizationFailure(
6669
$metadata->className(),
67-
$propertyMetadata->propertyName(),
70+
$propertyMetadata->propertyName,
6871
$normalizer::class,
6972
$e,
7073
);
7174
}
7275
} else {
73-
$value = $data[$propertyMetadata->fieldName()];
76+
$value = $data[$propertyMetadata->fieldName];
7477
}
7578

7679
try {
@@ -84,44 +87,49 @@ public function hydrate(ClassMetadata $metadata, array $data, Stack $stack): obj
8487
}
8588
}
8689

87-
foreach ($metadata->postHydrateCallbacks() as $callback) {
88-
$callback->invoke($object);
89-
}
90-
9190
return $object;
9291
}
9392

9493
/** @return array<string, mixed> */
9594
public function extract(ClassMetadata $metadata, object $object, Stack $stack): array
9695
{
97-
foreach ($metadata->preExtractCallbacks() as $callback) {
98-
$callback->invoke($object);
99-
}
96+
$objectId = spl_object_id($object);
10097

101-
$data = [];
98+
if (array_key_exists($objectId, $this->callStack)) {
99+
$references = array_values($this->callStack);
100+
$references[] = $object::class;
102101

103-
foreach ($metadata->properties() as $propertyMetadata) {
104-
$normalizer = $propertyMetadata->normalizer();
102+
throw new CircularReference($references);
103+
}
105104

106-
if ($normalizer) {
107-
try {
108-
/** @psalm-suppress MixedAssignment */
109-
$data[$propertyMetadata->fieldName()] = $normalizer->normalize(
110-
$propertyMetadata->getValue($object),
111-
);
112-
} catch (CircularReference $e) {
113-
throw $e;
114-
} catch (Throwable $e) {
115-
throw new NormalizationFailure(
116-
$object::class,
117-
$propertyMetadata->propertyName(),
118-
$normalizer::class,
119-
$e,
120-
);
105+
$this->callStack[$objectId] = $object::class;
106+
107+
try {
108+
$data = [];
109+
110+
foreach ($metadata->properties as $propertyMetadata) {
111+
if ($propertyMetadata->normalizer) {
112+
try {
113+
/** @psalm-suppress MixedAssignment */
114+
$data[$propertyMetadata->fieldName] = $propertyMetadata->normalizer->normalize(
115+
$propertyMetadata->getValue($object),
116+
);
117+
} catch (CircularReference $e) {
118+
throw $e;
119+
} catch (Throwable $e) {
120+
throw new NormalizationFailure(
121+
$object::class,
122+
$propertyMetadata->propertyName,
123+
$propertyMetadata->normalizer::class,
124+
$e,
125+
);
126+
}
127+
} else {
128+
$data[$propertyMetadata->fieldName] = $propertyMetadata->getValue($object);
121129
}
122-
} else {
123-
$data[$propertyMetadata->fieldName()] = $propertyMetadata->getValue($object);
124130
}
131+
} finally {
132+
unset($this->callStack[$objectId]);
125133
}
126134

127135
return $data;

0 commit comments

Comments
 (0)