Skip to content

Implementing Laravel Service-Repository pattern using Form Request

Putting all the business logic into the controller can be messy at times, specially when the project is large and complicated. As big your controller is, as difficult it becomes to maintain and accommodate new business logic. In order to resolve this issue, Laravel comes with different approaches which can make the controller skinny and the model thin. The most popular approaches are;

  • Service – Repository Pattern and
  • Services Pattern

We will discuss about the Service – Repository pattern in this post.

Adding the Repository Layer:

The first layer of Service - Repository Pattern is the Repository. This layer directly deals with the Model. All the database related operations must be performed here.

But before creating the repository which will contain all database operations, we should create an Interface for it, so that we can encapsulate the Repository class and ensure it’s re-usability in different forms (Polymorphism).  Create a directory called
Interfaces inside App directory where we will keep all the interfaces.

AccountInterface:

<?php
namespace App\Interfaces;

interface AccountInterface {
    public function getById($id);
    public function getAll();
    public function save($data);
    public function update($data, $id);
    public function delete($id);
}

Now, we are good to go with creating the Repository which implements the AccountInterface.

Laravel does not offer any artisan command for creating a Repository. For that you have to create the directory inside the App directory like, App\Repositories. Then create a class file like AccountRepository.php inside that Repositories directory.

<?php
namespace App\Repositories;
use App\Interfaces\AccountRepositoryInterface;
use App\Models\Account;

class AccountRepository implements AccountRepositoryInterface {
    protected $account;

    public function __construct(Account $account) {
        $this->account = $account;
    }

    public function getById($id): Account {
        return $this->account->where('id', $id)->get();
    }

    public function getAll() {
        return $this->account->get();
    }

    public function save($data) : Account {
        $account = new $this->account;

        $account->name = $data['name'];
        $account->type = $data['type'];
        $account->active = $data['active'];

        $account->save();

        return $account->fresh();
    }

    public function update($data, $id) : Account {
        $account = $this->account->find($id);

        $account->name = $data['name'];
        $account->type = $data['type'];
        $account->active = $data['type'];

        $account->update();

        return $account;
    }

    public function delete($id) : Account {
        $account = $this->account->find($id);
        $account->delete();

        return $account;
    }
}

Now, we need to bind the Interface with the Repository in order to get it to work.

  1. Go to App\Providers\AppServiceProvider.php.
  2. Add a line $this->app->bind('App\Interfaces\AccountInterface', 'App\Repositories\AccountRepository'); inside register method like below given code.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider {

    public function register() { 
        $this->app->bind('App\Interfaces\UserInterface', 'App\Repositories\UserRepository');
    }

    public function boot() {
        //
    }
}

Adding the Service Layer:

Services are places where we put all out business logic rather than Controllers. Like Repositories, create a directory named Services inside App and then create a Service class like AccountService as below.

<?php
namespace App\Services;

use App\Repositories\AccountRepository;
use http\Exception\InvalidArgumentException;
use Illuminate\Support\Facades\Validator;

class AccountService {
    protected $accountRepository;

    public function __construct(AccountRepository $accountRepository) {
        $this->accountRepository = $accountRepository;
    }

    public function getAccountById($id): AccountRepository {
        return $this->accountRepository->getById($id);
    }

    public function getAllAccounts() {
        return $this->accountRepository->getAll();
    }

    public function saveAccount($data) {
        $result = $this->accountRepository->save($data);
        return $result;
    }
}

Validating Data with Form Request:

Data coming from the Form can be validated inside Controller itself. But as our motive is to keep the Controller as clean as possible, it is the best practice to keep your validation logic in a separate Form Request. It will also make sure that, the inputs do not even enter the store() function before getting validated.

Create a Form Request using the following Artisan command.

php artisan make:request AccountStoreFormRequest

This will create a Form Request inside App\Http\Requests directory. Add the validation logic for creating the record like below.

<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;

class AccountStoreFormRequest extends FormRequest {
    public function authorize() {
        return true; /* set this to true */
    }

    public function rules() {
        return [
            'name' => 'required|unique:accounts,name|min:3|max:50',
            'type' => 'required|min:3|max:10'
        ];
    }

    /* following function gives customized error messages for validation failures */
    public function messages() {
        return [
            'name.required' => 'Account must have a name.',
            'name.unique' => 'Account already exists with this name.',
            'name.min' => 'Account name should contain at least 3 characters.',
            'name.max' => 'Account name can contain 50 characters at most.',
            'type.required' => 'Account type must be defined.',
            'type.min' => 'Account type should contain at least 3 characters.',
            'type.max' => 'Account type can contain 10 characters at most.',
        ];
    }
}

The Controller:

It is time to create the controller using artisan command. We will create a Resource Controller named AccountController in our case.

<?php
namespace App\Http\Controllers;

use App\Http\Requests\AccountStoreFormRequest;
use Illuminate\Http\Request;
use App\Services\AccountService;
use Mockery\Exception;

class AccountController extends Controller {

    protected $accountService;

    public function __construct(AccountService $accountService) {
        $this->accountService = $accountService;
    }

    public function index() {
        $data = $this->accountService->getAllAccounts();
        return view('accounts.index', compact('data'));
    }

    public function create() {
        return view('accounts.create');
    }

    public function store(AccountStoreFormRequest $request) {
        $validatedInputs = $request->validated();
        $validatedInputs['active'] = true; /* adding the status manually as it does not come as the form data */

        $result = ['status' => 200]; 

        try {
            $result['data'] = $this->accountService->saveAccount($validatedInputs);
        } catch (Exception $ex){
            $result = [
                'status' => 500,
                'error' => $ex->getMessage()
            ];
        }

        return redirect()->route('accounts.index')->with('success', 'Account created successfully.');
//        return response()->json($result, $result['status']); /* in case of API */
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        //
    }

    public function update(Request $request, $id)
    {
        //
    }

    public function destroy($id)
    {
        //
    }
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *