Better coding

https://openclassrooms.com/fr/courses/3613341-decouvrez-le-framework-php-laravel/3617473-migrations-et-modeles#/id/r-3617459

Table des matières

1. Context

In our /app/Http/Controllers/TodoController.php controller, we’ve this:

use App\Todo;

public function postForm(TodoRequest $request)
{
  $todo = new Todo();

  $todo->title = $request->input('title');
  $todo->description = $request->input('description');
  $todo->completed = $request->input('completed');
  $todo->user_id = Auth::user()->id;

  $todo->save();

  return view('todo_ok');
}

The problem: what will happen if, f.i., the description field is removed / renamed in the model /app/Todo.php? The controller will fail.

An updated version of the code can be the one below where the instantiation of the Todo() class has been removed in the function since the object is now part of the parameters.

use App\Todo;

public function postForm(TodoRequest $request, Todo $todo)
{
  $todo->title = $request->input('title');
  $todo->description = $request->input('description');
  $todo->completed = $request->input('completed');
  $todo->user_id = Auth::user()->id;

  $todo->save();

  return view('todo_ok');
}

But we still have our fields in our controller... The idea is to just call a save() method and without having a use App\Todo. Just save() without knowing what and where.

public function postForm(...)
{
  $todo->save();

  return view('todo_ok');
}

2. Using a repository

Put the data logic into the repository and remove it from the controller.

2.1. Edit the controller

The controller should only know that the method is called save() and nothing else. The controller will become agnostic: no need to known which fields are in the model, which ones needs to be saved, ... just call save() and that’s it.

Edit /app/Http/Controllers/TodoController.php, add a use statement for the repository and change the postForm() function:

use App\Repositories\TodoRepository;

public function postForm(
    TodoRequest $request,
    TodoRepository $todoRepository
) {
    $todoRepository->save($request);

    return view('todo_ok');
}

2.2. Create a repository

Create the /App/Repositories/TodoRepository.php file.

<?php

namespace App\Repositories;

use App\Todo;
use App\Http\Requests\TodoRequest;
use Auth;

class TodoRepository
{
    protected $todo;

    public function __construct(Todo $todo)
    {
        $this->todo = $todo;
    }

    public function save(TodoRequest $todo)
    {
        $this->todo->title = $todo->input('title');
        $this->todo->description = $todo->input('description');
        $this->todo->completed = $todo->input('completed');
        $this->todo->user_id = Auth::user()->id;

        $this->todo->save(); // Save the submitted data
    }
}

2.3. Test the repository

Once the controller has been updated and is referencing the repository, the save() method will be the one of the repository.

By going to http://127.0.0.1:8000/todo, if correctly implemented, the form will still work and the record stored in our database;

We can go one step further: by using an interface.

3. Using an interface

The interface will only inform other classes about the existing methods in the repository.

3.1. Create the interface

Create the /App/Repositories folder if needed and create the TodoRepositoryInterface.php file.

<?php

namespace App\Repositories;

use App\Http\Requests\TodoRequest;

interface TodoRepositoryInterface
{
    public function save(TodoRequest $todo);
}

3.2. Edit the repository

Edit /App/Repositories/TodoRepository.php and change

class TodoRepository

by

class TodoRepository implements TodoRepositoryInterface

The class extends the interface so, we need to implement the save() function and, too, we need to strictly respect the function declaration (so if the function in the interface call his parameter $todo, the repository should call it $todo too).

3.3. Edit the controller and use the interface

Edit /app/Http/Controllers/TodoController.php and change the postForm() function:

public function postForm(
    TodoRequest $request,
    TodoRepository $todoRepository
)

to

public function postForm(
    TodoRequest $request,
    TodoRepositoryInterface $todoRepository
)

So ask to use the interface and not the repository directly.

And ... this won’t work.

If you test to submit a new record, you’ll get this error:

Illuminate\Contracts\Container\BindingResolutionException
Target [App\Repositories\TodoRepositoryInterface] is not instantiable.

This because and interface can’t be instantiating.

Laravel comes with a little trick: bind the interface and the class using it.

Edit /app/Http/Providers/AppServiceProvider.php and add these lines to the register() function:

public function register()
{
    ...
    $this->app->bind(
        'App\Repositories\TodoRepositoryInterface',
        'App\Repositories\TodoRepository'
    );
}

Now, when Laravel will see TodoRepositoryInterface he’ll knows that he should use TodoRepository.

3.4. Test the interface

Once the controller has been updated and is referencing the interface, the save() method will be the one of the repository (which is an extension of the interface).

By going to http://127.0.0.1:8000/todo, if correctly implemented, the form will still work and the record stored in our database;