Proxy::__call()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.7998
c 0
b 0
f 0
cc 2
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 1
    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 4
    public function __construct($targetObject, $cacheStore, $cacheControl = 7200)
41
    {
42 4
        $this->ProxyConstruct( $targetObject );
43 4
        $this->targetObject = $targetObject;
44 4
        $this->cacheStore   = $cacheStore;
45 4
        $this->cacheControl = $cacheControl;
46 4
    }
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 4
    private function __callCatch($method, $args)
55
    {
56
        // catch all output and return value, return it
57 4
        ob_start();
58 4
        $result = call_user_func_array( array( $this->targetObject, $method ), $args );
59 4
        $output = ob_get_contents();
60 4
        ob_end_clean();
61
62
        return array(
63 4
            'output' => $output,
64 4
            'result' => $result
65
        );
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 4
    private function __callCached($method, $args, $path)
77
    {
78
        // check the cache, if fresh, use the cached version
79 4
        $cacheData = $this->cacheStore->getIfFresh( $path );
80 4
        if (!isset( $cacheData )) {
81 4
            if ($this->cacheStore->lock( $path )) {
82
                // try to get a lock to calculate the value
83 4
                $cacheData = $this->__callCatch( $method, $args );
84 4
                if (is_callable( $this->cacheControl )) {
85 2
                    $cacheTimeout = call_user_func(
86 2
                        $this->cacheControl,
87
                        array(
88 2
                            'target'    => $this->targetObject,
89 2
                            'method'    => $method,
90 2
                            'arguments' => $args,
91 2
                            'result'    => $cacheData['result'],
92 2
                            'output'    => $cacheData['output']
93
                        )
94
                    );
95
                } else {
96 2
                    $cacheTimeout = $this->cacheControl;
97
                }
98 4
                $this->cacheStore->set( $path, $cacheData, $cacheTimeout );
99
            } 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
        }
108
109 4
        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 4
    public function __call($method, $args)
120
    {
121
        // create a usable but unique filename based on the arguments and method name
122 4
        $path = $method . '(' . sha1( serialize($args) ) . ')';
123
124 4
        $cacheData = $this->__callCached( $method, $args, $path );
125 4
        echo $cacheData['output'];
126 4
        $result = $cacheData['result'];
127 4
        if (is_object( $result )) { // for fluent interface we want to cache the returned object as well
128 2
            $result = new static( $result, $this->cacheStore->cd( $path ), $this->cacheControl );
129
        }
130
131 4
        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