Using a Proxy Pattern to Organize PHP Caching

    Formulation of the problem. There is a valid debugged project in PHP containing a dozen models, each of which has 5 data sampling methods. The project is growing, everything is fine, but at some point, under the weight of the load, there is a need to somehow add caching for model calls.

    Possible solutions.

    The first way "forehead": in each method of the model we add caching according to the standard scheme: check the cache, if there is relevant data, return it, if not - we execute the method, as it was before and plus at the end we write the data received from the database to the cache. To say that this is a terrible way means to say nothing, so I’ll just say why this is bad:

    1. One of the principles of SOLID is violated, “the code must be open for extension, but closed for changes”, i.e. we take and break the already debugged production code in order to add new functionality, and this always causes a flurry of errors and, as a result, user and customer dissatisfaction.
    2. In the same code, the logic of data acquisition and caching are mixed, which leads to swelling of the classes and merciless repetition of the code.
    3. By doing so, we lose the ability to get live data bypassing the cache (the next step is to add the $ nocache flag).
    4. The very high complexity of cutting cache in this way and the greater complexity of cutting it later.

    The second method, “extend model classes”: add doublers to models that wrap calls to existing methods in caching, for example findById_Cached ().
    It seems to be better, we don’t touch the existing methods, instead we add new ones. But the rest of the minuses are in place:
    1. Mixing logic.
    2. Class sizes grow even more than in the previous method.
    3. Very high complexity (add 50 new methods, in our example) + replace the calls of the old methods everywhere in the application with new ones, and if in the future you have to cut the caching, then repeat all the steps back.


    The third method is “caching proxy” , a very simple and quick solution, striking with its elegance and speed of implementation. How to do it - look at the code.

    First we have a model (sample):

    And calling it in the application before caching:
    getTodayNews();
        $searchNews = $news->searchNews( array('tag' =>'sport') );
    ?>
    

    Then we have a cache (sample):
    data[$key]) ) {
                    return $this->data[$key];
                }
                else {
                    return null;
                }
            }
            public function set($key, $val, $ttl = 60)
            {
                $this->data[$key] = $val;
            }
        }
    ?>
    

    And here is the star of this topic - the caching proxy:
    realObject = $object; 
                // для примера, сделаем по-простому 
                // без инверсий зависимости
                $this->cache = new Cache;  
                $this->ttl = $ttl;
            }
            // для перехвата вызовов несуществующих методов прокси
            // и трансляции их реальному объекту
            // используем магический метод __call() 
            public function  __call( $method, $args ) 
            {
                $cacheKey = $method . '(' . \serialize($args) . ')';
                $data = $this->cache->get( $cacheKey );
                if ( null === $data ) {
                    $call = array( $this->realObject, $method );
                    $data = \call_user_func_array( $call, $args );
                    $this->cache->set( $cacheKey, $data, $this->ttl );
                }
                return $data; 
            }        
        }
    }
    ?>
    


    We use:
    getTodayNews();
        $searchNews = $news->searchNews( array('tag' =>'sport') );
    ?>
    


    As you can see, the proxy does not matter what object and method to cache, it is not at all necessary that this will be a class working with the database. If necessary, we retained the ability to receive live data. And the huge layer of the application responsible for caching was reduced to a small class, the implementation of which does not cause difficulties.

    UPD: There is a nuance, $ news in our example has become an object of a different type, and if somewhere in the code there are type checks (for example instanceof or data type in the method parameters), then these checks will break. To avoid this, you need to inherit \ Cache \ Proxy from \ Storage, of course the universality of the caching class will decrease in this case.

    Also popular now: