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
![](https://media.giphy.com/media/XYO7OdpYzKyac/giphy-downsized-large.gif)
CATS ALERT!
![](https://media.giphy.com/media/a5L9W5wbSj68g/giphy.gif)
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
![](https://media.giphy.com/media/Fkpp1YdpXL5zq/giphy.gif)
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!
![](https://i.imgur.com/Oxx42Q8.gif)
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
![](http://twistedsifter.files.wordpress.com/2014/05/longcat-hug-gif-remix-reddit-funny-3.gif)
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?
![](https://media.giphy.com/media/3o7aD9dn1LBSqrbp6g/giphy.gif)
Thank you!
twitter: @damiano_dev
slides: http://bit.ly/dp-eloquent
joind: https://joind.in/talk/c94e6
Using Active Record
By Damiano Petrungaro
Using Active Record
- 1,516