Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Sep 15, 2023
2 parents 8dbd8a7 + 6052b9c commit 8100f56
Show file tree
Hide file tree
Showing 53 changed files with 1,022 additions and 185 deletions.
Binary file removed .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
name: phpstan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/php-cs-fixer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
name: phpstan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: main

Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,31 @@ All notable changes to `laravel-data` will be documented in this file.
- Allow creating data objects using `from` without parameters
- Add support for a Dto and Resource object

## 3.9.0 - 2023-09-15

- Fix an issue where computed values could not be set as null
- Fix for no rules created on optional|nullable Data object and Collection (#532)
- Add `CustomValidationAttribute`'s
- Copy partial trees when using array access on a collection

## 3.8.1 - 2023-08-11

- fix abstract json cast format

## 3.8.0 - 2023-08-09

- Add Hidden Attribute (#505)
- Add Null value support for RequiredUnless Validation (#525)
- Add abstract eloquent casts (#526)

## 3.7.1 - 2023-08-04

- fix target namespace when creating files with Laravel Idea (#497)
- allow collection to be created passing null (#507)
- add Ulid validation rule (#510)
-add TARGET_PARAMETER to Attribute for improved Validation (#523)
>>>>>>> main
## 3.7.0 - 2023-07-05

- Add support for better exception messages when parameters are missing
Expand Down
4 changes: 2 additions & 2 deletions docs/advanced-usage/creating-a-cast.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ class Email implements Castable

}

public static function dataCastUsing(...$arguments)
public static function dataCastUsing(...$arguments): Cast
{
return new class implements Cast {
public function cast(DataProperty $property, mixed $value, array $context): mixed {
return new self($value);
return new Email($value);
}
};
}
Expand Down
90 changes: 89 additions & 1 deletion docs/advanced-usage/eloquent-casting.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Eloquent casting
weight: 1
---

Since data objects can be created from array's and be easily transformed into array's back again, they are excellent to be used
Since data objects can be created from arrays and be easily transformed into arrays back again, they are excellent to be used
with [Eloquent casts](https://laravel.com/docs/8.x/eloquent-mutators#custom-casts):

```php
Expand Down Expand Up @@ -40,6 +40,94 @@ This will internally be converted to a data object which you can later retrieve
Song::findOrFail($id)->artist; // ArtistData object
```

### Abstract data objects

Sometimes you have an abstract parent data object with multiple child data objects, for example:

```php
abstract class RecordConfig extends Data
{
public function __construct(
public int $tracks,
) {}
}

class CdRecordConfig extends RecordConfig
{
public function __construct(
int $tracks
public int $bytes,
) {
parent::__construct($tracks);
}
}

class VinylRecordConfig extends RecordConfig
{
public function __construct(
int $tracks
public int $rpm,
) {
parent::__construct($tracks);
}
}
```

A model can have a JSON field which is either one of these data objects:

```php
class Record extends Model
{
protected $casts = [
'config' => RecordConfig::class,
];
}
```

You can then store either a `CdRecordConfig` or a `VinylRecord` in the `config` field:

```php
$cdRecord = Record::create([
'config' => new CdRecordConfig(tracks: 12, bytes: 1000),
]);

$vinylRecord = Record::create([
'config' => new VinylRecordConfig(tracks: 12, rpm: 33),
]);

$cdRecord->config; // CdRecordConfig object
$vinylRecord->config; // VinylRecordConfig object
```

When a data object class is abstract and used as an Eloquent cast then this feature will work out of the box.

The child data object value of the model will be stored in the database as a JSON string with the class name as the key:

```json
{
"type": "\\App\\Data\\CdRecordConfig",
"value": {
"tracks": 12,
"bytes": 1000
}
}
```

When retrieving the model, the data object will be instantiated based on the `type` key in the JSON string.

#### Abstract data class morphs

By default, the `type` key in the JSON string will be the fully qualified class name of the child data object. This can break your application quite easily when you refactor your code. To prevent this, you can add a morph map like with [Eloquent models](https://laravel.com/docs/10.x/eloquent-relationships#polymorphic-relationships). Within your `AppServiceProvivder` you can add the following mapping:

```php
use Spatie\LaravelData\Support\DataConfig;

app(DataConfig::class)->enforceMorphMap([
'cd_record_config' => CdRecordConfig::class,
'vinyl_record_config' => VinylRecordConfig::class,
]);
```

## Casting data collections

It is also possible to store data collections in an Eloquent model:
Expand Down
13 changes: 9 additions & 4 deletions docs/advanced-usage/validation-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,24 @@ class SongData extends Data

## Creating your validation attribute

A validation attribute is a class that extends `ValidationRule` and returns an array of validation rules when the `getRules` method is called:
It is possible to create your own validation attribute by extending the `CustomValidationAttribute` class, this class has a `getRules` method that returns the rules that should be applied to the property.

```php
#[Attribute(Attribute::TARGET_PROPERTY)]
class CustomRule extends ValidationRule
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class CustomRule extends CustomValidationAttribute
{
public function getRules(): array
/**
* @return array<object|string>|object|string
*/
public function getRules(ValidationPath $path): array|object|string;
{
return [new CustomRule()];
}
}
```

Quick note: you can only use these rules as an attribute, not as a class rule within the static `rules` method of the data class.

## Available validation attributes

### Accepted
Expand Down
10 changes: 6 additions & 4 deletions docs/as-a-data-transfer-object/casts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ class SongData extends Data
The `Format` property here is an `Enum` and looks like this:

```php
enum Format {
case cd;
case vinyl;
case cassette;
enum Format: string {
case cd = 'cd';
case vinyl = 'vinyl';
case cassette = 'cassette';
}
```

Expand Down Expand Up @@ -114,6 +114,8 @@ class SongData extends Data
}
```

Tip: we can also remove the `EnumCast` since the package will automatically cast enums because they're a native PHP type, but this made the example easy to understand.

## Creating your own casts

It is possible to create your casts. You can read more about this in the [advanced chapter](/docs/laravel-data/v3/advanced-usage/creating-a-cast).
2 changes: 1 addition & 1 deletion docs/as-a-data-transfer-object/creating-a-data-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ SongData::from(Song::firstOrFail($id));
Data can also be created from JSON strings:

```php
SongData::from('{"name" : "Never Gonna Give You Up","artist" : "Rick Astley"}');
SongData::from('{"title" : "Never Gonna Give You Up","artist" : "Rick Astley"}');
```

The package will find the required properties within the model and use them to construct the data object.
Expand Down
2 changes: 1 addition & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Changelog
weight: 6
weight: 7
---

All notable changes to laravel-data are documented [on GitHub](https://github.com/spatie/laravel-data/blob/main/CHANGELOG.md)
2 changes: 1 addition & 1 deletion docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ PostData::from(Post::findOrFail($id));

## Using requests

Let's say we have a Laravel request coming from the front with these properties. Our controller would then would validate these properties and then it would store them in a model; this can be done as such:
Let's say we have a Laravel request coming from the front with these properties. Our controller would then validate these properties and then it would store them in a model; this can be done as such:

```php
class DataController
Expand Down
2 changes: 1 addition & 1 deletion docs/questions-issues.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Questions and issues
weight: 5
weight: 6
---

Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving Laravel Data? Feel free to [create an issue on GitHub](https://github.com/spatie/laravel-data/issues), we'll try to address it as soon as possible.
Expand Down
10 changes: 10 additions & 0 deletions docs/third-party-packages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Third party packages
weight: 5
---

Some community members created packages that extend the functionality of Laravel Data. Here's a list of them:

- [laravel-data-openapi-generator](https://github.com/xolvionl/laravel-data-openapi-generator)

Created a package yourself that you want to add to this list? Send us a PR!
10 changes: 10 additions & 0 deletions src/Attributes/Hidden.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Spatie\LaravelData\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Hidden
{
}
14 changes: 14 additions & 0 deletions src/Attributes/Validation/CustomValidationAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Spatie\LaravelData\Attributes\Validation;

use Spatie\LaravelData\Support\Validation\ValidationPath;
use Spatie\LaravelData\Support\Validation\ValidationRule;

abstract class CustomValidationAttribute extends ValidationRule
{
/**
* @return array<object|string>|object|string
*/
abstract public function getRules(ValidationPath $path): array|object|string;
}
4 changes: 2 additions & 2 deletions src/Attributes/Validation/RequiredUnless.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class RequiredUnless extends StringValidationAttribute implements RequiringRule
protected string|array $values;

public function __construct(
string|FieldReference $field,
array|string|BackedEnum|RouteParameterReference ...$values
string|FieldReference $field,
null|array|string|BackedEnum|RouteParameterReference ...$values
) {
$this->field = $this->parseFieldReference($field);
$this->values = Arr::flatten($values);
Expand Down
8 changes: 8 additions & 0 deletions src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ public static function prepareForPipeline(Collection $properties): Collection
return $properties;
}

public function getMorphClass(): string
{
/** @var class-string<\Spatie\LaravelData\Contracts\BaseData> $class */
$class = static::class;

return app(DataConfig::class)->morphMap->getDataClassAlias($class) ?? $class;
}

public function __sleep(): array
{
return app(DataConfig::class)->getDataClass(static::class)
Expand Down
3 changes: 3 additions & 0 deletions src/Contracts/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ public static function normalizers(): array;
public static function prepareForPipeline(\Illuminate\Support\Collection $properties): \Illuminate\Support\Collection;

public static function pipeline(): DataPipeline;
public static function empty(array $extra = []): array;

public function getMorphClass(): string;
}
10 changes: 9 additions & 1 deletion src/DataCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
use Spatie\LaravelData\Concerns\WrappableData;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Contracts\DataCollectable;
use Spatie\LaravelData\Contracts\IncludeableData as IncludeableDataContract;
use Spatie\LaravelData\Exceptions\CannotCastData;
use Spatie\LaravelData\Exceptions\InvalidDataCollectionOperation;
use Spatie\LaravelData\Support\EloquentCasts\DataCollectionEloquentCast;
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;

/**
* @template TKey of array-key
Expand Down Expand Up @@ -103,7 +105,13 @@ public function offsetGet($offset): mixed
throw InvalidDataCollectionOperation::create();
}

return $this->items->offsetGet($offset);
$data = $this->items->offsetGet($offset);

if ($data instanceof IncludeableDataContract) {
$data->getDataContext()->partialsDefinition->merge($this->getPartialsDefinition());
}

return $data;
}

/**
Expand Down
Loading

0 comments on commit 8100f56

Please sign in to comment.