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
50 changes: 50 additions & 0 deletions src/Cryptography/CryptographyMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Cryptography;

use Patchlevel\Hydrator\Metadata\ClassMetadata;
use Patchlevel\Hydrator\Middleware\Middleware;
use Patchlevel\Hydrator\Middleware\Stack;

final class CryptographyMiddleware implements Middleware
{
public function __construct(
private readonly PayloadCryptographer $cryptography,
) {
}

/**
* @param ClassMetadata<T> $metadata
* @param array<string, mixed> $data
*
* @return T
*
* @template T of object
*/
public function hydrate(ClassMetadata $metadata, array $data, Stack $stack): object
{
return $stack->next()->hydrate(
$metadata,
$this->cryptography->decrypt($metadata, $data),
$stack,
);
}

/**
* @param ClassMetadata<T> $metadata
* @param T $object
*
* @return array<string, mixed>
*
* @template T of object
*/
public function extract(ClassMetadata $metadata, object $object, Stack $stack): array
{
return $this->cryptography->decrypt(
$metadata,
$stack->next()->extract($metadata, $object, $stack),
);
}
}
4 changes: 2 additions & 2 deletions src/Cryptography/SensitiveDataPayloadCryptographer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,13 @@ public function decrypt(ClassMetadata $metadata, array $data): array
private function subjectId(PropertyMetadata $propertyMetadata, ClassMetadata $metadata, array $data): string
{
if (!$propertyMetadata->isSensitiveData()) {
throw new NotSensitiveData($metadata->className(), $propertyMetadata->propertyName());
throw new NotSensitiveData($metadata->className(), $propertyMetadata->propertyName);
}

$sensitiveDataSubjectIdName = $propertyMetadata->sensitiveDataSubjectIdName;

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

$fieldName = $metadata->getSubjectIdFieldName($sensitiveDataSubjectIdName);
Expand Down
25 changes: 25 additions & 0 deletions src/Metadata/ArrayCacheMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Metadata;

class ArrayCacheMetadataFactory implements MetadataFactory

Check failure on line 7 in src/Metadata/ArrayCacheMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.4, ubuntu-latest)

ClassMustBeFinal

src/Metadata/ArrayCacheMetadataFactory.php:7:7: ClassMustBeFinal: Class Patchlevel\Hydrator\Metadata\ArrayCacheMetadataFactory is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)
{
/** @var array<class-string, ClassMetadata> */
private array $classMetadata = [];

public function __construct(
private readonly MetadataFactory $metadataFactory,
) {
}

public function metadata(string $class): ClassMetadata

Check failure on line 17 in src/Metadata/ArrayCacheMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.4, ubuntu-latest)

InvalidReturnType

src/Metadata/ArrayCacheMetadataFactory.php:17:46: InvalidReturnType: The declared return type 'Patchlevel\Hydrator\Metadata\ClassMetadata<T:fn-patchlevel\hydrator\metadata\metadatafactory::metadata as object>' for Patchlevel\Hydrator\Metadata\ArrayCacheMetadataFactory::metadata is incorrect, got 'Patchlevel\Hydrator\Metadata\ClassMetadata|(Patchlevel\Hydrator\Metadata\ClassMetadata<T:fn-patchlevel\hydrator\metadata\metadatafactory::metadata as object>)' (see https://psalm.dev/011)
{
if (!isset($this->classMetadata[$class])) {
$this->classMetadata[$class] = $this->metadataFactory->metadata($class);
}

return $this->classMetadata[$class];

Check failure on line 23 in src/Metadata/ArrayCacheMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.4, ubuntu-latest)

InvalidReturnStatement

src/Metadata/ArrayCacheMetadataFactory.php:23:16: InvalidReturnStatement: The inferred type 'Patchlevel\Hydrator\Metadata\ClassMetadata|(Patchlevel\Hydrator\Metadata\ClassMetadata<T:fn-patchlevel\hydrator\metadata\metadatafactory::metadata as object>)' does not match the declared return type 'Patchlevel\Hydrator\Metadata\ClassMetadata<T:fn-patchlevel\hydrator\metadata\metadatafactory::metadata as object>' for Patchlevel\Hydrator\Metadata\ArrayCacheMetadataFactory::metadata (see https://psalm.dev/128)
}
}
32 changes: 32 additions & 0 deletions src/Metadata/HydratorSetterMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Metadata;

use Patchlevel\Hydrator\Hydrator;
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;

class HydratorSetterMetadataFactory implements MetadataFactory

Check failure on line 10 in src/Metadata/HydratorSetterMetadataFactory.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.4, ubuntu-latest)

