Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fab2s committed Apr 26, 2024
1 parent af44ed4 commit 941e923
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 12 deletions.
90 changes: 86 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI](https://github.com/fab2s/laravel-dt0/actions/workflows/ci.yml/badge.svg)](https://github.com/fab2s/laravel-dt0/actions/workflows/ci.yml) [![QA](https://github.com/fab2s/laravel-dt0/actions/workflows/qa.yml/badge.svg)](https://github.com/fab2s/laravel-dt0/actions/workflows/qa.yml) [![codecov](https://codecov.io/gh/fab2s/laravel-dt0/graph/badge.svg?token=YE6AYEDA64)](https://codecov.io/gh/fab2s/laravel-dt0) [![Latest Stable Version](http://poser.pugx.org/fab2s/laravel-dt0/v)](https://packagist.org/packages/fab2s/laravel-dt0) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com) [![License](http://poser.pugx.org/fab2s/dt0/license)](https://packagist.org/packages/fab2s/dt0)

Laravel support for [fab2s/dt0](https://github.com/fab2s/dt0), a DTO (_Data-Transport-Object_) PHP implementation than can both secure mutability and implement convenient ways to take control over input and output in various formats.
[Laravel](https://laravel.com/) support for [fab2s/dt0](https://github.com/fab2s/dt0), a [DTO](https://en.wikipedia.org/wiki/Data_transfer_object) (_Data-Transport-Object_) PHP implementation that can both secure mutability and implement convenient ways to take control over input and output in various formats.

## Installation

Expand All @@ -12,19 +12,101 @@ Laravel support for [fab2s/dt0](https://github.com/fab2s/dt0), a DTO (_Data-Tran
composer require "fab2s/laravel-dt0"
```

## Usage

`Laravel Dt0` only adds Validation implementation and model attribute casting to `Dt0`. All other features will work exactly the same. Have a look at [`Dt0`](https://github.com/fab2s/dt0) to find out more.

## Validation

Laravel `Dt0` is able to leverage the full power of Laravel validation on each of its public properties. The validation is performed on the input data prior to any property casting or instantiation.
`Laravel Dt0` is able to leverage the full power of Laravel validation on each of its public properties. The validation is performed on the input data prior to any property casting or instantiation.

```php
`Laravel Dt0` comes with a [`Validator`](./src/Validator.php) out of the box that can leverage the full power of [laravel validation](https://laravel.com/docs/master/validation).

To use it on any `Dt0`, just add the [`Validate`](https://github.com/fab2s/dt0/blob/main/src/Attribute/Validate.php) class attribute :

````php
#[Validate(Validator::class)] // same as #[Validate(new Validator)]
class MyDt0 extends Dt0 {
// ...
}
````

### Rules can be added in three ways:

- using the second argument of the [`Validate`](https://github.com/fab2s/dt0/blob/main/src/Attribute/Validate.php) **class attribute**:

````php
use fab2s\Dt0\Attribute\Rule;
use fab2s\Dt0\Attribute\Rules;
use fab2s\Dt0\Attribute\Validate;
use fab2s\Dt0\Laravel\Dt0;
use fab2s\Dt0\Laravel\Validator;

#[Validate(
Validator::class,
new Rules(
propName: new Rule('string|size:2'),
// ...
),
)]
class MyDt0 extends Dt0 {
public readonly string $propName;
}
````

- using the [`Rules`](https://github.com/fab2s/dt0/blob/main/src/Attribute/Rules.php) **class attribute**:

````php
use fab2s\Dt0\Attribute\Rule;
use fab2s\Dt0\Attribute\Rules;
use fab2s\Dt0\Attribute\Validate;
use fab2s\Dt0\Laravel\Dt0;
use fab2s\Dt0\Laravel\Validator;

#[Validate(Validator::class)]
#[Rules(
propName: new Rule(['required', 'string', 'size:2']),
// ...
)]
class MyDt0 extends Dt0 {
public readonly string $propName;
}
````

- using the [`Rule`](https://github.com/fab2s/dt0/blob/main/src/Attribute/Rule.php) **property attribute**:

````php
use fab2s\Dt0\Attribute\Rule;
use fab2s\Dt0\Attribute\Rules;
use fab2s\Dt0\Attribute\Validate;
use fab2s\Dt0\Laravel\Dt0;
use fab2s\Dt0\Laravel\Validator;
use fab2s\Dt0\Laravel\Tests\Artifacts\Rules\Lowercase;

#[Validate(Validator::class)]
class MyDt0 extends Dt0 {
#[Rule(new Lowercase)] // or any custom rule instance
public readonly string $propName;
}
````

Combo of the above three are permitted as illustrated in [`ValidatableDt0`](./tests/Artifacts/ValidatableDt0.php).

> In case of redundancy, priority will be first in `Validate`, `Rules` then `Rule`.
> Dt0 has no opinion of the method used to define rules. They will all perform the same as they are compiled once per process and kept ready for any reuse.

Validation is performed using `withValidation` method:

```php
// either get a Dt0 instance or a ValidationException
$dt0 = SomeValidatableDt0::withValidation(...Request::all());
```

## Model Attribute casting

Should you want to use a `Dt0` as a Laravel Model attribute, you cas use [Dt0Cast](./src/Casts/Dt0Cast.php) to cast it.
Should you want to use a `Dt0` as a Laravel Model attribute, you can directly cast it as your `Dt0` thanks to the generic cast [`Dt0Cast`](./src/Casts/Dt0Cast.php).

Only requirement is for your Dt0 to extend [`fab2s\Dt0\Laravel\Dt0`](./src/Dt0.php) or to extend [`fab2s\Dt0\Dt0`](https://github.com/fab2s/dt0/blob/main/src/Dt0.php) _and_ use [`fab2s\Dt0\Laravel\LaravelDt0Trait`](./src/LaravelDt0Trait.php).

````php
use Illuminate\Database\Eloquent\Model;
Expand Down
69 changes: 69 additions & 0 deletions src/Caster/CollectionOfCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of fab2s/laravel-dt0.
* (c) Fabrice de Stefanis / https://github.com/fab2s/dt0
* This source file is licensed under the MIT license which you will
* find in the LICENSE file or at https://opensource.org/licenses/MIT
*/

namespace fab2s\Dt0\Laravel\Caster;

use fab2s\Dt0\Caster\ArrayType;
use fab2s\Dt0\Caster\CasterInterface;
use fab2s\Dt0\Caster\ScalarType;
use fab2s\Dt0\Caster\ScalarTypeCaster;
use fab2s\Dt0\Dt0;
use fab2s\Dt0\Exception\CasterException;
use fab2s\Dt0\Property\Property;
use Illuminate\Support\Collection;

class CollectionOfCaster implements CasterInterface
{
public readonly ArrayType|ScalarType|string $logicalType;
protected ?ScalarTypeCaster $scalarTypeCaster;

/**
* @throws CasterException
*/
public function __construct(
/** @var class-string<Dt0|UnitEnum>|ScalarType|string */
public readonly ScalarType|string $type,
) {
if (is_string($type)) {
$logicalType = match (true) {
is_subclass_of($type, Dt0::class) => ArrayType::DT0,
is_subclass_of($type, UnitEnum::class) => ArrayType::ENUM,
default => ScalarType::tryFrom($type),
};
} else {
$logicalType = $type;
}

if (! $logicalType) {
throw new CasterException('[' . Dt0::classBasename(static::class) . "] $type is not an ArrayType nor a ScalarType");
}

$this->logicalType = $logicalType;
$this->scalarTypeCaster = $this->logicalType instanceof ScalarType ? new ScalarTypeCaster($this->logicalType) : null;
}

public function cast(mixed $value): ?Collection
{
if (! is_iterable($value)) {
return null;
}

$result = Collection::make();

foreach ($value as $item) {
$result->push(match ($this->logicalType) {
ArrayType::DT0 => $this->type::tryFrom($item),
ArrayType::ENUM => Property::tryEnum($this->type, $item),
default => $this->scalarTypeCaster->cast($item),
});
}

return $result;
}
}
7 changes: 1 addition & 6 deletions src/Dt0.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@
namespace fab2s\Dt0\Laravel;

use fab2s\Dt0\Dt0 as BaseDt0;
use fab2s\Dt0\Laravel\Casts\Dt0Cast;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Support\Arrayable;

abstract class Dt0 extends BaseDt0 implements Arrayable, Castable
{
public static function castUsing(array $arguments): CastsAttributes
{
return new Dt0Cast(static::class, ...$arguments);
}
use LaravelDt0Trait;
}
21 changes: 21 additions & 0 deletions src/LaravelDt0Trait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of fab2s/laravel-dt0.
* (c) Fabrice de Stefanis / https://github.com/fab2s/dt0
* This source file is licensed under the MIT license which you will
* find in the LICENSE file or at https://opensource.org/licenses/MIT
*/

namespace fab2s\Dt0\Laravel;

use fab2s\Dt0\Laravel\Casts\Dt0Cast;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

trait LaravelDt0Trait
{
public static function castUsing(array $arguments): CastsAttributes
{
return new Dt0Cast(static::class, ...$arguments);
}
}
3 changes: 2 additions & 1 deletion src/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
namespace fab2s\Dt0\Laravel;

use fab2s\Dt0\Attribute\Rule;
use fab2s\Dt0\Validator\ValidatorInterface;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
use Illuminate\Validation\ValidationException;

class Validator implements \fab2s\Dt0\Validator\ValidatorInterface
class Validator implements ValidatorInterface
{
public array $rules = [];

Expand Down
2 changes: 1 addition & 1 deletion tests/Artifacts/ValidatableDt0.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use fab2s\Dt0\Laravel\Validator;

#[Validate(
new Validator,
Validator::class,
new Rules(
string: new Rule(new Lowercase),
),
Expand Down
91 changes: 91 additions & 0 deletions tests/Caster/CollectionOfCasterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

/*
* This file is part of fab2s/laravel-dt0.
* (c) Fabrice de Stefanis / https://github.com/fab2s/dt0
* This source file is licensed under the MIT license which you will
* find in the LICENSE file or at https://opensource.org/licenses/MIT
*/

namespace fab2s\Dt0\Laravel\Tests\Caster;

use Exception;
use fab2s\Dt0\Caster\ScalarType;
use fab2s\Dt0\Exception\CasterException;
use fab2s\Dt0\Laravel\Caster\CollectionOfCaster;
use fab2s\Dt0\Laravel\Tests\Artifacts\DumbDt0;
use fab2s\Dt0\Laravel\Tests\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;

class CollectionOfCasterTest extends TestCase
{
/**
* @throws Exception
*/
#[DataProvider('castProvider')]
public function test_cast(ScalarType|string $type, $value, $expected): void
{
$caster = new CollectionOfCaster($type);
$casted = $caster->cast($value);

$this->assertSame(json_encode($expected), json_encode($caster->cast($value)));
}

public function test_exception(): void
{
$this->expectException(CasterException::class);
new CollectionOfCaster('NotAType');
}

public static function castProvider(): array
{
return [
[
'type' => DumbDt0::class,
'value' => [
DumbDt0::make(prop1: 'ONE', prop2: 'ONE', prop3: 'ONE'),
['prop1' => 'TWO', 'prop2' => 'TWO', 'prop3' => 'TWO'],
'{"prop1":"three","prop2":"three","prop3":"three"}',
],
'expected' => collect([
DumbDt0::make(prop1: 'ONE', prop2: 'ONE', prop3: 'ONE'),
DumbDt0::make(prop1: 'TWO', prop2: 'TWO', prop3: 'TWO'),
DumbDt0::make(prop1: 'three', prop2: 'three', prop3: 'three'),
]),
],
[
'type' => 'string',
'value' => collect([
'ONE',
'TWO',
'three',
]),
'expected' => collect([
'ONE',
'TWO',
'three',
]),
],
[
'type' => 'int',
'value' => [
null,
'42',
42.42,
'1337.1337',
],
'expected' => collect([
0,
42,
42,
1337,
]),
],
[
'type' => ScalarType::bool,
'value' => null,
'expected' => null,
],
];
}
}

0 comments on commit 941e923

Please sign in to comment.