laravel icon indicating copy to clipboard operation
laravel copied to clipboard

Question: How do i create a write-only field?

Open preliot opened this issue 3 years ago • 8 comments

The question boils down to this: How do i create a write-only field?

Right now I have a field catachall that should be write-only for a super user. But that field seems to be writable no matter what i do. Even if I force always readonly() or change the field type f.e. to Str, it still allows creation of the resource.

MailAddressSchema.php

    public function fields(): array
    {
        return [
            //..
            
            Boolean::make('catchall')
                ->sortable()
                ->readOnly(static fn ($request): bool => !$request->user()->isSuperUser()),

            //..

        ];
    }

MailAddressResource.php

    public function attributes($request): iterable
    {
        $attributes = [
            //..
            'name' => $this->name,
            //..
        ];

        if ($request->user()->isSuperUser()) {
            $attributes['catchall'] = $this->catchall;
        }

        return $attributes;
    }

MailAddressRequest.php

class MailAddressRequest extends ResourceRequest
{
    /**
     * Get the validation rules for the resource.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            //..
            'name' => ['required', 'string', 'max:255'],
            //..
            'catchall' => 'nullable|boolean',
        ];
    }
}

Policies

Policies allow all actions on this resource, which is what i want.

preliot avatar May 11 '22 06:05 preliot

  • laravel-json-api/laravel: 2.3.0
  • laravel/framework: 8.83.9

preliot avatar May 11 '22 06:05 preliot

I've 'fixed' this by using this rule (but i wonder if this is the right way):

    public function rules(): array
    {
        $user = Auth::user();

        return [
\\...
            'catchall' => $user && $user->isSuperUser() ? 'nullable|boolean' : 'prohibited',
\\...
        ];
    }

preliot avatar May 11 '22 07:05 preliot

Thanks for raising this. I think your solution is a good one for now.

There's actually two things going on here:

  1. you're only serializing the attribute if the user is a super user
  2. you only want to fill the attribute with a value if the user is a super user.

So the field itself isn't really "write-only", it has conditional visibility and is conditionally written. I'll need to think about whether there's a better way of implementing this.

FYI the "conditional visibility" is definitely already support via a resource class, which I see you've written. Your MailAddressResource class could be a lot simpler if you make use of conditional attributes: https://laraveljsonapi.io/docs/2.0/resources/attributes.html#conditional-attributes

lindyhopchris avatar Jun 25 '22 15:06 lindyhopchris

Leaving a note to myself...

We currently have fillUsing() on the fields to control field hydration. We could perhaps add fillWhen() and fillUnless() methods that take a callback like readOnly(), which would allow something like this:

    public function fields(): array
    {
        return [
            //..
            
            Boolean::make('catchall')
                ->sortable()
                ->fillWhen(static fn ($request): bool => $request->user()->isSuperUser()),

            //..

        ];
    }

lindyhopchris avatar Jun 25 '22 15:06 lindyhopchris

@preliot just checked the docs and you can actually already do conditional visibility of the field via the hidden() method. Here's the docs: https://laraveljsonapi.io/docs/2.0/schemas/attributes.html#hiding-fields

And here's an example that would work in your use case:

Boolean::make('catchall')->hidden(
  static fn($request) => !$request->user()->isSuperUser()
)

lindyhopchris avatar Jun 25 '22 15:06 lindyhopchris

But this would mean the field:

  • is not writable for ! su users (needed)
  • not readable for ! su users (unwanted side effect).

preliot avatar Jul 05 '22 09:07 preliot

@preliot sorry, but I'm not following how this wouldn't meet your use case.

Not readable by users who are not super users is covered by this (existing functionality):

Boolean::make('catchall')->hidden(
  static fn($request) => !$request->user()->isSuperUser()
)

As that hides the field if the user is not a super user.

Not writable by users who are not super users would be covered by this (future functionality):

Boolean::make('catchall')
                ->fillWhen(static fn ($request): bool => $request->user()->isSuperUser()),

As that would make it fillable only if the user is a super user.

If this doesn't meet your use-case, can you provide a lot more clarity as to why it wouldn't?

lindyhopchris avatar Jul 06 '22 10:07 lindyhopchris

Now i understand, you're talking about future functionaliy. I was under the impression it should be possible somehow now.

preliot avatar Jul 08 '22 06:07 preliot