Completed
Pull Request — master (#15)
by Robbert
02:28
created

Proxy::__call()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 6
Bugs 2 Features 2
Metric Value
c 6
b 2
f 2
dl 0
loc 14
ccs 9
cts 9
cp 1
rs 9.4286
cc 2
eloc 8
nc 2
nop 2
crap 2
1
<?php
2
3
/*
4
 * This file is part of the Ariadne Component Library.
5
 *
6
 * (c) Muze <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace arc\cache;
13
14
/**
15
 * Class Proxy
16
 * @package arc\cache
17
 */
18
final class Proxy
19
{
20
    use \arc\traits\Proxy {
21
        \arc\traits\Proxy::__construct as private ProxyConstruct;
22
    }
23
24
    private $cacheStore   = null;
25
    private $cacheControl = null;
26
    private $targetObject = null;
27
28
    /**
29
     * Creates a new Caching Proxy object.
30
     * @param object $targetObject  The object to cache.
31
     * @param object $cacheStore    The cache store to use, e.g. \arc\cache\FileStore
32
     * @param mixed  $cacheControl  Either an int with the number of seconds to cache results, or a Closure that returns an int.
33
     *                              The Closure is called with a single array with the following entries:
34
     *                              - target      The cached object a method was called on.
35
     *                              - method      The method called
36
     *                              - arguments   The arguments to the method
37
     *                              - output      Any output generated by the method
38
     *                              - result      The result of the method
39
     */
40 2
    public function __construct($targetObject, $cacheStore, $cacheControl = 7200)
41
    {
42 2
        $this->ProxyConstruct( $targetObject );
43 2
        $this->targetObject = $targetObject;
44 2
        $this->cacheStore   = $cacheStore;
45 2
        $this->cacheControl = $cacheControl;
46 2
    }
47
48
    /**
49
     * Catches output and return values from a method call and returns them.
50
     * @param string $method
51
     * @param array $args
52
     * @return array with keys 'output' and 'result'
53
     */
54 2
    private function __callCatch($method, $args)
55
    {
56
        // catch all output and return value, return it
57 2
        ob_start();
58 2
        $result = call_user_func_array( array( $this->targetObject, $method ), $args );
59 2
        $output = ob_get_contents();
60 2
        ob_end_clean();
61
62
        return array(
63 2
            'output' => $output,
64
            'result' => $result
65 2
        );
66
    }
67
68
    /**
69
     * Checks if a fresh cache image for this method and these arguments is available
70
     * and returns those. If not, it lets the call through and caches its output and results.
71
     * @param string $method
72
     * @param array  $args
73
     * @param string $path
74
     * @return array
75
     */
76 2
    private function __callCached($method, $args, $path)
77
    {
78
        // check the cache, if fresh, use the cached version
79 2
        $cacheData = $this->cacheStore->getIfFresh( $path );
80 2
        if (!isset( $cacheData )) {
81 2
            if ($this->cacheStore->lock( $path )) {
82
                // try to get a lock to calculate the value
83 2
                $cacheData = $this->__callCatch( $method, $args );
84 2
                if (is_callable( $this->cacheControl )) {
85 1
                    $cacheTimeout = call_user_func(
86 1
                        $this->cacheControl,
87
                        array(
88 1
                            'target'    => $this->targetObject,
89 1
                            'method'    => $method,
90 1
                            'arguments' => $args,
91 1
                            'result'    => $cacheData['result'],
92 1
                            'output'    => $cacheData['output']
93 1
                        )
94 1
                    );
95 1
                } else {
96 1
                    $cacheTimeout = $this->cacheControl;
97
                }
98 2
                $this->cacheStore->set( $path, $cacheData, $cacheTimeout );
99 2
            } else if ($this->cacheStore->wait( $path )) {
100
                // couldn't get a lock, so there is another proces writing a cache, wait for that
101
                // stampede protection
102
                $cacheData = $this->cacheStore->get( $path );
103
            } else {
104
                // wait failed, so just do the work without caching
105
                $cacheData = $this->__callCatch( $method, $args );
106
            }
107 2
        }
108
109 2
        return $cacheData;
110
    }
111
112
    /**
113
     * Catches a call to the target object and caches it. If the result is an object, it creates a
114
     * cache proxy for that as well.
115
     * @param string $method
116
     * @param array  $args
117
     * @return mixed
118
     */
119 2
    public function __call($method, $args)
120
    {
121
        // create a usable but unique filename based on the arguments and method name
122 2
        $path = $method . '(' . sha1( serialize($args) ) . ')';
123
124 2
        $cacheData = $this->__callCached( $method, $args, $path );
125 2
        echo $cacheData['output'];
126 2
        $result = $cacheData['result'];
127 2
        if (is_object( $result )) { // for fluent interface we want to cache the returned object as well
128 1
            $result = new static( $result, $this->cacheStore->cd( $path ), $this->cacheControl );
129 1
        }
130
131 2
        return $result;
132
    }
133
134
    /**
135
     * Catches any property access to the target object and caches it. If the property is an object
136
     * it creates a cache proxy for that as well.
137
     * @param string $name
138
     * @return mixed
139
     */
140
    public function __get($name)
141
    {
142
        $result = $this->targetObject->{$name};
143
        if (is_object( $result )) {
144
            $result = new static( $result, $this->cacheStore->cd( $name ), $this->cacheControl );
145
        }
146
147
        return $result;
148
    }
149
}
150