Overview
- Part 1: Walk-through installing and configuring Laravel
- Part 2: Create the core models/tables, controllers, and views
- Part 3: Authorization/Login
- Part 4: Post CRUD
- Part 5: Comment CRUD
- Part 6: Validation
- Part 7: Testing
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
- 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') }}" />
- 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
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
- 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(); }
- 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)
Roundup
That's it for now. Part 7 will go through automating the testing to ensure the functionality works.
Next: Testing