Using resources

1. Create a resource

php artisan make:controller TodoController --resource

2. Update routes

Edit /routes/web.php and replace any existing routes for Todo by

Route::resource('todos', 'TodoController');

3. Update our previous controller

Edit the /app/Http/Controllers/TodoController.php controller and use this code:

<?php

namespace App\Http\Controllers;

use App\Http\Requests\TodoRequest;
use App\Repositories\TodoRepositoryInterface;
use App\Repositories\TodoRepository;
use Response;

class TodoController extends Controller
{
    protected $todoRepository;

    public function __construct(TodoRepositoryInterface $todoRepository)
    {
        $this->todoRepository = $todoRepository;

        // Methods in the controller needs to be logged in
        // Except getting the list of todos (method index) and showing
        // one of them (method show).
        // All other methods requires a valid login.
        $this->middleware('auth', ['except' => ['index', 'show']]);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        // Retrieve all todos
        $datas = $this->todoRepository->index();

        // call the index.blade.php view and pass the data
        return view('index', compact('datas'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(TodoRequest $request)
    {
        $todo = $this->todoRepository->store($request);

        return redirect()->route('todos.show', ['id' => $todo->id])->withOk('Todo has been successfully created');
    }

    /**
     * Display the specified resource.
     *
     * @param  int                       $id
     * @return \Illuminate\Http\Response
     */
    public function show(int $id)
    {
        $data = $this->todoRepository->getById($id);

        return view('show', compact('data'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int                       $id
     * @param  TodoRepositoryInterface   $todoRepository
     * @return \Illuminate\Http\Response
     */
    public function edit(int $id)
    {
        $data = $this->todoRepository->getById($id);

        return view('edit', compact('data'));
    }

    /**
     * Update the specified resource in storage.
     * This function answer to the PUT method (updating an existing record)
     *
     * @param  TodoRequest               $request
     * @param  TodoRepositoryInterface   $todoRepository
     * @param  int                       $id
     * @return \Illuminate\Http\Response
     */
    public function update(TodoRequest $request, int $id)
    {
        $this->todoRepository->update($id, $request);

        // Redirect to the edit form
        return redirect()->route('todos.edit', ['id' => $id])->withOk('Successfully updated');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int                       $id
     * @param  TodoRepositoryInterface   $todoRepository
     * @return \Illuminate\Http\Response
     */
    public function destroy(int $id)
    {
        $this->todoRepository->destroy($id);

        // Return a JSON string that will be used in an Ajax request
        return Response::json([
            'status' => true,
            'message' => '<div class="alert alert-success" role="alert">' .
                'Successfully deleted</div>'
        ]);
    }
}

As we can see in your constructor, we’ve the following line to add authentication layer to our controller:

$this->middleware('auth', ['except' => ['index', 'show']]);

This means that every functions in the controller requires a valid logged-in user except the ones publicly accessible: index and show. So, we’ll easily prevent anyone to delete a resource f.i. (function destroy) if not connected. Easy!

4. Update our previous views

In the first part of the lab (1. Create), we’ve hard-coded URLs in our views.

For instance, in the /resources/views/list.blade.php, we have this line:

<h3><a href="todo/{{ $data->id }}">{{ $data->title }}</a></h3>

This means that we’ve hard-coded our URL for showing a given Todo. The URL should be todo/ follow by the ID of the given TODO.

Now, we’ll use our named routes:

<h3><a href="{{ route('todos.show', ['id' => $data->id]) }}">{{ $data->title }}</a></h3>

That name (todos.show) has been defined in our /web/routes.php file. We can retrieve the list of routes with Artisan:

php artisan route:list

Update the three others views like that i.e. no more hard-coded URL but using named routes.

5. Create a view for the update

To be as faithful as possible to the standard, don’t use anymore one view for both the creation (POST with todos.store as route) and the modification (PUT with todos.update as route) but use two different views.

So, we’ll no more use the unique /resources/views/form.blade.php with both logic for insertion or updates but two files: /resources/views/create.blade.php and /resources/views/edit.blade.php.

Remove /resources/views/form.blade.php and create the two views like described below:

5.1. resources/views/create.blade.php

@extends('master')

@section('content')

  {{-- Display the name of the connected user --}}
  <div class="panel-heading">Hi {{ Auth::user()->name }}, please add your new Todo below</div>  

  <div class="panel-body">   

    {{-- 
      Messages sent by the controller like in
        return view('...')->with('message', '<div class="...">success</div>'))
      will be displayed here
    --}}
    @if(Session::has('message'))
      {!! Session::get('message') !!}
    @endif

    {!! Form::open(['route' => 'todos.store']) !!}

    <div class="form-group {!! $errors->has('todo') ? 'has-error' : '' !!}"> 
        {{--
          Output the "Title" entry.
        --}}
        {!! Form::text('title', '', array('size' => '100', 'class' => 'form-control', 'placeholder' => 'Enter Todo\'s title')) !!}
        {!! $errors->first('title', '<div class="alert alert-danger">:message</div>') !!}
      </div>

      <div class="form-group">
        {{--
          Output the "Completed" flag.
        --}}      
        {!! Form::checkbox('completed', 1, 0)  !!}
        {!! Form::label('completed', 'Completed'); !!}
      </div>

      <div class="form-group {!! $errors->has('todo') ? 'has-error' : '' !!}">
        {{--
          Output the "Description" textarea.
        --}}      
        {!! Form::label('description', 'Description (optional)'); !!}
        {!! Form::textarea('description', '', array('class' => 'form-control')) !!}
      </div>

      {{--
        Output the submit button
      --}}      
      {!! Form::submit('Submit !') !!}
    {!! Form::close() !!}
  </div>
@endsection

@section('navigation')
  <a href="{{ route('todos.index') }}">Show all</a>
@endsection

5.2. resources/views/edit.blade.php

https://laravel.com/docs/4.2/html#form-model-binding

The edit form should display the current values: the field title should be linked with the title of the todo, the field description, and so on.

This can be automatically done by Laravel! Instead of using Form::open, we’ll use Form::model. This allow automatic linking.

@extends('master')

@section('content')

  {{-- Display the name of the connected user --}}
  <div class="panel-heading">Hi {{ Auth::user()->name }}, please add your new Todo below</div>  

  <div class="panel-body">   

    {{-- 
      Messages sent by the controller like in
        return view('...')->with('message', '<div class="...">success</div>'))
      will be displayed here
    --}}
    @if(Session::has('message'))
      {!! Session::get('message') !!}
    @endif

    {!! Form::model($data, ['route' => ['todos.update', $data->id], 'method' => 'PUT']) !!}

      <div class="form-group {!! $errors->has('todo') ? 'has-error' : '' !!}"> 
        {{--
          Output the "Title" entry.
        --}}
        {!! Form::text('title', null, array('size' => '100', 'class' => 'form-control', 'placeholder' => 'Enter Todo\'s title')) !!}
        {!! $errors->first('title', '<div class="alert alert-danger">:message</div>') !!}
      </div>

      <div class="form-group">
        {{--
          Output the "Completed" flag.
        --}}      
        {!! Form::checkbox('completed', 1, null)  !!}
        {!! Form::label('completed', 'Completed'); !!}
      </div>

      <div class="form-group {!! $errors->has('todo') ? 'has-error' : '' !!}">
        {{--
          Output the "Description" textarea.
        --}}      
        {!! Form::label('description', 'Description (optional)'); !!}
        {!! Form::textarea('description', null, array('class' => 'form-control')) !!}
      </div>

      {{--
        Output the submit button
      --}}      
      {!! Form::submit('Submit !') !!}
    {!! Form::close() !!}
  </div>
@endsection

@section('navigation')
  <a href="{{ route('todos.index') }}">Show all</a> - <a href="{{ route('todos.show', ['id' => $data->id]) }}">Show</a>
@endsection

6. Rename actions and files

To better follow the structure and naming convention of Laravel, rename some actions and files.

6.1. Rename actions in the controller

Old name New name
delete destroy

6.2. Rename files

Old name New name
/resources/views/list.blade.php /resources/views/index.blade.php