DEV Community

Cover image for Clean Code in Laravel: Business Logic Patterns You Should Know
Tahsin Abrar
Tahsin Abrar

Posted on

Clean Code in Laravel: Business Logic Patterns You Should Know

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:

  1. Repository Pattern
  2. Custom Query Builder
  3. Service Class
  4. Action Class
  5. 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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

💡 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

🔁 You can inject this builder in services or controllers and chain queries like:

$postBuilder = new PostQueryBuilder();
$posts = $postBuilder->published()->get();
Enter fullscreen mode Exit fullscreen mode

✅ 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

📌 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

⚡ 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
    ) {}
}
Enter fullscreen mode Exit fullscreen mode

🚀 Use DTOs to pass validated data from a controller to a service:

$postDto = new PostDTO('Title', 'Content', true);
$postService->createPost((array) $postDto);
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
xwero profile image
david duymelinck

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 like addAuthor and removeAuthor.

Actions are not single operation, they are single purpose. They can contain multiple operations.

class CreateUserAction
{
    private $userRepository;
    private $mailer;

    public function __construct(UserRepositoryInterface $userRepository, MailerInterface $mailer)
    {
        $this->userRepository = $userRepository;
        $this->mailer = $mailer;
    }

    public function execute(array $data): User
    {
        $user = new User($data);
        $this->userRepository->save($user);
        $this->mailer->sendWelcomeEmail($user);
        return $user;
    }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tahsin000 profile image
Tahsin Abrar

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.