Skip to content

Commit f5ed341

Browse files
committed
Merge branch '1.21.x' into 1.22.x
2 parents 92de0c4 + 5304846 commit f5ed341

8 files changed

Lines changed: 368 additions & 7 deletions

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ parameters:
6060
count: 1
6161
path: src/Extension/Cryptography/Cipher/OpensslCipherKeyFactory.php
6262

63+
-
64+
message: '#^Cannot access property \$nameToField on mixed\.$#'
65+
identifier: property.nonObject
66+
count: 1
67+
path: src/Extension/Cryptography/LegacyCryptographyMetadataEnricher.php
68+
6369
-
6470
message: '#^Method Patchlevel\\Hydrator\\Guesser\\BuiltInGuesser\:\:guess\(\) has parameter \$type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types\: T$#'
6571
identifier: missingType.generics

src/Extension/Cryptography/CryptographyExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class CryptographyExtension implements Extension
1414
public function __construct(
1515
private readonly Cryptographer $cryptography,
1616
private readonly PayloadCryptographer|null $legacyCryptographer = null,
17+
private readonly bool $legacyMetadataMapping = false,
1718
) {
1819
}
1920

@@ -22,6 +23,10 @@ public function configure(StackHydratorBuilder $builder): void
2223
$builder->addMetadataEnricher(new CryptographyMetadataEnricher(), 64);
2324
$builder->addMiddleware(new CryptographyMiddleware($this->cryptography), 64);
2425

