Using
repository pattern with Eloquent
for code that lasts

Who am I?

<?php

$me = [
    'name' => 'Damiano',
    'surname' => 'Petrungaro',
    'job' => 'Software Developer',
    'company' => [
        'name' => 'Car Match',
        'url' => 'https://carmatch.mx/'
    ],
    'github' => 'https://github.com/damianopetrungaro',
    'twitter' => '@damiano_dev',
    'buzzwords' => [
        'food', 'php', 'code', 'perfectionist', 'DDD', 'never stop learning'
    ]
];

Me(ow) at work

CATS ALERT!

Eloquent

The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database.

- Laravel documentation

Active Record

An object that wraps a row

in a database table or view,

encapsulates the database access,

and adds domain logic on that data.

- Martin Fowler, PoEAA

Data Mapper, Table Data Gateway, Row Data Gateway

BUSINESS LOGIC

PERSISTENCE

USER

PRODUCT

CART

<?php

namespace App\Http\Controllers;

//...

class UserController extends Controller
{
    public function show(Request $request)
    {
        $name = $request->get('name', 'Damiano');

        return UserEloquent::where('name', $name)->first() ?: "No $name was found";
    }
}

How eloquent models are usually used

Read

<?php

namespace App\Http\Controllers;

class UserController extends Controller
{
    public function create(Request $request)
    {
        // Some validation logic here
        $user = new UserEloquent();
        $user->name = $request->name;
        // Some domain logic here
        $user->save();
        return $user
    }
}

How eloquent models are usually used

Write

Every time a breaking change is introduced in the model

SRP

A class should have only a single responsibility

Changes to only one part of the software's specification should be able to affect the specification of the class

Low flexibility & high coupling

One to one mapping between table structure and entity

One property of an Entity will be a column in a table or a field in a document

Our domain model depends on ORM behavior by design

Our model can't exist without the ORM and exposes behavior not needed by the domain

Repository pattern

to the rescue!

Repository pattern

Mediates between the domain
and data mapping layers
using a collection-like interface
for accessing domain objects.

- Edward Hieatt and Rob Mee, PoEAA

BUSINESS LOGIC

PERSISTENCE

USER
REPOSITORY

PRODUCT
REPOSITORY

CART
REPOSITORY

USER

PRODUCT

CART

<?php

namespace App\Repository;

// ...

class UserRepository
{
    public function __construct(UserEloquent $userModel)
    {
        $this->userModel = $userModel;
    }

    public function findByName(string $name):? UserEloquent
    {
        return $this->userModel->where('name', $name)->first();
    }
}

Free to use all the benefits of the
active record <3

<?php

namespace App\Http\Controllers;

//...

class UserController extends Controller
{
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function show(Request $request)
    {
        $name = $request->get('name', 'Damiano');

        return $this->userRepository->findByName($name) ?: "No $name were found";
    }
}
<?php

namespace App\Http\Controllers;

class UserController extends Controller
{
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function show(Request $request)
    {
        $name = $request->get('name', 'Damiano');

        if ($user = $this->userRepository->findByName($name)) {
            // We do not want this
            // $user->delete();
        }

        return $user ?: "No $name were found";
    }
}

Inject the dependecy where needed

...but

<?php

namespace App\Model;

interface User
{
    public function id(): int;

    public function name(): string;
}
<?php

namespace App\Model;

// ...

class UserEloquent extends Model implements User
{
    // ...
    public function id(): int
    {
        return $this->id;
    }

    public function name(): string
    {
        return $this->name;
    }
}

Use it with an interface

and encapsulate the active record feature only in the repositories

Imagine the repository as

an object that holds eloquent local query scope

<?php

namespace App\Model;

// ...

class UserEloquent extends Model implements User
{
    // ...

    public function scopeFindByName(string $name)
    {
        // ...
    }

    public function scopeBannedLastWeek()
    {
        // ...
    }
}
<?php

namespace App\Repository;

// ...

class UserRepository
{
    // ...

    public function findByName(string $name):? User
    {
        // ...
    }

    public function bannedLastWeek(): UserCollection
    {
        // ...
    }
}

How to 'write' using repositories?

<?php

namespace App\Repository;

// ...

class UserRepository
{
    // ...
    
    public function add(User $user): void
    {
        // ...
    }

    public function update(User $user): void
    {
        // ...
    }

    public function delete(int $id): void
    {
        // ...
    }

    // ...
}

Final tips

yes, it can be done better

<?php

namespace App\Repository;

use App\Model\User;

interface UserRepository
{
    public function findByName(string $name):? User;

    public function add(User $user): void;

    public function update(User $user): void;

    public function delete(int $id): void;
}
<?php

namespace App\Repository;

// ...

final class UserEloquentRepository implements UserRepository
{
    // ...

    public function findByName(string $name):? User
    {
        // ...
    }

    public function add(User $user): void
    {
        // ...
    }

    public function update(User $user): void
    {
        // ...
    }

    public function delete(int $id): void
    {
        // ...
    }
}

Use interface for repository too

will save you a lot of time when you need to migrate

<?php

namespace App\Model;

interface User
{
    public static fromName(string $name): self;
    // ...
}
<?php

namespace App\Model;

// ...

class UserEloquent implements User
{
    private function __construct()
    {
        // Ah! You can't use me!
    }

    public static function fromName(string $name): self
    {
        if(strlen($name) <= 5) {
            throw new InvalidNameException($name);
        }

        $user = new self;
        $user->name = $name;
        
        return $user;
    }
}

Build your entities always in a valid state

... with named constructor

<?php

namespace App\ValueObject;

final class Name
{
    // ...

    public function __construct(string $name)
    {
        // All validation rules here
        $this->name = $name;
    }
}
<?php

namespace App\Model;

// ...

class UserEloquent implements User
{

    // ...
    private function __construct()
    {
        // Ah! You can't use me!
    }

    public static function fromName(Name $name): self
    {
        
        $user = new self;
        $user->name = $name;
        
        return $user;
    }

    // ...
}

Build your entities always in a valid state

... using value objects

Repository easier to maintain

<?php

namespace App\Repository;

// ...

interface UserRepository
{
    public function findByEmail(Email $email):? User;

    public function findByName(Name $name):? User;

    public function getById(UserId $id): User;

    public function add(User $user): void;

    public function update(User $user): void;

    public function delete(UserId $id): void;
}
  • Isolate read and write operation
     
  • Domain model won't be anemic but rich
     
  • Maintain and migrate can't be easier
     
  • Isolate bugs focusing on domain model
     
  • Still using your preferred ORM under the hood
     
  • Easier to test

Recap

Questions?

Thank you!

twitter: @damiano_dev

slides: http://bit.ly/dp-eloquent

joind:  https://joind.in/talk/c94e6