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
Top comments (2)
I would skip the builder. Eloquent models already have the builder pattern baked in. There is no benefit in an extra abstraction layer.
Services are used when multiple entities are involved so
PostService
isn't a good example.A better example would be
PostAuthorService
with methods likeaddAuthor
andremoveAuthor
.Actions are not single operation, they are single purpose. They can contain multiple operations.
Thanks for the feedback! Great point about the service and action examples-makes sense to focus on purpose and multi-entity logic. Appreciate the insight! 🙌
Some comments may only be visible to logged-in visitors. Sign in to view all comments.