Skip to content

Commit 3fd7064

Browse files
authored
Merge pull request #3 from Dgame/v0.4.0
Started v0.4.0
2 parents 88686fa + 4db9f09 commit 3fd7064

22 files changed

+902
-72
lines changed

Diff for: .github/workflows/php.yml

+68-68
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,68 @@
1-
name: Validation Workflow
2-
3-
on:
4-
pull_request:
5-
types:
6-
- opened
7-
- synchronize
8-
- reopened
9-
- ready_for_review
10-
paths:
11-
- '**/*.php'
12-
13-
jobs:
14-
lint-and-test:
15-
strategy:
16-
matrix:
17-
operating-system: ['ubuntu-latest']
18-
php-versions: ['8.0']
19-
runs-on: ${{ matrix.operating-system }}
20-
if: github.event.pull_request.draft == false
21-
steps:
22-
- name: Cancel Previous Runs
23-
uses: styfle/[email protected]
24-
with:
25-
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26-
27-
- name: Checkout
28-
uses: actions/checkout@v2
29-
30-
- name: Setup PHP
31-
uses: shivammathur/setup-php@v2
32-
with:
33-
php-version: ${{ matrix.php-versions }}
34-
tools: composer:v2
35-
coverage: none
36-
env:
37-
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38-
39-
- name: Display PHP information
40-
run: |
41-
php -v
42-
php -m
43-
composer --version
44-
- name: Get composer cache directory
45-
id: composer-cache
46-
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
47-
48-
- name: Cache dependencies
49-
uses: actions/cache@v2
50-
with:
51-
path: ${{ steps.composer-cache.outputs.dir }}
52-
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
53-
restore-keys: ${{ runner.os }}-composer-
54-
55-
- name: Run composer validate
56-
run: composer validate
57-
58-
- name: Install dependencies
59-
run: composer install --no-interaction --no-suggest --no-scripts --prefer-dist --ansi
60-
61-
- name: Run phpcstd
62-
run: vendor/bin/phpcstd --ci --ansi
63-
64-
- name: Setup problem matchers for PHPUnit
65-
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
66-
67-
- name: Run Unit tests
68-
run: composer test --ansi
1+
name: Validation Workflow
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- synchronize
8+
- reopened
9+
- ready_for_review
10+
paths:
11+
- '**/*.php'
12+
13+
jobs:
14+
lint-and-test:
15+
strategy:
16+
matrix:
17+
operating-system: ['ubuntu-latest']
18+
php-versions: ['8.0']
19+
runs-on: ${{ matrix.operating-system }}
20+
if: github.event.pull_request.draft == false
21+
steps:
22+
- name: Cancel Previous Runs
23+
uses: styfle/[email protected]
24+
with:
25+
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26+
27+
- name: Checkout
28+
uses: actions/checkout@v2
29+
30+
- name: Setup PHP
31+
uses: shivammathur/setup-php@v2
32+
with:
33+
php-version: ${{ matrix.php-versions }}
34+
tools: composer:v2
35+
coverage: none
36+
env:
37+
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Display PHP information
40+
run: |
41+
php -v
42+
php -m
43+
composer --version
44+
- name: Get composer cache directory
45+
id: composer-cache
46+
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
47+
48+
- name: Cache dependencies
49+
uses: actions/cache@v2
50+
with:
51+
path: ${{ steps.composer-cache.outputs.dir }}
52+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
53+
restore-keys: ${{ runner.os }}-composer-
54+
55+
- name: Run composer validate
56+
run: composer validate
57+
58+
- name: Install dependencies
59+
run: composer install --no-interaction --no-suggest --no-scripts --prefer-dist --ansi
60+
61+
- name: Run phpcstd
62+
run: vendor/bin/phpcstd --ci --ansi
63+
64+
- name: Setup problem matchers for PHPUnit
65+
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
66+
67+
- name: Run Unit tests
68+
run: composer test --ansi

