DEV Community

Cover image for Design Patterns in PHP: Decorator (with Laravel)
Ahmed Ashraf
Ahmed Ashraf

Posted on

Design Patterns in PHP: Decorator (with Laravel)

Design patterns are very important for every developer. it solves very common problems in every project you build.

Decorator pattern definition:

It helps you to add extra behavior on an object without affecting the other objects from the same class, will see what does that means.

Wikipedia:

the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class

The Problem

Let's say we have a Post model

class Post extends Model
{
    public function scopePublished($query) {
        return $query->where('published_at', '<=', 'NOW()');
    }
}

and in our PostsController we have the index method like below

class PostsController extends Controller
{
    public function index() {
        $posts = Post::published()->get();
        return $posts;
    }
}

in order to cache posts and avoid querying the database every time we need to list posts we can do the following

class PostsController extends Controller
{
    public function index() {
        $minutes = 1440; # 1 day
        $posts = Cache::remember('posts', $minutes, function () {
            return Post::published()->get();
        });
        return $posts;
    }
}

Now we are caching posts for 1 day. but looking at the code. the controller knows too much. it knows how many days we cache, it initiates the cache object by himself.

also, imagine you are implementing the same for Tags, Categories, Archives for your HomePageController. it would be too much to read and maintain.

Repository pattern

for most of the cases, the Repository pattern is connected to the decorator pattern and you will see how.

First, let's separate how we get the posts using the Repository pattern, create app/Repositories/Posts/PostsRepositoryInterface.php with the following content

namespace App\Repositories\Posts;

interface PostsRepositoryInterface 
{

    public function get();

    public function find(int $id);

}

create PostsRepository in the same path with the following content

namespace App\Repositories\Posts;

use App\Post;

class PostsRepository implements PostsRepositoryInterface
{
    protected $model;

    public function __construct(Post $model) {
        $this->model = $model;
    }

    public function get() {
        return $this->model->published()->get();
    }

    public function find(int $id) {
        return $this->model->published()->find($id);
    }

}

getting back to the PostsController and apply the changes to be like

namespace App\Http\Controllers;

use App\Repositories\Posts\PostsRepositoryInterface;
use Illuminate\Http\Request;

class PostsController extends Controller
{
    public function index(PostsRepositoryInterface $postsRepo) {
        return $postsRepo->get();
    }
}

The controller became healthy and knows enough details to get the job done.

here we depend on Laravel's IOC to inject the concrete object of the Posts interface to get our posts

All we just need to do is to tell Laravel's IOC which class to create when using the interface.

In your app/Providers/AppServiceProvider.php add the bind method

namespace App\Providers;

use App\Repositories\Posts\PostsRepositoryInterface;
use App\Repositories\Posts\PostsRepository;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(PostsRepositoryInterface::class,PostsRepository::class);
    }
}

Now anytime we inject PostsRepositoryInterface laravel will create an instance of PostsRepository and give it back.

Implement caching through Decorator

We said at the beginning that, Decorator pattern allows behavior to be added to an individual object without affecting other objects from the same class.

Caching here is the behavior and the object/class is PostsRepository

Let's create PostsCacheRepository in the same path app/Repositories/Posts/PostsCacheRepository.php with the following content


namespace App\Repositories\Posts;

use App\Post;
use Illuminate\Cache\CacheManager;

class PostsCacheRepository implements PostsRepositoryInterface
{
    protected $repo;

    protected $cache;

    const TTL = 1440; # 1 day

    public function __construct(CacheManager $cache, PostsRepository $repo) {
        $this->repo = $repo;
        $this->cache = $cache;
    }

    public function get() {
        return $this->cache->remember('posts', self::TTL, function () {
            return $this->repo->get();
        });
    }

    public function find(int $id) {
        return $this->cache->remember('posts.'.$id, self::TTL, function () {
            return $this->repo->find($id);
        });
    }
}

In this class, we accept the Caching object and PostsRepository object then we use the class (Decorator) to add caching behavior to the PostsReposiory instance.

we can use the same example to send HTTP requests to some service and then we fall back to the model in case of failure. I believe you got the benefit from the pattern and how it makes it easy to add behaviors.

The last thing is to modify AppServiceProvider interface binding to create PostsCacheRepository instance instead of PostsRepository

namespace App\Providers;

use App\Repositories\Posts\PostsRepositoryInterface;
use App\Repositories\Posts\PostsCacheRepository;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(PostsRepositoryInterface::class,PostsCacheRepository::class);
    }
}

Check your files again now you will find it very easy to read and maintain. also, it's testable and if at some point you decided to remove the caching layer. you will just change the binding in the AppServiceProvider and that's it. nothing extra needs to change.

Conclusions

  • We learned how to cache models with the Decorator pattern
  • We showed how the Repository pattern is connected to Decorator Pattern
  • How DependecyInjection and Laravel IOC make our life easy
  • How powerfull are laravel components

Hope you enjoyed reading the article. and it showed you the powerfull of design patterns and how it makes your project easy to maintain and manage .

Top comments (5)

Collapse
 
ooghry profile image
Milad

Do more post like this one please.

Collapse
 
ahmedash95 profile image
Ahmed Ashraf

Glad you like it and I'll do my best :)

Collapse
 
4n70w4 profile image
Krot Eval

This is not decorator. You have wrapped all the methods of the class.
This is not decoration. This is Proxy pattern.
Also I recommend looking to aspect-oriented programming for such tasks.

Collapse
 
edgardbraz profile image
edgardbraz

Awesome post!

Collapse
 
ahmedsliman profile image
Ahmed sliman

Thanks, y Ahmed powerful explanation, waiting for more :)