Skip to content

Added cipher targets #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 26, 2024
Merged
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
13 changes: 1 addition & 12 deletions .github/workflows/pr-label-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,7 @@ jobs:
uses: actions/github-script@v6
with:
script: |
const labels = context.payload.pull_request.labels;
const labels = context.payload.pull_request?.labels || [];
if (labels.length === 0) {
throw new Error('This pull request must have at least one label.');
}
- name: Comment on missing labels
if: failure()
uses: actions/github-script@v6
with:
script: |
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'This pull request must have at least one label.'
})
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
}
],
"require": {
"php": "~8.4"
"php": "~8.4",
"ext-openssl": "*"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
Expand Down
5 changes: 3 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 57 additions & 1 deletion docs/PropertyAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Property Attributes
In order to provide more functionality to your DTOs, you can use the following attributes:
- [Computed](#Computed) - To define a property that is computed from other properties.
- [Aliases](#Aliases) - To define aliases for a property.
- [CipherTarget](#CipherTarget) - To define a property that should be encrypted/decrypted.

Computed
-
Expand Down Expand Up @@ -46,10 +47,65 @@ final readonly class Person extends Data
public function __construct(
#[Aliases('first_name')]
public string $firstName,
#[Aliases('last_name')]
#[Aliases('last_name', 'family_name')]
public string $lastName
) {}
}
```

This will make it possible to hydrate properties from multiple array keys.

CipherTarget
-

Sometimes, we may need to specify that some properties are considered sensitive, and should be
handled carefully, especially when saving it.

For this we can use encryption/decryption using the `CipherTarget` attribute.

```php
use Nuxtifyts\PhpDto\Data;
use Nuxtifyts\PhpDto\Attributes\Property\Types\ArrayOfData;
use Nuxtifyts\PhpDto\Attributes\Property\CipherTarget;

final readonly class User extends Data
{
/**
* @param list<UserConfigData> $userConfigs
*/
public function __construct(
#[ArrayOfData(UserConfigData::class)]
#[CipherTarget(
secret: 'user-configs-secret-key', // By default, it uses the class name
encoded: true // By default, it does not perform encoding
)]
public array $userConfigs
) {}
}

```

it is also possible to specify a custom DataCipher for the property,
the new class should implement the `Nuxtifyts\PhpDto\DataCipher` interface.

```php

use Nuxtifyts\PhpDto\DataCiphers\DataCipher;

class CustomDataCipher implements DataCipher
{
// Implement the interface
}
```

Then you can specify the custom DataCipher in the `CipherTarget` attribute.

```php
public function __construct(
#[CipherTarget(
dataCipherClass: CustomDataCipher::class,
)]
public UserConfigData $userConfig
) {}
```

2 changes: 1 addition & 1 deletion src/Attributes/Property/Aliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Aliases
{
/** @var list<string> */
private(set) array $aliases;
protected(set) array $aliases;

public function __construct(
string $alias,
Expand Down
20 changes: 20 additions & 0 deletions src/Attributes/Property/CipherTarget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Nuxtifyts\PhpDto\Attributes\Property;

use Attribute;
use Nuxtifyts\PhpDto\DataCiphers\DataCipher;

#[Attribute(Attribute::TARGET_PROPERTY)]
class CipherTarget
{
/**
* @param class-string<DataCipher> $dataCipherClass
*/
public function __construct(
protected(set) string $dataCipherClass = DataCipher::class,
protected(set) string $secret = '',
protected(set) bool $encoded = false
) {
}
}
6 changes: 3 additions & 3 deletions src/Attributes/Property/Types/ArrayOfBackedEnums.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
class ArrayOfBackedEnums
{
/** @var array<string, ReflectionEnum<BackedEnum>> */
private static array $_enumReflections = [];
protected static array $_enumReflections = [];

/** @var list<class-string<BackedEnum>> $enums */
private(set) array $enums;
protected(set) array $enums;

/** @var array<string, ReflectionEnum<BackedEnum>> */
private(set) array $resolvedBackedEnumReflections = [];
protected(set) array $resolvedBackedEnumReflections = [];

/**
* @param class-string<BackedEnum>|list<class-string<BackedEnum>> $enums
Expand Down
6 changes: 3 additions & 3 deletions src/Attributes/Property/Types/ArrayOfData.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
class ArrayOfData
{
/** @var array<string, ReflectionClass<Data>> */
private static array $_dataReflections = [];
protected static array $_dataReflections = [];

/** @var list<class-string<Data>> */
private(set) array $dataClasses;
protected(set) array $dataClasses;

/** @var array<string, ReflectionClass<Data>> */
private(set) array $resolvedDataReflections = [];
protected(set) array $resolvedDataReflections = [];

/**
* @param class-string<Data>|list<class-string<Data>> $dataClasses
Expand Down
6 changes: 3 additions & 3 deletions src/Attributes/Property/Types/ArrayOfDateTimes.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
class ArrayOfDateTimes
{
/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
private static array $_dateTimeReflections = [];
protected static array $_dateTimeReflections = [];

/** @var list<class-string<DateTime|DateTimeImmutable>> */
private(set) array $dateTimes;
protected(set) array $dateTimes;

/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
private(set) array $resolvedDateTimeReflections = [];
protected(set) array $resolvedDateTimeReflections = [];

/**
* @param class-string<DateTime|DateTimeImmutable>|list<class-string<DateTime|DateTimeImmutable>> $dateTimes
Expand Down
2 changes: 1 addition & 1 deletion src/Attributes/Property/Types/ArrayOfScalarTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class ArrayOfScalarTypes
{
/** @var list<Type> $types */
private(set) array $types;
protected(set) array $types;

/**
* @param Type|list<Type> $types
Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/Property/WithRefiner.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
class WithRefiner
{
/** @var array<array-key, mixed> */
private array $refinerArgs;
protected array $refinerArgs;

/**
* @param class-string<DataRefiner> $refinerClass
*/
public function __construct(
private readonly string $refinerClass,
protected readonly string $refinerClass,
mixed ...$refinerArgs
) {
$this->refinerArgs = $refinerArgs;
Expand Down
8 changes: 5 additions & 3 deletions src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Nuxtifyts\PhpDto\Contexts\ClassContext;
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
use Nuxtifyts\PhpDto\Exceptions\SerializeException;
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DecipherDataPipe;
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable;
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\RefineDataPipe;
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\ResolveValuesFromAliasesPipe;
Expand Down Expand Up @@ -37,6 +38,7 @@ final public static function from(mixed $value): static
$data = new Pipeline(DeserializePipelinePassable::class)
->through(ResolveValuesFromAliasesPipe::class)
->through(RefineDataPipe::class)
->through(DecipherDataPipe::class)
->sendThenReturn(new DeserializePipelinePassable(
classContext: $context,
data: $value
Expand Down Expand Up @@ -112,18 +114,18 @@ final public function jsonSerialize(): array
try {
$context = ClassContext::getInstance(new ReflectionClass($this));

$serializableArray = [];
$serializedData = [];
foreach ($context->properties as $propertyContext) {
if ($propertyContext->isComputed) {
continue;
}

$propertyName = $propertyContext->propertyName;

$serializableArray[$propertyName] = $propertyContext->serializeFrom($this)[$propertyName];
$serializedData[$propertyName] = $propertyContext->serializeFrom($this)[$propertyName];
}

return $serializableArray;
return $serializedData;
} catch (Throwable $e) {
throw new SerializeException($e->getMessage(), $e->getCode(), $e);
}
Expand Down
39 changes: 37 additions & 2 deletions src/Contexts/PropertyContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Nuxtifyts\PhpDto\Contexts;

use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
use Nuxtifyts\PhpDto\Attributes\Property\CipherTarget;
use Nuxtifyts\PhpDto\Attributes\Property\Computed;
use Nuxtifyts\PhpDto\Attributes\Property\WithRefiner;
use Nuxtifyts\PhpDto\DataCiphers\CipherConfig;
use Nuxtifyts\PhpDto\DataRefiners\DataRefiner;
use Nuxtifyts\PhpDto\Enums\Property\Type;
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
Expand All @@ -16,6 +18,7 @@
use Nuxtifyts\PhpDto\Support\Traits\HasTypes;
use ReflectionProperty;
use ReflectionAttribute;
use Exception;

class PropertyContext
{
Expand All @@ -37,6 +40,8 @@ class PropertyContext

private(set) bool $isComputed = false;

private(set) ?CipherConfig $cipherConfig = null;

/** @var list<DataRefiner> */
private(set) array $dataRefiners = [];

Expand Down Expand Up @@ -96,6 +101,17 @@ private function syncPropertyAttributes(): void
/** @var ReflectionAttribute<Aliases> $aliasesAttribute */
$this->aliases = $aliasesAttribute->newInstance()->aliases;
}

if ($cipherTargetAttribute = $this->reflection->getAttributes(CipherTarget::class)[0] ?? null) {
/** @var ReflectionAttribute<CipherTarget> $cipherTargetAttribute */
$instance = $cipherTargetAttribute->newInstance();

$this->cipherConfig = new CipherConfig(
dataCipherClass: $instance->dataCipherClass,
secret: $instance->secret ?: $this->reflection->getName(),
encoded: $instance->encoded
);
}
}

public function getValue(object $object): mixed
Expand Down Expand Up @@ -169,11 +185,30 @@ public function serializeFrom(object $object): array
{
foreach ($this->serializers() as $serializer) {
try {
return $serializer->serialize($this, $object);
$serializedData = $serializer->serialize($this, $object);
} catch (SerializeException) {
}
}

throw new SerializeException('Could not serialize value for property: ' . $this->propertyName);
if (empty($serializedData)) {
throw new SerializeException('Could not serialize value for property: ' . $this->propertyName);
}

try {
if ($this->cipherConfig) {
return array_map(
fn (mixed $value) => $this->cipherConfig->dataCipherClass::cipher(
data: $value,
secret: $this->cipherConfig->secret,
encode: $this->cipherConfig->encoded
),
$serializedData
);
}

return $serializedData;
} catch (Exception) {
throw new SerializeException('Could not serialize value for property: ' . $this->propertyName);
}
}
}
16 changes: 16 additions & 0 deletions src/DataCiphers/CipherConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Nuxtifyts\PhpDto\DataCiphers;

readonly class CipherConfig
{
/**
* @param class-string<DataCipher> $dataCipherClass
*/
public function __construct(
public string $dataCipherClass,
public string $secret,
public bool $encoded
) {
}
}
26 changes: 26 additions & 0 deletions src/DataCiphers/DataCipher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Nuxtifyts\PhpDto\DataCiphers;

use Nuxtifyts\PhpDto\Exceptions\DataCipherException;

interface DataCipher
{
/**
* @throws DataCipherException
*/
public static function cipher(
mixed $data,
string $secret,
bool $encode = false
): string;

/**
* @throws DataCipherException
*/
public static function decipher(
string $data,
string $secret,
bool $decode = false
): mixed;
}
Loading
Loading