26+
if ($this->legacyMetadataMapping) {
27+
$builder->addMetadataEnricher(new LegacyCryptographyMetadataEnricher(), 63);
28+
}
29+
2530
if ($this->legacyCryptographer === null) {
2631
return;
2732
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Extension\Cryptography;
6+
7+
use Patchlevel\Hydrator\Attribute\DataSubjectId;
8+
use Patchlevel\Hydrator\Attribute\PersonalData;
9+
use Patchlevel\Hydrator\Metadata\ClassMetadata;
10+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
11+
use ReflectionProperty;
12+
13+
use function array_key_exists;
14+
use function in_array;
15+
16+
final class LegacyCryptographyMetadataEnricher implements MetadataEnricher
17+
{
18+
private const SUBJECT_ID = 'legacy';
19+
20+
public function enrich(ClassMetadata $classMetadata): void
21+
{
22+
/** @var array<string, string> $subjectIdMapping */
23+
$subjectIdMapping = isset($classMetadata->extras[SubjectIdFieldMapping::class])
24+
? $classMetadata->extras[SubjectIdFieldMapping::class]->nameToField
25+
: [];
26+
27+
foreach ($classMetadata->properties as $property) {
28+
$attributeReflectionList = $property->reflection->getAttributes(DataSubjectId::class);
29+
30+
if ($attributeReflectionList) {
31+
if (array_key_exists(self::SUBJECT_ID, $subjectIdMapping)) {
32+
throw new DuplicateSubjectIdIdentifier(
33+
$classMetadata->className,
34+
$classMetadata->propertyForField($subjectIdMapping[self::SUBJECT_ID])->propertyName,
35+
$property->propertyName,
36+
self::SUBJECT_ID,
37+
);
38+
}
39+
40+
$subjectIdMapping[self::SUBJECT_ID] = $property->fieldName;
41+
}
42+
43+
$sensitiveDataInfo = $this->sensitiveDataInfo($property->reflection);
44+
45+
if (!$sensitiveDataInfo) {
46+
continue;
47+
}
48+
49+
if (in_array($property->fieldName, $subjectIdMapping, true)) {
50+
throw new SubjectIdAndSensitiveDataConflict($classMetadata->className, $property->propertyName);
51+
}
52+
53+
if (isset($property->extras[SensitiveDataInfo::class])) {
54+
throw new PersonalDataAndSensitiveDataOnSameProperty($classMetadata->className, $property->propertyName);
55+
}
56+
57+
$property->extras[SensitiveDataInfo::class] = $sensitiveDataInfo;
58+
}
59+
60+
if ($subjectIdMapping === []) {
61+
return;
62+
}
63+
64+
$classMetadata->extras[SubjectIdFieldMapping::class] = new SubjectIdFieldMapping($subjectIdMapping);
65+
}
66+
67+
private function sensitiveDataInfo(ReflectionProperty $reflectionProperty): SensitiveDataInfo|null
68+
{
69+
$attributeReflectionList = $reflectionProperty->getAttributes(PersonalData::class);
70+
71+
if ($attributeReflectionList === []) {
72+
return null;
73+
}
74+
75+
$attribute = $attributeReflectionList[0]->newInstance();
76+
77+
return new SensitiveDataInfo(
78+
self::SUBJECT_ID,
79+
$attribute->fallbackCallable !== null ? ($attribute->fallbackCallable)(...) : $attribute->fallback,
80+
);
81+
}
82+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Extension\Cryptography;
6+
7+
use Patchlevel\Hydrator\Metadata\MetadataException;
8+
use RuntimeException;
9+
10+
use function sprintf;
11+
12+
/** @experimental */
13+
final class PersonalDataAndSensitiveDataOnSameProperty extends RuntimeException implements MetadataException
14+
{
15+
/** @param class-string $class */
16+
public function __construct(string $class, string $property)
17+
{
18+
parent::__construct(
19+
sprintf(
20+
'Personal data and sensitive data cannot be used on the same property %s::%s',
21+
$class,
22+
$property,
23+
),
24+
);
25+
}
26+
}

src/StackHydratorBuilder.php

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,11 @@ public function setCache(CacheItemPoolInterface|CacheInterface|null $cache): sta
8181

8282
public function build(): StackHydrator
8383
{
84-
krsort($this->guessers);
85-
krsort($this->metadataEnrichers);
86-
krsort($this->middlewares);
87-
8884
$metadataFactory = new EnrichingMetadataFactory(
8985
new AttributeMetadataFactory(
90-
guesser: new ChainGuesser(array_merge(...$this->guessers)),
86+
guesser: new ChainGuesser($this->guessers()),
9187
),
92-
array_merge(...$this->metadataEnrichers),
88+
$this->metadataEnrichers(),
9389
);
9490

9591
if ($this->cache instanceof CacheItemPoolInterface) {
@@ -102,8 +98,37 @@ public function build(): StackHydrator
10298

10399
return new StackHydrator(
104100
$metadataFactory,
105-
array_merge(...$this->middlewares),
101+
$this->middlewares(),
106102
$this->defaultLazy,
107103
);
108104
}
105+
106+
public function defaultLazy(): bool
107+
{
108+
return $this->defaultLazy;
109+
}
110+
111+
/** @return list<Middleware> */
112+
public function middlewares(): array
113+
{
114+
krsort($this->middlewares);
115+
116+
return array_merge(...$this->middlewares);
117+
}
118+
119+
/** @return list<Guesser> */
120+
public function guessers(): array
121+
{
122+
krsort($this->guessers);
123+
124+
return array_merge(...$this->guessers);
125+
}
126+
127+
/** @return list<MetadataEnricher> */
128+
public function metadataEnrichers(): array
129+
{
130+
krsort($this->metadataEnrichers);
131+
132+
return array_merge(...$this->metadataEnrichers);
133+
}
109134
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Tests\Unit\Extension\Cryptography;
6+
7+
use Patchlevel\Hydrator\Cryptography\PayloadCryptographer;
8+
use Patchlevel\Hydrator\Extension\Cryptography\Cryptographer;
9+
use Patchlevel\Hydrator\Extension\Cryptography\CryptographyExtension;
10+
use Patchlevel\Hydrator\Extension\Cryptography\CryptographyMetadataEnricher;
11+
use Patchlevel\Hydrator\Extension\Cryptography\CryptographyMiddleware;
12+
use Patchlevel\Hydrator\Extension\Cryptography\LegacyCryptographyDecryptMiddleware;
13+
use Patchlevel\Hydrator\Extension\Cryptography\LegacyCryptographyMetadataEnricher;
14+
use Patchlevel\Hydrator\StackHydratorBuilder;
15+
use PHPUnit\Framework\Attributes\CoversClass;
16+
use PHPUnit\Framework\TestCase;
17+
18+
#[CoversClass(CryptographyExtension::class)]
19+
final class CryptographyExtensionTest extends TestCase
20+
{
21+
public function testConfigureWithoutLegacyOptions(): void
22+
{
23+
$builder = new StackHydratorBuilder();
24+
$cryptographer = $this->createMock(Cryptographer::class);
25+
26+
$extension = new CryptographyExtension($cryptographer);
27+
$extension->configure($builder);
28+
29+
$middlewares = $builder->middlewares();
30+
self::assertCount(1, $middlewares);
31+
self::assertInstanceOf(CryptographyMiddleware::class, $middlewares[0]);
32+
33+
$metadataEnrichers = $builder->metadataEnrichers();
34+
self::assertCount(1, $metadataEnrichers);
35+
self::assertInstanceOf(CryptographyMetadataEnricher::class, $metadataEnrichers[0]);
36+
}
37+
38+
public function testConfigureWithLegacyMetadataMapping(): void
39+
{
40+
$builder = new StackHydratorBuilder();
41+
$cryptographer = $this->createMock(Cryptographer::class);
42+
43+
$extension = new CryptographyExtension($cryptographer, legacyMetadataMapping: true);
44+
$extension->configure($builder);
45+
46+
$metadataEnrichers = $builder->metadataEnrichers();
47+
self::assertCount(2, $metadataEnrichers);
48+
self::assertInstanceOf(CryptographyMetadataEnricher::class, $metadataEnrichers[0]);
49+
self::assertInstanceOf(LegacyCryptographyMetadataEnricher::class, $metadataEnrichers[1]);
50+
}
51+
52+
public function testConfigureWithLegacyCryptographerAndMetadataMapping(): void
53+
{
54+
$builder = new StackHydratorBuilder();
55+
$cryptographer = $this->createMock(Cryptographer::class);
56+
$legacyCryptographer = $this->createMock(PayloadCryptographer::class);
57+
58+
$extension = new CryptographyExtension($cryptographer, $legacyCryptographer, true);
59+
$extension->configure($builder);
60+
61+
$middlewares = $builder->middlewares();
62+
self::assertCount(2, $middlewares);
63+
self::assertInstanceOf(LegacyCryptographyDecryptMiddleware::class, $middlewares[0]);
64+
self::assertInstanceOf(CryptographyMiddleware::class, $middlewares[1]);
65+
66+
$metadataEnrichers = $builder->metadataEnrichers();
67+
self::assertCount(2, $metadataEnrichers);
68+
self::assertInstanceOf(CryptographyMetadataEnricher::class, $metadataEnrichers[0]);
69+
self::assertInstanceOf(LegacyCryptographyMetadataEnricher::class, $metadataEnrichers[1]);
70+
}
71+
}

0 commit comments

Comments
 (0)