ClassMustBeFinal

src/Metadata/HydratorSetterMetadataFactory.php:10:7: ClassMustBeFinal: Class Patchlevel\Hydrator\Metadata\HydratorSetterMetadataFactory is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)
{
public function __construct(
private readonly Hydrator $hydrator,
private readonly MetadataFactory $metadataFactory,
) {
}

public function metadata(string $class): ClassMetadata
{
$metadata = $this->metadataFactory->metadata($class);

foreach ($metadata->properties as $property) {
if (!($property->normalizer instanceof HydratorAwareNormalizer)) {
continue;
}

$property->normalizer->setHydrator($this->hydrator);
}

return $metadata;
}
}
31 changes: 30 additions & 1 deletion src/Metadata/PropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
* className: class-string,
* property: string,
* fieldName: string,
* propertyName: string,
* directRead: bool,
* directWrite: bool,
* normalizer: Normalizer|null,
* subjectIdName: string|null,
* sensitiveDataSubjectIdName: string|null,
Expand All @@ -26,6 +29,12 @@ final class PropertyMetadata
{
private const ENCRYPTED_PREFIX = '!';

public readonly string $propertyName;

private readonly bool $directRead;

private readonly bool $directWrite;

/** @param (callable(string, mixed):mixed)|null $sensitiveDataFallbackCallable */
public function __construct(
public readonly ReflectionProperty $reflection,
Expand All @@ -39,11 +48,15 @@ public function __construct(
if (str_starts_with($fieldName, self::ENCRYPTED_PREFIX)) {
throw new InvalidArgumentException('fieldName must not start with !');
}

$this->propertyName = $reflection->getName();
$this->directRead = $reflection->isPublic();
$this->directWrite = $reflection->isPublic();
}

public function propertyName(): string
{
return $this->reflection->getName();
return $this->propertyName;
}

public function encryptedFieldName(): string
Expand All @@ -53,11 +66,21 @@ public function encryptedFieldName(): string

public function setValue(object $object, mixed $value): void
{
if ($this->directWrite) {
$object->{$this->propertyName} = $value;

return;
}

$this->reflection->setValue($object, $value);
}

public function getValue(object $object): mixed
{
if ($this->directRead) {
return $object->{$this->propertyName};
}

return $this->reflection->getValue($object);
}

Expand Down Expand Up @@ -90,6 +113,9 @@ public function __serialize(): array
'className' => $this->reflection->getDeclaringClass()->getName(),
'property' => $this->reflection->getName(),
'fieldName' => $this->fieldName,
'propertyName' => $this->propertyName,
'directRead' => $this->directRead,
'directWrite' => $this->directWrite,
'normalizer' => $this->normalizer,
'subjectIdName' => $this->subjectIdName,
'sensitiveDataSubjectIdName' => $this->sensitiveDataSubjectIdName,
Expand All @@ -102,6 +128,9 @@ public function __unserialize(array $data): void
{
$this->reflection = new ReflectionProperty($data['className'], $data['property']);
$this->fieldName = $data['fieldName'];
$this->propertyName = $data['propertyName'];
$this->directRead = $data['directRead'];
$this->directWrite = $data['directWrite'];
$this->normalizer = $data['normalizer'];
$this->subjectIdName = $data['subjectIdName'];
$this->sensitiveDataSubjectIdName = $data['sensitiveDataSubjectIdName'];
Expand Down
30 changes: 30 additions & 0 deletions src/Middleware/Middleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Middleware;

use Patchlevel\Hydrator\Metadata\ClassMetadata;

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

/**
* @param ClassMetadata<T> $metadata
* @param T $object
*
* @return array<string, mixed>
*
* @template T of object
*/
public function extract(ClassMetadata $metadata, object $object, Stack $stack): array;
}
16 changes: 16 additions & 0 deletions src/Middleware/NoMoreMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Middleware;

use Patchlevel\Hydrator\HydratorException;
use RuntimeException;

final class NoMoreMiddleware extends RuntimeException implements HydratorException
{
public function __construct()
{
parent::__construct('no more middlewares');
}
}
29 changes: 29 additions & 0 deletions src/Middleware/Stack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Middleware;

final class Stack
{
private int $index = 0;

/** @param list<Middleware> $middlewares */
public function __construct(
private readonly array $middlewares,
) {
}

public function next(): Middleware
{
$next = $this->middlewares[$this->index] ?? null;

if ($next === null) {
throw new NoMoreMiddleware();
}

$this->index++;

return $next;
}
}
Loading
Loading