# 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, Context $context) {
        return $model->id === $context->getRequest()->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 (Context $context) {
        return $context->getRequest()->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, Context $context) {
        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, Context $context) {
        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, Context $context) {
        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, Context $context) {
        $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, Context $context) {
        $model->preferences()
            ->where('key', 'locale')
            ->update(['value' => $value]);
    });
```

## Events

### `saved`

Run after a field has been successfully saved.

```php
$type->attribute('email')
    ->saved(function ($value, $model, Context $context) {
        event(new EmailWasChanged($model));
    });
```
