Create a Laravel 8 Blog - Part 6: Validation

Overview

Everything a user, whether they are an admin or anonymous, puts into the application needs to be vetted to ensure it's what we expect. It protects from accidents as much as it does from malicious intent. Whether you do this as you're developing the code or make it a separate milestone is up to you. I find benefits with both paths.

Posts

First off, let's think about what we know should be true about posts.

  • They always need a title
  • They always need a body
  • The length of the values should never exceed the length of the database column storing the data
  • The URL slug needs to be unique to avoid collisions when routing
  • Make sure only URL-friendly characters are used in the slug
  • Only admins or authors should be able to add posts
  • Only admins or authors should be able to edit posts
  • Only admins or authors should be able to delete posts

Now, let's work through these.

Form Request makes it easy to do validation on the data before it hits the controller. Using artisan we can create the barebones class to work from.

./vendor/bin/sail artisan make:request PostRequest
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class PostRequest extends FormRequest { // This ensures only admins/authors can hit the endpoints this PostRequest is used with public function authorize() { if ($this->user()->canManagePosts()) { return true; } return false; } public function rules() { $rules = [ // We need a title and the database column only allows for 255 chars 'title' => 'required|max:255', // We need a body 'body' => 'required', // The slug should be alphanumeric characters with dashes/spaces allowed 'slug' => 'unique:posts|regex:/^[A-Za-z0-9- ]+$/i', 'metaTitle' => 'max:255', 'metaDescription' => 'max:255' ]; return $rules; } public function messages() { return [ 'title.required' => 'Please add a title', 'body.required' => 'Please add some content to the post', 'slug.unique' => 'The URL slug has already been used' ]; } }

Update PostController.php to use PostRequest.

public function save(PostRequest $request) { $data = $request->validated(); $post = new Post(); $post->title = $data['title']; $post->metaTitle = $data['metaTitle']; $post->body = $data['body']; $post->metaDescription = $data['metaDescription']; $post->slug = $data['urlSlug'] ?? Str::slug($post->title); ... public function update(PostRequest $request, string $slug) ...

Update the blade file to render errors if there are any.

# resources/views/blog/posts/create.blade.php @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif

See commit 1

See commit 2

  1. If there is an error, show the form values

I'll only show a snippet, but it utilizes Laravel's old function to populate the values.

<input type="text" name="title" class="form-control" value="{{ old('title') }}" />

See the commit

  1. Add validation to the post update route

Update PostRequest.php slug validation.

public function rules() { $rules = [ 'title' => 'required|max:255', 'body' => 'required', 'slug' => 'unique:posts|regex:/^[A-Za-z0-9 ]+$/i', 'metaTitle' => 'max:255', 'metaDescription' => 'max:255' ]; if ($this->method() === 'PUT') { // this ensures that an existing post still requires a slug, but that // it doesn't validate against itself and trigger an error! $rules['slug'] = 'unique:posts,title,' . $this->get('slug') . '|regex:/^[A-Za-z0-9\- ]+$/i'; } return $rules; }

Update the blade file to render errors if there are any.

# resources/views/blog/posts/edit.blade.php @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif

See commit 1

See commit 2

Comments

Let's think about what we know should be true about comments.

  • There always needs to be a comment
  • The length of the values should never exceed the length of the database column storing the data
  • Logged in users can add comments
  • Anonymous users can not add comments
  1. Add comment validation
./vendor/bin/sail artisan make:request CommentRequest
# app/Http/Requests/CommentRequest.php <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CommentRequest extends FormRequest { public function authorize() { if ($this->user() && $this->user()->id > 0) { return true; } return false; } public function rules() { return [ 'comment' => 'required|string', 'post_id' => 'required|integer' ]; } }

Update the comment blade file to display errors

# resources/views/blog/comments/create.blade.php @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif

Update CommentController.php to use the new CommentRequest class.

public function save(CommentRequest $request) { $data = $request->validated(); $comment = new Comment(); $comment->comment = $data['comment']; $comment->user_id = $request->user()->id; $comment->post_id = $data['post_id']; $comment->save(); return redirect() ->back(); }

See the commit

  1. Add delete comment validation The main goal here is to make sure only admins and authors can delete comments.
./vendor/bin/sail artisan make:request DeleteCommentRequest
# app/Http/Requests/DeleteCommentRequest.php <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class DeleteCommentRequest extends FormRequest { public function rules() { return []; } public function authorize() { if ($this->user() && $this->user()->canManagePosts()) { return true; } return false; } }

Update CommentController.php to use the new DeleteCommentRequest class.

public function delete(DeleteCommentRequest $request, int $commentId)

See commit 1

See commit 2

Roundup

That's it for now. Part 7 will go through automating the testing to ensure the functionality works.

Next: Testing

If you have any feedback for me, I'd love to hear it - corrections, alternative paths, you name it! Send me an email levi@levijackson.xyz