Resource controllers and reusable views

Written by Riari 11 months ago.

Laravel-5

Resource controllers in Laravel are very useful for quickly building out CRUD interfaces for your models. Although some people prefer to avoid them in favour of explicitly defining all of their routes, they're especially useful for creating admin tools and become even more powerful when combined with route model binding.

If you use this feature, one thing you can do to keep your resource controllers and views as slim as possible is merge the create and edit functionality.

For example:

<?php namespace App\Http\Controllers;

use App\Article;
use Illuminate\Http\Request;

class ArticleController extends Controller
{
    ...

    /**
     * Display an article creation page.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $this->authorize('createArticles');
        return $this->edit(new Article);
    }

    ...

    /**
     * Display an article editing page.
     *
     * @param  Article  $article
     * @return \Illuminate\Http\Response
     */
    public function edit(Article $article)
    {
        if ($article->exists) {
            $this->authorize($article);
        }

        return view('article.edit', compact('article'));
    }

    ...
}

With this, a single view is used for both creating and editing an article, avoiding the need to duplicate the form in two separate views. This is where the exists property of Eloquent's Model class comes in useful as it can be used to determine if the model in question has been persisted to the database yet:

@extends('app')

@section('title', $article->exists ? 'Edit Article' : 'Create Article')

@section('content')
<div class="row">
    <div class="col m8 offset-m2">
        <form method="post" action="{{ route($article->exists ? 'article.update' : 'article.store', $article->id) }}">
            {!! csrf_field() !!}
            @if ($article->exists)
                {!! method_field('patch') !!}
            @endif

            <div class="row">
                <div class="input-field col s12">
                    <input id="title" name="title" type="text" value="{{ !empty(old('title')) ? old('title') : $article->title }}">
                    <label for="title">Title</label>
                </div>
            </div>
            <div class="row">
                <div class="input-field col s12">
                    <label for="body">Body</label>
                    <textarea id="body" name="body" class="materialize-textarea">{{ !empty(old('body')) ? old('body') : $article->body }}</textarea>
                </div>
            </div>
            <div class="row">
                <div class="input-field col s12">
                    <input id="tags" name="tags" type="text" value="{{ !empty(old('tags')) ? old('tags') : $tags }}" data-role="materialtags">
                    <label for="tags">Tags</label>
                </div>
            </div>
            <div class="row">
                <div class="col s12">
                    <label for="published_at">Publish date & time</label>
                    <input id="published_at" name="published_at" type="text" class="datetimepicker" value="{{ !empty(old('published_at')) ? old('published_at') : $published_at }}">
                </div>
            </div>

            <div class="row">
                <div class="input-field col s12 right-align">
                    <button type="submit" class="waves-effect waves-light btn-large">
                        Save
                    </button>
                </div>
            </div>
        </form>
    </div>
</div>
@stop

And that's it. No more doubled up views, and you can handle whatever differences there may be (such as fields that are only visible when creating a new resource) via the exists property.