prettus/l5-repository
Repository pattern implementation for Laravel that abstracts the data layer with base repositories, criteria/query filters, presenters/transformers, caching, validators, and Artisan generators. Helps keep controllers thin and makes apps easier to maintain and test.
Installation:
composer require prettus/l5-repository
php artisan vendor:publish --provider="Prettus\Repository\Providers\RepositoryServiceProvider"
For Laravel 5.5+, the service provider auto-registers.
Generate a Repository:
php artisan make:repository Post
This creates:
PostRepository (base class)PostRepositoryEloquent (implementation)PostRepositoryInterface)First Use Case: Inject the repository into a controller and fetch data:
use App\Repositories\PostRepository;
class PostController extends Controller {
protected $repository;
public function __construct(PostRepository $repository) {
$this->repository = $repository;
}
public function index() {
$posts = $this->repository->all(); // Fetch all posts
return view('posts.index', compact('posts'));
}
}
CRUD Operations:
// Create
$post = $this->repository->create(['title' => 'Hello', 'body' => 'World']);
// Read
$post = $this->repository->find(1);
$posts = $this->repository->paginate(10);
// Update
$this->repository->update(['title' => 'Updated'], 1);
// Delete
$this->repository->delete(1);
Query Scoping:
// Custom scope
$posts = $this->repository->scopeQuery(function ($query) {
return $query->where('published', true);
})->paginate();
// Relationships
$posts = $this->repository->with(['author', 'comments'])->get();
Criteria for Dynamic Filtering:
// Define a criteria
class ActiveCriteria implements CriteriaInterface {
public function apply($model, RepositoryInterface $repository) {
return $model->where('is_active', true);
}
}
// Apply in controller
$this->repository->pushCriteria(new ActiveCriteria());
$activePosts = $this->repository->all();
Presenters for API Responses:
// Define presenter
class PostPresenter implements PresenterInterface {
public function present($data) {
return [
'id' => $data->id,
'title' => $data->title,
'url' => route('posts.show', $data->id),
];
}
}
// Enable in repository
$this->repository->setPresenter(new PostPresenter());
$presentedPost = $this->repository->present($this->repository->find(1));
Validation:
// Define validator
class PostValidator extends Validator {
public function rules() {
return [
'title' => 'required|max:255',
'body' => 'required',
];
}
}
// Enable in repository
$this->repository->setValidator(new PostValidator());
$validated = $this->repository->validator()->validate(Input::all());
Generators for Boilerplate:
# Generate full entity (model, repo, validator, presenter, controller)
php artisan make:entity Post --fillable="title:string,body:text" --rules="title:required|max:255,body:required"
Namespace Conflicts:
generator.basePath and generator.rootNamespace in config/repository.php match your project structure.src/App, set:
'basePath' => base_path('src'),
'rootNamespace' => 'App\\',
Criteria Order Matters:
$this->repository->pushCriteria(new ActiveCriteria());
$this->repository->pushCriteria(new PublishedCriteria()); // Runs first
Presenter Overhead:
$this->repository->skipPresenter(true);
Cache Invalidation:
Cache::forget($this->repository->getCacheKey('find', [1]));
Mass Assignment Risks:
$fillable in your model. The repository does not override this:
class Post extends Model {
protected $fillable = ['title', 'body']; // Only these can be mass-assigned
}
Eloquent vs. Query Builder:
BaseRepository and override model() to return a QueryBuilder instance.Log Queries:
Enable Eloquent logging in AppServiceProvider:
DB::enableQueryLog();
$posts = $this->repository->all();
dd(DB::getQueryLog());
Criteria Debugging: Temporarily log criteria application:
class DebugCriteria implements CriteriaInterface {
public function apply($model, RepositoryInterface $repository) {
\Log::info('Criteria applied:', ['query' => $model->toSql(), 'bindings' => $model->getBindings()]);
return $model;
}
}
Validator Errors: Access raw validation errors:
$validator = $this->repository->validator();
$validator->validate(Input::all());
if ($validator->fails()) {
dd($validator->errors()->all());
}
Presenter Debugging: Dump presenter output:
$data = $this->repository->find(1);
$presenter = $this->repository->presenter();
dd($presenter->present($data));
Custom Repository Methods:
Extend BaseRepository to add domain-specific methods:
class PostRepository extends BaseRepository {
public function publishedAndRecent($limit = 5) {
return $this->scopeQuery(function ($query) {
return $query->where('published', true)
->orderBy('created_at', 'desc')
->limit($limit);
})->get();
}
}
Dynamic Criteria: Create dynamic criteria based on request input:
class SearchCriteria implements CriteriaInterface {
protected $search;
public function __construct($search) {
$this->search = $search;
}
public function apply($model, RepositoryInterface $repository) {
return $model->where('title', 'like', "%{$this->search}%");
}
}
// Usage:
$this->repository->pushCriteria(new SearchCriteria(request('q')));
Event Hooks:
Listen to repository events (e.g., retrieving, retrieved) via Laravel events:
Event::listen('retrieving: App\Post', function ($repository, $method, $args) {
\Log::info("Retrieving posts with method: {$method}");
});
Repository Interfaces: Define strict interfaces for better dependency injection:
interface PostRepositoryInterface extends RepositoryInterface {
public function publishedAndRecent($limit);
}
Testing: Mock repositories in tests:
$mock = Mockery::mock(PostRepositoryInterface::class);
$mock->shouldReceive('find')->andReturn(new Post());
$this->app->instance(PostRepositoryInterface::class, $mock);
How can I help you explore Laravel packages today?