json-api-server/docs/writing.md

127 lines
4.2 KiB
Markdown

# Field Writability
By default, fields are read-only. You can allow a field to be written to in `PATCH` and `POST` requests using the `writable` and `readonly` methods.
You can optionally supply a closure to these methods which will receive the model instance, and should return a boolean value.
For example, the following schema will make an email attribute that is only writable by the self:
```php
$type->attribute('email')
->writable(function ($model, Request $request, Attribute $field) {
return $model->id === $request->getAttribute('userId');
});
```
## Writable Once
You may want a field to only be writable when creating a new resource, but not when an existing resource is being updated. This can be achieved by calling the `once` method:
```php
$type->hasOne('author')
->writable()->once();
```
## Default Values
You can provide a default value to be used when creating a new resource if there is no value provided by the consumer. Pass a value or a closure to the `default` method:
```php
$type->attribute('joinedAt')
->default(new DateTime);
$type->attribute('ipAddress')
->default(function (Request $request, Attribute $attribute) {
return $request->getServerParams()['REMOTE_ADDR'] ?? null;
});
```
::: tip
If you're using Eloquent, you could also define [default attribute values](https://laravel.com/docs/8.x/eloquent#default-attribute-values) to achieve a similar thing. However, the Request instance will not be available in this context.
:::
## Validation
You can ensure that data provided for a field is valid before the resource is saved. Provide a closure to the `validate` method, and call the first argument if validation fails:
```php
$type->attribute('email')
->validate(function (callable $fail, $value, $model, Request $request, Attribute $attribute) {
if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
$fail('Invalid email');
}
});
```
::: tip
You can easily use Laravel's [Validation](https://laravel.com/docs/8.x/validation) component for field validation with the [`rules` helper function](laravel.md#validation).
:::
This works for relationships, too. The related models will be retrieved via your adapter and passed into your validation function.
```php
$type->hasMany('groups')
->validate(function (callable $fail, array $groups, $model, Request $request, Attribute $attribute) {
foreach ($groups as $group) {
if ($group->id === 1) {
$fail('You cannot assign this group');
}
}
});
```
## Transformers
Use the `transform` method on an attribute to mutate any incoming value before it is saved to the model.
```php
$type->attribute('firstName')
->transform(function ($value, Request $request, Attribute $attribute) {
return ucfirst($value);
});
```
::: tip
If you're using Eloquent, you could also define attribute [casts](https://laravel.com/docs/8.x/eloquent-mutators#attribute-casting) or [mutators](https://laravel.com/docs/8.x/eloquent-mutators#defining-a-mutator) on your model to achieve a similar thing.
:::
## Setters
Use the `set` method to define custom mutation logic for your field, instead of just setting the value straight on the model property.
```php
$type->attribute('firstName')
->set(function ($value, $model, Request $request, Attribute $attribute) {
$model->first_name = ucfirst($value);
if ($model->first_name === 'Toby') {
$model->last_name = 'Zerner';
}
});
```
## Savers
If your field corresponds to some other form of data storage rather than a simple property on your model, you can use the `save` method to provide a closure that will be run _after_ your model has been successfully saved. If specified, the adapter will NOT be used to set the field on the model.
```php
$type->attribute('locale')
->save(function ($value, $model, Request $request, Attribute $attribute) {
$model->preferences()
->where('key', 'locale')
->update(['value' => $value]);
});
```
## Events
### `onSaved`
Run after a field has been successfully saved.
```php
$type->attribute('email')
->onSaved(function ($value, $model, Request $request, Attribute $attribute) {
event(new EmailWasChanged($model));
});
```