PatrickDaniel's avatar

Silent failure on request validation

I working on the "next steps" to Laravel 8 From Scratch- specifically step 2)Update the "Edit Post" page in the admin section to allow for changing the author of a post.

I am trying to better understand why I'm not getting any feedback on the page with dd() after I call the validatePost() method within the update() method in AdminPostController.php. I think I'm missing an understanding of the validatePost() method that is called within update(), but I don't know.

I saw there was an issue with validations on large requests failing silently, but the solution for those was to change SESSION_DRIVER in .env to file from cookie- however, mine is already set to file. Also in session.php I have 'driver' => env('SESSION_DRIVER', 'file'), for what it's worth.

Here's the code within AdminPostController.php:

public function update(Post $post)
    {
        // This works- data from $post is displayed when I click on update on the form
        // dd($post);

        $attributes = $this->validatePost($post);

        // These don't work
        // dd($attributes);
        // dd('test');

        if ($attributes['thumbnail'] ?? false) {
            $attributes['thumbnail'] = request()->file('thumbnail')->store('thumbnails');
        }

        if (is_null($post->published_at) && $attributes['is_published'] == 1) {
            $attributes['published_at'] = now();
        }

        $post->update($attributes);

        return back()->with('success', 'Post Updated!');
    }

protected function validatePost(?Post $post = null): array
    {
        $post ??= new Post();

        return request()->validate([
            'title' => 'required',
            'thumbnail' => $post->exists ? ['image'] : ['required', 'image'],
            'slug' => ['required', Rule::unique('posts', 'slug')->ignore($post)],
            'excerpt' => 'required',
            'body' => 'required',
            'category_id' => ['required', Rule::exists('categories', 'id')],
            'is_published' => 'required'
        ]);
    }

And here is the code for the form in edit.blade.php (I'm not sure how to get the syntax highlighting to work, sorry):

<x-layout>
    <x-setting :heading="'Edit Post: ' . $post->title">
        <form method="POST" action="/admin/posts/{{ $post->id }}" enctype="multipart/form-data"/>
            @csrf
            @method('PATCH')

            <x-form.input name="title" :value="old('title', $post->title)" />
            <x-form.input name="slug" :value="old('slug', $post->slug)"/>


            <div class="flex mt-6">
                <div class="flex-1">
                    <x-form.input name="thumbnail" type="file" :value="old('thumbnail', $post->thumbnail)"/>
                </div>

                <img src="{{ asset('storage/' . $post->thumbnail) }}" alt="" class="rounded-xl ml-6" width="100">
            </div>

            <x-form.textarea name="excerpt">{{ old('excerpt', $post->excerpt) }}</x-form.textarea>
            <x-form.textarea name="body">{{ old('body', $post->body) }}</x-form.textarea>

            <x-form.field>
                <x-form.label name="category" />

                <select name="category_id" id="category_id">
                    @foreach (\App\Models\Category::all() as $category)
                        <option
                            value="{{ $category->id }}"
                            {{ old('category_id', $post->category_id) == $category->id ? 'selected' : ''  }}
                        >{{ ucwords($category->name) }}</option>
                    @endforeach
                </select>

                <x-form.error name="category" />
            </x-form.field>

            @if ((bool)!$post->is_published)
                <x-form.field>
                    <x-form.label name="Do you want to publish your post?" />
                    <select name="is_published" id="is_published">
                        <option value=1 >Yes</option>
                        <option value=0 selected>No, keep it as a draft</option>
                    </select>
                </x-form.field>
            @endif

            <x-form.button>Update</x-form.button>
        </form>
    </x-setting>
</x-layout>
0 likes
3 replies
tykus's avatar
tykus
Best Answer
Level 104

You might be getting redirected back to the edit page with validation errors; do all of the inputs have error messages?

If not, you can quickly render all of the validation error messages on the page using:

<pre>@json($errors)</pre>

This may hint at the issue; if not report back.

1 like
PatrickDaniel's avatar

So using <pre>@json($errors)</pre> did not show me anything, but I do have an error in the browser console that I overlooked- GET http://localhost:8000/storage/images/illustration-5.png 404 (Not Found). The error in the console only appears when I am trying to edit a blog post that was created with the seeder rather than created by a user.

The DatabaseSeeder.php calls the PostFactory.php:

class PostFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Post::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'user_id' => User::factory(),
            'category_id' => Category::factory(),
            'title' => $this->faker->sentence(),
            'slug' => $this->faker->slug(),
            // Random number 1 through 5 is based on number of default thumbnails in public/storage/images
            // as well as naming convention used
            'thumbnail' => "images/illustration-" . rand(1, 5) . ".png",
            'is_published' => true,
            'excerpt' => '<p>' . implode('</p><p>', $this->faker->paragraphs(2)) . '</p>',
            'body' => '<p>' . implode('</p><p>', $this->faker->paragraphs(6)) . '</p>',
        ];
    }
}

and here's my AdminPostController.php store() method for context:

public function store()
    {
        Post::create(array_merge($this->validatePost(), [
            'user_id' => request()->user()->id,
            'thumbnail' => request()->file('thumbnail')->store('thumbnails')],
            (request()->is_published ? ['published_at' => now()] : []),
        ));

        return redirect('/');
    }

I replaced the code in edit.blade.php that was causing the problem with a blade component to conditionally set the img src. Pertinent edit.blade.php code:

{{-- <img src="{{ asset('storage/' . $post->thumbnail) }}" alt="" class="rounded-xl ml-6" width="100"> --}}
<x-post-thumbnail :thumbnail="$post->thumbnail" width="100"/>

post-thumbnail.blade.php:

@props(['thumbnail'])

@if (str_contains($thumbnail, "images/illustration"))
    <img src="{{ asset($thumbnail) }}" alt="Blog Post illustration" class="rounded-xl">
@else
    <img src="{{ asset('storage/' . $thumbnail) }}" alt="Blog Post illustration" class="rounded-xl">
@endif

So my question now becomes: is there a way that I can seed post thumbnails so that they can be accessed with the same code as post thumbnails that are created by a user? Or is my component approach with the conditional logic my best bet?

PatrickDaniel's avatar

<pre>@json($errors)</pre> didn't show anything, but <pre>@json($errors->all())</pre> did, as well as <pre>@dump($errors)</pre>, which I missed on my first pass. My issue was that I was conditionally rendering an input field for is_published, but I was conditionally rendering the error as well (hence no feedback when the field wasn't rendered). Since my validation rule was 'is_published' => 'required',, I was getting a validation error and not displaying it.

Fixes:

  1. Move my form error field outside of the conditional so that it always displays.
  2. Make my validation rules conditional as well, and base it on the same condition as the display of the form field.

Thanks @tykus , you were spot on.

Please or to participate in this conversation.