Diff for: CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## v0.4.0 - 10.09.2021
4+
5+
### Added
6+
7+
- [Boolean](/README.md/#Boolean)
8+
- [Date](/README.md/#Date)
9+
- [In](/README.md/#In)
10+
- [Matches](/README.md/#Matches)
11+
- [NotIn](/README.md/#NotIn)
12+
- [Numeric](/README.md/#Numeric)
13+
- [Trim](/README.md/#Trim)
14+
15+
### Changed
16+
17+
- `finalize`of `DataTransferObject` is now private an will be called at the end of `from`.
18+
319
## v0.3.0 - 15.08.2021
420

521
### Added

Diff for: README.md

+138
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,144 @@ $foo = Foo::from([]);
497497
assert($foo->id === 23);
498498
```
499499

500+
## Numeric
501+
502+
You have `int` or `float` properties but aren't sure if those aren't delivered as e.g. `string`? `Numeric` to the rescue! It will translate the value to a numeric representation (to `int` or `float`):
503+
504+
```php
505+
use Dgame\DataTransferObject\Annotation\Numeric;
506+
507+
final class Foo
508+
{
509+
use DataTransfer;
510+
511+
#[Numeric(message: 'id must be numeric')]
512+
public int $id;
513+
}
514+
515+
$foo = Foo::from(['id' => '23']);
516+
assert($foo->id === 23);
517+
```
518+
519+
## Boolean
520+
521+
You have `bool` properties but aren't sure if those aren't delivered as `string` or `int`? `Boolean` can help you with that!
522+
523+
```php
524+
use Dgame\DataTransferObject\Annotation\Boolean;
525+
526+
final class Foo
527+
{
528+
use DataTransfer;
529+
530+
#[Boolean(message: 'checked must be a bool')]
531+
public bool $checked;
532+
#[Boolean(message: 'verified must be a bool')]
533+
public bool $verified;
534+
}
535+
536+
$foo = Foo::from(['checked' => 'yes', 'verified' => 0]);
537+
assert($foo->checked === true);
538+
assert($foo->verified === false);
539+
```
540+
541+
## Date
542+
543+
You want a `DateTime` but got a string? No problem:
544+
545+
```php
546+
use Dgame\DataTransferObject\Annotation\Date;
547+
548+
final class Foo
549+
{
550+
use DataTransfer;
551+
552+
#[Date(format: 'd.m.Y', message: 'Your birthday must be a date')]
553+
public bool $birthday;
554+
}
555+
556+
$foo = Foo::from(['birthday' => '19.09.1979']);
557+
assert($foo->birthday === DateTime::createFromFormat('d.m.Y', '19.09.1979'));
558+
```
559+
560+
561+
## In
562+
563+
Your value must be one of a specific range or enumeration? You can ensure that with `In`:
564+
565+
```php
566+
use Dgame\DataTransferObject\Annotation\In;
567+
568+
final class Foo
569+
{
570+
use DataTransfer;
571+
572+
#[In(values: ['beginner', 'advanced', 'difficult'], message: 'Must be either "beginner", "advanced" or "difficult"')]
573+
public string $difficulty;
574+
}
575+
576+
Foo::from(['difficulty' => 'foo']); // will throw a error, since difficulty is not in the provided values
577+
$foo = Foo::from(['difficulty' => 'advanced']);
578+
assert($foo->difficulty === 'advanced');
579+
```
580+
581+
## NotIn
582+
583+
Your value must **not** be one of a specific range or enumeration? You can ensure that with `NotIn`:
584+
585+
```php
586+
use Dgame\DataTransferObject\Annotation\NotIn;
587+
588+
final class Foo
589+
{
590+
use DataTransfer;
591+
592+
#[NotIn(values: ['holy', 'shit', 'wtf'], message: 'Must not be a swear word')]
593+
public string $word;
594+
}
595+
```
596+
597+
## Matches
598+
599+
You must be sure that your values match a specific pattern? You can do that for **all scalar** values by using `Matches`:
600+
601+
```php
602+
use Dgame\DataTransferObject\Annotation\Matches;
603+
604+
final class Foo
605+
{
606+
use DataTransfer;
607+
608+
#[Matches(pattern: '/^[a-z]+\w*/', message: 'Your name must start with a-z')]
609+
public string $name;
610+
611+
#[Matches(pattern: '/[1-9][0-9]+/', message: 'products must be at least 10')]
612+
public int $products;
613+
}
614+
615+
Foo::from(['name' => '_', 'products' => 99]); // will throw a error, since name does not start with a-z
616+
Foo::from(['name' => 'John', 'products' => 9]); // will throw a error, since products must be at least 10
617+
```
618+
619+
## Trim
620+
621+
You have to make sure, that `string` values are trimmed? No worries, we have `Trim`:
622+
623+
```php
624+
use Dgame\DataTransferObject\Annotation\Trim;
625+
626+
final class Foo
627+
{
628+
use DataTransfer;
629+
630+
#[Trim]
631+
public string $name;
632+
}
633+
634+
$foo = Foo::from(['name' => ' John ']);
635+
assert($foo->name === 'John');
636+
```
637+
500638
## Path
501639

502640
Did you ever wanted to extract a value from a provided array? `Path` to the rescue:

Diff for: composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
],
1717
"require": {
1818
"php": "^8.0",
19+
"dgame/php-cast": "^0.1.0",
1920
"dgame/php-type": "^1.0",
2021
"thecodingmachine/safe": "^1.3"
2122
},

Diff for: phpstan.neon

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ parameters:
1414
checkTooWideReturnTypesInProtectedAndPublicMethods: true
1515
checkUninitializedProperties: true
1616
checkMissingCallableSignature: true
17+
checkExplicitMixed: true
1718
level: max
1819
paths:
1920
- src

Diff for: src/Annotation/Boolean.php

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dgame\DataTransferObject\Annotation;
6+
7+
use Attribute;
8+
use function Dgame\Cast\Assume\bool;
9+
use ReflectionProperty;
10+
11+
#[Attribute(Attribute::TARGET_PROPERTY)]
12+
final class Boolean implements Validation, Transformation
13+
{
14+
public function __construct(private ?string $message = null)
15+
{
16+
}
17+
18+
public function validate(mixed $value, ValidationStrategy $validationStrategy): void
19+
{
20+
if (bool($value) === null) {
21+
$validationStrategy->setFailure(
22+
strtr(
23+
$this->message ?? 'Value {value} of {path} is not a bool',
24+
['{value}' => var_export($value, true)]
25+
)
26+
);
27+
}
28+
}
29+
30+
public function transform(mixed $value, ReflectionProperty $property): mixed
31+
{
32+
return bool($value) ?? $value;
33+
}
34+
}

Diff for: src/Annotation/Date.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dgame\DataTransferObject\Annotation;
6+
7+
use Attribute;
8+
use DateTime;
9+
use DateTimeZone;
10+
11+
#[Attribute(Attribute::TARGET_PROPERTY)]
12+
final class Date implements Validation
13+
{
14+
public function __construct(private ?string $format = null, private ?int $timezone = null, private ?string $message = null)
15+
{
16+
}
17+
18+
public function validate(mixed $value, ValidationStrategy $validationStrategy): void
19+
{
20+
$dt = null;
21+
if ($this->format !== null) {
22+
/** @phpstan-ignore-next-line => short ternary */
23+
$dt = DateTime::createFromFormat($this->format, $value) ?: null;
24+
} else {
25+
/** @phpstan-ignore-next-line => short ternary */
26+
$info = date_parse($value) ?: [];
27+
if (($info['error_count'] ?? 0) === 0 && ($info['warning_count'] ?? 0) === 0) {
28+
/** @phpstan-ignore-next-line */
29+
$dt = new DateTime($value, $this->timezone === null ? null : new DateTimeZone($this->timezone));
30+
}
31+
}
32+
33+
if ($dt === null) {
34+
$validationStrategy->setFailure(
35+
strtr(
36+
$this->message ?? '{value} of {path} is not a Date',
37+
['{value}' => var_export($value, true)]
38+
)
39+
);
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)