When you're building a Laravel application that scales beyond a few controllers and models, you’ll quickly feel the need for a cleaner, more maintainable architecture.
In this guide, I’ll walk you through 5 powerful patterns that can help organize your business logic:
- Repository Pattern
- Custom Query Builder
- Service Class
- Action Class
- DTO (Data Transfer Object)
Let’s break each one down with examples 👇
✅ 1. Repository Pattern
Purpose:
Decouples your business logic from Eloquent, making your app easier to maintain and test.
Abstraction (Interface):
interface PostRepositoryInterface {
public function all();
public function find($id);
public function create(array $data);
}
Concrete Implementation:
class PostRepository implements PostRepositoryInterface {
public function all() {
return Post::all();
}
public function find($id) {
return Post::findOrFail($id);
}
public function create(array $data) {
return Post::create($data);
}
}
💡 Now you can inject PostRepositoryInterface
anywhere and swap implementations later if needed (e.g., database vs API).
✅ 2. Custom Query Builder
Purpose:
Encapsulates complex or reusable query logic, keeping your code clean.
Class Example:
class PostQueryBuilder {
public function published() {
return Post::where('status', 'published');
}
public function byAuthor($authorId) {
return Post::where('author_id', $authorId);
}
}
🔁 You can inject this builder in services or controllers and chain queries like:
$postBuilder = new PostQueryBuilder();
$posts = $postBuilder->published()->get();
✅ 3. Service Class
Purpose:
Holds core business logic. Services call repositories or builders to perform operations.
Example:
class PostService {
public function createPost(array $data) {
return app(PostRepositoryInterface::class)->create($data);
}
public function listPublishedPosts() {
return (new PostQueryBuilder())->published()->get();
}
}
📌 Use services in controllers to reduce logic clutter and promote testability.
✅ 4. Action Class
Purpose:
Encapsulates a single operation, usually something that modifies state — like the Command Pattern.
Example:
class DeletePostAction {
public function execute($postId) {
Post::destroy($postId);
}
}
⚡ Clear, focused, and testable. Perfect for things like CreateUserAction
, UpdatePasswordAction
, etc.
✅ 5. DTO (Data Transfer Object)
Purpose:
Structures and sanitizes data passed between layers of your app.
Example:
class PostDTO {
public function __construct(
public string $title,
public string $content,
public bool $published
) {}
}
🚀 Use DTOs to pass validated data from a controller to a service:
$postDto = new PostDTO('Title', 'Content', true);
$postService->createPost((array) $postDto);
These patterns are not Laravel-specific, but Laravel makes them easy to implement thanks to features like service container, contracts, and facades.
By adopting patterns like Repository, Service, and DTOs:
- ✅ You reduce controller bloat
- ✅ Your logic becomes reusable and testable
- ✅ Your codebase stays clean as it grows
Attention writers! If you’ve ever published on Dev.to, you may be eligible for free tokens. Claim your rewards here. no gas fees. – Dev.to Community Support