PHP: Proxy

Proxy

Proxy is a structural design pattern that provides an object that acts as a substitute for a real service object used by a client. Proxy receives client requests, does some work (access control, caching, etc.) and then passes request to a service object.

The proxy object has the same interface as a service, which makes it interchangeable with a real object when passed to a client.

More about Proxy

Application of the pattern in PHP

Complexity:

Popularity:

Usage examples: While the Proxy pattern is not a frequent guest in most PHP applications, it's still very handy in some special cases. It's irreplaceable when you want to add some additional behaviors to an object of some existing class without changing the client code.

Example: Structure of the Pattern

This example illustrates the structure of the Proxy design pattern and focuses on following questions:

  • What classes does it consists of?
  • What roles do these classes play?
  • In what way the elements of the pattern are related?

After learning about the pattern's structure it will be easier for you to grasp the following example, based on a real world PHP use case.

ProxyStructural.php: Structural Example

<?php

namespace RefactoringGuru\Proxy\Structural;

/**
 * The Subject interface declares common operations for both RealSubject and the
 * Proxy. As long as the client works with RealSubject using this interface,
 * you'll be able to pass it a proxy instead of a real subject.
 */
interface Subject
{
    public function request();
}

/**
 * The RealSubject contains some core business logic. Usually, RealSubjects are
 * capable of doing some useful work which may also be very slow or sensitive -
 * e.g. correcting input data. A Proxy can solve these issues without any
 * changes to the RealSubject's code.
 */
class RealSubject implements Subject
{
    public function request()
    {
        print("RealSubject: Handling request.\n");
    }
}

/**
 * The Proxy has an interface identical to the RealSubject.
 */
class Proxy implements Subject
{
    /**
     * @var RealSubject
     */
    private $realSubject;

    /**
     * The Proxy maintains a reference to an object of the RealSubject class. It
     * can be either lazy-loaded or passed to the Proxy by the client.
     */
    public function __construct(RealSubject $realSubject)
    {
        $this->realSubject = $realSubject;
    }

    /**
     * The most common applications of the Proxy pattern are lazy loading,
     * caching, controlling the access, logging, etc. A Proxy can perform one of
     * these things and then, depending on the result, pass the execution to the
     * same method in a linked RealSubject object.
     */
    public function request()
    {
        if ($this->checkAccess()) {
            $this->realSubject->request();
            $this->logAccess();
        }
    }

    private function checkAccess()
    {
        // Some real checks should go here. RU: Некоторые реальные проверки
        // должны проходить здесь.
        print("Proxy: Checking access prior to firing a real request.\n");

        return true;
    }

    private function logAccess()
    {
        print("Proxy: Logging the time of request.\n");
    }
}

/**
 * The client code is supposed to work with all objects (both subjects and
 * proxies) via the Subject interface in order to support both real subjects and
 * proxies. In real life, however, clients mostly work with their real subjects
 * directly. In this case, to implement the pattern more easily, you can extend
 * your proxy from the real subject's class.
 */
function clientCode(Subject $subject)
{
    // ...

    print($subject->request());

    // ...
}

print("Client: Executing the client code with a real subject:\n");
$realSubject = new RealSubject();
clientCode($realSubject);

print("\n");

print("Client: Executing the same client code with a proxy:\n");
$proxy = new Proxy($realSubject);
clientCode($proxy);

Output.txt: Output

Client: Executing the client code with a real subject:
RealSubject: Handling request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

Example: Real World Example

There are countless ways proxies can be used: caching, logging, access control, delayed initialization, etc. This example demonstrates how the Proxy pattern can improve the performance of a downloader object by caching its results.

ProxyRealWorld.php: Real world example

<?php

namespace RefactoringGuru\Proxy\RealWorld;

/**
 * Proxy Design Pattern
 *
 * Intent: Provide a surrogate or placeholder for another object to control
 * access to the original object or to add other responsibilities.
 *
 * Example: There are countless ways proxies can be used: caching, logging,
 * access control, delayed initialization, etc. This example demonstrates how
 * the Proxy pattern can improve the performance of a downloader object by
 * caching its results.
 */

/**
 * The Subject interface describes the interface of a real object.
 *
 * The truth is that many real apps may not have this interface clearly defined.
 * If you're in that boat, your best bet would be to extend the Proxy from one
 * of your existing application classes. If that's awkward, then extracting a
 * proper interface should be your first step.
 */
interface Downloader
{
    public function download(string $url): string;
}

/**
 * The Real Subject does the real job, albeit not in the most efficient way.
 * When a client tries to download the same file for the second time, our
 * downloader does just that, instead of fetching the result from cache.
 */
class SimpleDownloader implements Downloader
{
    public function download(string $url): string
    {
        print("Downloading a file from the Internet.\n");
        $result = file_get_contents($url);
        print("Downloaded bytes: " . strlen($result) . "\n");
        return $result;
    }
}

/**
 * The Proxy class is our attempt to make the download more efficient. It wraps
 * the real downloader object and delegates it the first download calls. The
 * result is then cached, making subsequent calls return an existing file
 * instead of downloading it again.
 *
 * Note that the Proxy MUST implement the same interface as the Real Subject.
 */
class CachingDownloader implements Downloader
{
    /**
     * @var SimpleDownloader
     */
    private $downloader;

    /**
     * @var string[]
     */
    private $cache = [];

    public function __construct(SimpleDownloader $downloader)
    {
        $this->downloader = $downloader;
    }

    public function download(string $url): string
    {
        if (!isset($this->cache[$url])) {
            print("CacheProxy MISS. ");
            $result = $this->downloader->download($url);
            $this->cache[$url] = $result;
        } else {
            print("CacheProxy HIT. Retrieving result from cache.\n");
        }
        return $this->cache[$url];
    }
}

/**
 * The client code may issue several similar download requests. In this case,
 * the caching proxy saves time and traffic by serving results from cache.
 *
 * The client is unaware that it works with a proxy because it works with
 * downloaders via the abstract interface.
 */
function clientCode(Downloader $subject)
{
    // ...

    $result = $subject->download("http://example.com/");

    // Duplicate download requests could be cached for a speed gain.

    $result = $subject->download("http://example.com/");

    // ...
}

print("Executing client code with real subject:\n");
$realSubject = new SimpleDownloader();
clientCode($realSubject);

print("\n");

print("Executing the same client code with a proxy:\n");
$proxy = new CachingDownloader($realSubject);
clientCode($proxy);

Output.txt: Output

Executing client code with real subject:
Downloading a file from the Internet.
Downloaded bytes: 1270
Downloading a file from the Internet.
Downloaded bytes: 1270

Executing the same client code with a proxy:
CacheProxy MISS. Downloading a file from the Internet.
Downloaded bytes: 1270
CacheProxy HIT. Retrieving result from cache.