Skip to content

Commit 70abf1e

Browse files
committed
Merge remote-tracking branch 'origin/master' into featrue/open-api
2 parents 71c4a93 + 06c5b89 commit 70abf1e

27 files changed

+159
-84
lines changed

.github/workflows/phpstan.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: PHPStan
2+
3+
on:
4+
push:
5+
paths:
6+
- '**.php'
7+
pull_request:
8+
paths:
9+
- '**.php'
10+
jobs:
11+
phpstan:
12+
name: phpstan
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup PHP
18+
uses: shivammathur/setup-php@v2
19+
with:
20+
php-version: '8.4'
21+
coverage: none
22+
23+
- name: Install dependencies
24+
run: |
25+
composer update --prefer-dist --no-interaction --no-suggest
26+
27+
- name: Run PHPStan
28+
run: ./vendor/bin/phpstan --error-format=github

.github/workflows/test.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Run Tests
2+
3+
on: ['push', 'pull_request']
4+
5+
permissions:
6+
contents: read
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
php-version: [ 8.1, 8.2, 8.3, 8.4 ]
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: PHP ${{ matrix.php-version }}
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: ${{ matrix.php-version }}
22+
extensions: mbstring, intl, pdo_mysql
23+
coverage: none
24+
25+
- name: Validate composer.json and composer.lock
26+
run: composer validate --strict
27+
28+
- name: Cache Composer packages
29+
uses: actions/cache@v3
30+
with:
31+
path: vendor
32+
key: ${{ runner.os }}-php-${{ hashFiles('composer.lock') }}
33+
restore-keys: |
34+
${{ runner.os }}-php-
35+
36+
- name: Install dependencies
37+
run: composer install --prefer-dist --no-progress
38+
39+
- name: Run Pest tests
40+
run: vendor/bin/pest
41+
42+
- name: Run benchmarks
43+
run: vendor/bin/phpbench run benchmarks/ --bootstrap=vendor/autoload.php

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
[![Packagist Downloads](https://img.shields.io/packagist/dm/astral/php-serialize)](https://packagist.org/packages/astral/php-serialize)
1+
[![Total Downloads](https://img.shields.io/packagist/dt/astral/php-serialize.svg?style=flat-square)](https://packagist.org/packages/astral/php-serialize)
2+
[![Tests](https://github.com/astral-data/php-serialize/actions/workflows/test.yml/badge.svg)](https://github.com/astral-data/php-serialize/actions/workflows/test.yml)
3+
[![PHPStan](https://github.com/astral-data/php-serialize/actions/workflows/phpstan.yml/badge.svg)](https://github.com/astral-data/php-serialize/actions/workflows/phpstan.yml)
24

35
# Languages
46

benchmarks/SerializeBench.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function setupObject(): void
8383
Revs(500),
8484
Iterations(5),
8585
BeforeMethods([ 'setupObjectCreation']),
86-
Assert('mode(variant.time.avg) < 90 microseconds +/- 5%')
86+
Assert('mode(variant.time.avg) < 140 microseconds +/- 5%')
8787
]
8888
public function benchObjectCreation(): void
8989
{
@@ -94,7 +94,7 @@ public function benchObjectCreation(): void
9494
Revs(5000),
9595
Iterations(5),
9696
BeforeMethods([ 'setupObjectCreation']),
97-
Assert('mode(variant.time.avg) < 250 microseconds +/- 5%')
97+
Assert('mode(variant.time.avg) < 350 microseconds +/- 5%')
9898
]
9999
public function benchObjectCreationWithoutCache(): void
100100
{
@@ -106,7 +106,7 @@ public function benchObjectCreationWithoutCache(): void
106106
Revs(500),
107107
Iterations(5),
108108
BeforeMethods(['setupObject']),
109-
Assert('mode(variant.time.avg) < 25 microseconds +/- 5%')
109+
Assert('mode(variant.time.avg) < 80 microseconds +/- 5%')
110110
]
111111
public function benchObjectToArray(): void
112112
{
@@ -117,7 +117,7 @@ public function benchObjectToArray(): void
117117
Revs(5000),
118118
Iterations(5),
119119
BeforeMethods(['setupObject']),
120-
Assert('mode(variant.time.avg) < 150 microseconds +/- 5%')
120+
Assert('mode(variant.time.avg) < 270 microseconds +/- 5%')
121121
]
122122
public function benchObjectToArrayWithoutCache(): void
123123
{

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "astral/php-serialize",
3+
"description": "An advanced PHP serialization tool leveraging attributes for flexible object-to-array and JSON conversion. Supports property aliases, type conversions, and nested object handling. Ideal for APIs, data persistence, and configuration management.",
34
"type": "library",
45
"keywords" : [
56
"php-serialize",
@@ -24,7 +25,7 @@
2425
"psr/simple-cache": "^3.0"
2526
},
2627
"require-dev" : {
27-
"phpstan/phpstan": "^2.0.2",
28+
"phpstan/phpstan": "^2.0.2",
2829
"friendsofphp/php-cs-fixer": "^3.0",
2930
"mockery/mockery": "^1.6",
3031
"phpbench/phpbench": "^1.2",

phpstan.neon

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
parameters:
22
level: 5
3-
mockery:
4-
class: DataCollection
5-
mockClass: Mockery\MockInterface
63
treatPhpDocTypesAsCertain: false
74
paths:
85
- src

src/Annotations/Faker/FakerMethod.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use ReflectionClass;
1111
use ReflectionException;
1212
use ReflectionMethod;
13+
use ReflectionNamedType;
1314
use ReflectionParameter;
15+
use RuntimeException;
1416

1517
#[Attribute(Attribute::TARGET_PROPERTY)]
1618
class FakerMethod implements FakerCastInterface
@@ -30,11 +32,11 @@ public function __construct(
3032
public function resolve(DataCollection $collection): mixed
3133
{
3234
if (!class_exists($this->className)) {
33-
throw new Exception("Class $this->className not found");
35+
throw new RuntimeException("Class $this->className not found");
3436
}
3537

3638
if (!method_exists($this->className, $this->methodName)) {
37-
throw new Exception("Method $this->methodName not found from class $this->className");
39+
throw new RuntimeException("Method $this->methodName not found from class $this->className");
3840
}
3941

4042
$instance = $this->createInstanceWithResolvedConstructor($this->className);
@@ -65,7 +67,7 @@ private function createInstanceWithResolvedConstructor(string $className, array
6567
{
6668
if (in_array($className, $dependencyChain, true)) {
6769
$chain = implode(' -> ', array_merge($dependencyChain, [$className]));
68-
throw new Exception("Circular dependency detected: $chain");
70+
throw new RuntimeException("Circular dependency detected: $chain");
6971
}
7072

7173
$dependencyChain[] = $className;
@@ -79,14 +81,20 @@ private function createInstanceWithResolvedConstructor(string $className, array
7981

8082
return $reflectionClass->newInstanceArgs(array_map(function ($param) use ($dependencyChain) {
8183
$paramType = $param->getType();
82-
$name = $paramType->getName();
84+
85+
if(!$paramType instanceof ReflectionNamedType){
86+
throw new \http\Exception\RuntimeException("$paramType is not ReflectionNamedType");
87+
}
88+
89+
$typeName = $paramType->getName();
90+
8391
return match(true) {
84-
!$paramType->isBuiltin() && class_exists($name) => $this->createInstanceWithResolvedConstructor($name, $dependencyChain),
92+
!$paramType->isBuiltin() && class_exists($typeName) => $this->createInstanceWithResolvedConstructor($typeName, $dependencyChain),
8593
$param->isDefaultValueAvailable() => $param->getDefaultValue(),
8694
default => null,
8795
};
8896

89-
}, $constructor->getParameters()));
97+
}, $constructor?->getParameters()));
9098
}
9199

92100
/**
@@ -103,7 +111,12 @@ private function resolveMethodParameters(ReflectionMethod $method): array
103111

104112
return array_map(function (ReflectionParameter $param) {
105113
$paramType = $param->getType();
106-
$name = $param->getName();
114+
115+
if(!$paramType instanceof ReflectionNamedType){
116+
throw new \http\Exception\RuntimeException("$paramType is not ReflectionNamedType");
117+
}
118+
119+
$name = $param->getName();
107120
$typeName = $paramType->getName();
108121

109122
return match(true) {
@@ -134,7 +147,7 @@ private function extractNestedValue(mixed $result, string $path): mixed
134147
} elseif (is_object($result) && property_exists($result, $key)) {
135148
$result = $result->$key;
136149
} else {
137-
throw new Exception("Unable to extract path '$path' from result");
150+
throw new RuntimeException("Unable to extract path '$path' from result");
138151
}
139152
}
140153
return $result;

src/Annotations/Input/InputDateFormat.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Attribute;
1111
use DateInvalidTimeZoneException;
1212
use DateTime;
13+
use DateTimeImmutable;
1314
use DateTimeInterface;
1415
use DateTimeZone;
1516

@@ -34,16 +35,20 @@ public function match(mixed $value, DataCollection $collection, InputValueContex
3435
*/
3536
public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): string|DateTime
3637
{
38+
3739
$timezone = $this->timezone ? new DateTimeZone($this->timezone) : null;
40+
$className = current($collection->getTypes())->className;
3841

3942
if (!$this->outFormat
43+
&& is_subclass_of($className, DateTimeInterface::class)
44+
&& method_exists($className, 'createFromFormat')
4045
&& count($collection->getTypes()) === 1
41-
&& is_subclass_of(current($collection->getTypes())?->className, DateTimeInterface::class)
4246
) {
43-
return (current($collection->getTypes())?->className)::createFromFormat($this->inputFormat, (string)$value, $timezone);
47+
return $className::createFromFormat($this->inputFormat, (string)$value, $timezone);
4448
}
4549

4650
$dateTime = DateTime::createFromFormat($this->inputFormat, (string)$value, $timezone);
4751
return $dateTime !== false ? $dateTime->format($this->outFormat) : (string)$value;
52+
4853
}
4954
}

src/Casts/InputValue/InputValueEnumCast.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Astral\Serialize\Enums\TypeKindEnum;
99
use Astral\Serialize\Exceptions\ValueCastError;
1010
use Astral\Serialize\Support\Collections\DataCollection;
11+
use Astral\Serialize\Support\Collections\TypeCollection;
1112
use Astral\Serialize\Support\Context\InputValueContext;
1213
use BackedEnum;
1314
use UnitEnum;
@@ -24,7 +25,6 @@ public function match(mixed $value, DataCollection $collection, InputValueContex
2425
*/
2526
public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): UnitEnum|string
2627
{
27-
2828
$types = $collection->getTypes();
2929
foreach ($types as $type) {
3030
if ($type->kind !== TypeKindEnum::ENUM) {
@@ -41,7 +41,7 @@ public function resolve(mixed $value, DataCollection $collection, InputValueCont
4141
sprintf(
4242
'Enum value "%s" not found in EnumClass: %s',
4343
$value,
44-
current($types)?->className
44+
current($types)->className
4545
)
4646
);
4747
}

src/Enums/TypeKindEnum.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Astral\Serialize\Enums;
44

5+
use http\Exception\RuntimeException;
6+
57
enum TypeKindEnum
68
{
79
case MIXED;
@@ -63,6 +65,7 @@ public static function getNameTo(string $type, ?string $className = null): self
6365
'array' => self::ARRAY,
6466
'object' => self::OBJECT,
6567
'mixed' => self::MIXED,
68+
default => throw new RuntimeException("not found type $type"),
6669
};
6770
}
6871
}

0 commit comments

Comments
 (0)