DEV Community

Cover image for Laravel & Design Patterns - Practice Series: Proxy
Demian Kostelny
Demian Kostelny

Posted on Edited on

Laravel & Design Patterns - Practice Series: Proxy

Welcome back, and this is the third article of the patterns series. We are going to implement a practical structural design pattern — Proxy. So, let’s begin with the theoretical part.

Introduction

First, let’s see what the definition of the Proxy pattern can tell us:

Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.

And let’s check the UML schema to visualize how it’s working:

Example of a problem

Imagine that you have a database with a lot of different financial data, and in your application, you make a lot of the same requests to receive data that consumes a large amount of resources. In this case, it will be good to implement caching for some requests that fetch data that is rarely changed.

Implementation

To solve this, we can implement some kind of class that will always be requested before proceeding to the original class that we need — you can imagine that like a guard that does a check. Let’s continue our example, and let’s imagine that we want to have a Redis caching for posts in our application, and instead of always making requests to the database — we can cache this data for 12 hours and retrieve it — and to handle this, we are going to use the Proxy pattern as was mentioned before. So first, let’s create our simple Post model that will be used in the service class later:

<?php

namespace App\Models\Post;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  protected $fillable = ['title', 'content'];
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to have an actual service to retrieve posts from the database:

<?php

namespace App\Services;

use App\Models\Post;
use Illuminate\Support\Collection;

class PostService
{
  public function find(int $id): ?Post
  {
    return Post::find($id);
  }

  public function latest(int $limit = 10): Collection
  {
    return Post::latest()->limit($limit)->get();
  }
}
Enter fullscreen mode Exit fullscreen mode

And finally — PostCache class — that will be our Proxy for caching or getting actual data from the database if we don’t have cached data:

<?php

namespace App\Cache;

use App\Services\PostService;
use App\Models\Post;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Collection;

class PostCache
{
  public function __construct(
    private PostService $service
  ) {}

  public function find(int $id): ?Post
  {
    // As you can see, when there is no cached post
    // we request again actual data from service
    return Cache::remember(
      "posts:{$id}",
      now()->addMinutes(10),
      fn () => $this->service->find($id)
    );
  }

  public function latest(int $limit = 10): Collection
  {
    return Cache::remember(
      "posts:latest:{$limit}",
      now()->addMinutes(5),
      fn () => $this->service->latest($limit)
    );
  }

  public function forgot(int $id): void
  {
    Cache::forget("posts:{$id}");
    Cache::forget("posts:latest:*");
  }
}
Enter fullscreen mode Exit fullscreen mode

And finally, let’s create a controller to use it:

<?php

namespace App\Http\Controllers;

use App\Cache\PostCache;

class PostController
{
  public function __construct(
    private PostCache $cache
  ) {}

  public function index()
  {
    $posts = $postCache->latest();
    return response()->json($posts);
  }

  public function show(int $id)
  {
    $post = $postCache->find($id);
    return response()->json($post);
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

As you can see, it’s pretty easy to use the proxy pattern, and it’s very useful when you want to use it in caching to implement it with the cache-aside pattern with Redis or Memcached.

Top comments (0)