Skip to content

Added computed properties #1

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 3 commits into from
Dec 24, 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
5 changes: 1 addition & 4 deletions .github/workflows/php-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: PHP Composer
name: CI Tests

on:
push:
Expand Down Expand Up @@ -38,9 +38,6 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: Get directory
run: ls -la

- name: Run PHPUnit
run: composer run-script ci-test

Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/phpstan-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: PHPStan Checks

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-

- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: Run PHPStan
run: composer run-script phpstan
29 changes: 29 additions & 0 deletions .github/workflows/pr-label-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Enforce PR Label

on:
pull_request:
types: [opened, edited, unlabeled]

jobs:
check-label:
runs-on: ubuntu-latest
steps:
- name: Check for labels
uses: actions/github-script@v6
with:
script: |
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 @@ -27,6 +27,7 @@
}
},
"scripts": {
"ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=ci --configuration phpunit.xml"
"ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=ci --configuration phpunit.xml",
"phpstan": "vendor/bin/phpstan analyse --configuration phpstan.neon"
}
}
10 changes: 10 additions & 0 deletions src/Attributes/PropertyAttributes/Computed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Nuxtifyts\PhpDto\Attributes\PropertyAttributes;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Computed
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class ArrayOfBackedEnums
{
/** @var ReflectionEnum */
/** @var array<string, ReflectionEnum<BackedEnum>> */
private static array $_enumReflections = [];

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

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

/**
* @param class-string<BackedEnum>|list<class-string<BackedEnum>> $enums
Expand Down
8 changes: 4 additions & 4 deletions src/Attributes/PropertyAttributes/Types/ArrayOfData.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class ArrayOfData
{
/** @var ReflectionClass */
/** @var array<string, ReflectionClass<Data>> */
private static array $_dataReflections = [];

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

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

/**
* @param class-string<Data>|list<class-string<Data>> $dataClasses
Expand Down
8 changes: 4 additions & 4 deletions src/Attributes/PropertyAttributes/Types/ArrayOfDateTimes.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class ArrayOfDateTimes
{
/** @var ReflectionClass */
/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
private static array $_dateTimeReflections = [];

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

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

/**
* @param class-string<DateTime|DateTimeImmutable>|list<class-string<DateTime|DateTimeImmutable>> $dateTimes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class ArrayOfScalarTypes
{
/** @var list<Type> $types */
private(set) public array $types;
private(set) array $types;

/**
* @param Type|list<Type> $types
Expand Down
101 changes: 51 additions & 50 deletions src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,41 +29,64 @@ final public static function from(mixed $value): static

/** @var ClassContext<static> $context */
$context = ClassContext::getInstance(new ReflectionClass(static::class));
$instance = $context->newInstanceWithoutConstructor();

foreach ($context->properties as $propertyContext) {
$serializers = $propertyContext->serializers();
return $context->hasComputedProperties
? static::instanceWithConstructorCallFrom($context, $value)
: static::instanceWithoutConstructorFrom($context, $value);
} catch (Throwable $e) {
throw new DeserializeException($e->getMessage(), $e->getCode(), $e);
}
}

if (!$serializers) {
throw new DeserializeException(
code: DeserializeException::NO_SERIALIZERS_ERROR_CODE
);
}
/**
* @param ClassContext<static> $context
* @param array<string, mixed> $value
*
* @throws Throwable
*/
protected static function instanceWithoutConstructorFrom(ClassContext $context, array $value): static
{
$instance = $context->newInstanceWithoutConstructor();

$propertyName = $propertyContext->propertyName;
$propertyDeserialized = false;
foreach ($serializers as $serializer) {
try {
$propertyValue = $serializer->deserialize($propertyContext, $value);
foreach ($context->properties as $propertyContext) {
$propertyName = $propertyContext->propertyName;

$instance->{$propertyName} = $propertyValue;
$instance->{$propertyName} = $propertyContext->deserializeFrom($value);
}

$propertyDeserialized = true;
return $instance;
}

break;
} catch (DeserializeException) {
}
}
/**
* @param ClassContext<static> $context
* @param array<string, mixed> $value
*
* @throws Throwable
*/
protected static function instanceWithConstructorCallFrom(ClassContext $context, array $value): static
{
/** @var array<string, mixed> $args */
$args = [];

if (!$propertyDeserialized) {
throw new DeserializeException("Could not deserialize value for property: $propertyName");
}
foreach ($context->constructorParams as $paramName) {
$propertyContext = $context->properties[$paramName] ?? null;

if (!$propertyContext) {
throw new DeserializeException(
"Could not find property context for constructor param: $paramName"
);
}

return $instance;
} catch (Throwable $e) {
throw new DeserializeException($e->getMessage(), $e->getCode(), $e);
$args[$paramName] = $propertyContext->deserializeFrom($value);
}

$instance = $context->newInstanceWithConstructorCall(...$args);

if (!$instance instanceof static) {
throw new DeserializeException('Could not create instance of ' . static::class);
}

return $instance;
}

/**
Expand All @@ -77,36 +100,14 @@ final public function jsonSerialize(): array
$context = ClassContext::getInstance(new ReflectionClass($this));

$serializableArray = [];

foreach ($context->properties as $propertyContext) {
$serializers = $propertyContext->serializers();

if (!$serializers) {
throw new SerializeException(
code: SerializeException::NO_SERIALIZERS_ERROR_CODE
);
if ($propertyContext->isComputed) {
continue;
}

$propertyName = $propertyContext->propertyName;
$propertySerialized = false;
foreach ($serializers as $serializer) {
try {
$propertyValue = $serializer->serialize($propertyContext, $this);

$serializableArray[$propertyName] = $propertyValue[$propertyName];

$propertySerialized = true;

break;
} catch (SerializeException) {
}
}

if (!$propertySerialized) {
throw new SerializeException(
"Could not serialize property: $propertyName",
);
}
$serializableArray[$propertyName] = $propertyContext->serializeFrom($this)[$propertyName];
}

return $serializableArray;
Expand Down
30 changes: 25 additions & 5 deletions src/Contexts/ClassContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;

/**
* @template T of object
Expand All @@ -22,7 +23,10 @@ class ClassContext
/**
* @var array<string, PropertyContext>
*/
protected readonly array $_properties;
protected(set) readonly array $properties;

/** @var list<string> List of param names */
public readonly array $constructorParams;

/**
* @param ReflectionClass<T> $_reflectionClass
Expand All @@ -32,12 +36,20 @@ class ClassContext
final private function __construct(
protected readonly ReflectionClass $_reflectionClass
) {
$this->_properties = self::getPropertyContexts($this->_reflectionClass);
$this->properties = self::getPropertyContexts($this->_reflectionClass);
$this->constructorParams = array_map(
static fn (ReflectionParameter $param) => $param->getName(),
$this->_reflectionClass->getConstructor()?->getParameters() ?? [],
);
}

/** @var array<string, PropertyContext> */
public array $properties {
get => $this->_properties;
public bool $hasComputedProperties {
get => count(
array_filter(
$this->properties,
static fn (PropertyContext $property) => $property->isComputed
)
) > 0;
}

/**
Expand Down Expand Up @@ -86,4 +98,12 @@ public function newInstanceWithoutConstructor(): mixed
{
return $this->_reflectionClass->newInstanceWithoutConstructor();
}

/**
* @throws ReflectionException
*/
public function newInstanceWithConstructorCall(mixed ...$args): mixed
{
return $this->_reflectionClass->newInstance(...$args);
}
}
Loading
Loading