Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
namespace GuzzleHttp;
3
 
4
use Psr\Http\Message\RequestInterface;
5
 
6
/**
7
 * Creates a composed Guzzle handler function by stacking middlewares on top of
8
 * an HTTP handler function.
9
 */
10
class HandlerStack
11
{
12
    /** @var callable */
13
    private $handler;
14
 
15
    /** @var array */
16
    private $stack = [];
17
 
18
    /** @var callable|null */
19
    private $cached;
20
 
21
    /**
22
     * Creates a default handler stack that can be used by clients.
23
     *
24
     * The returned handler will wrap the provided handler or use the most
25
     * appropriate default handler for you system. The returned HandlerStack has
26
     * support for cookies, redirects, HTTP error exceptions, and preparing a body
27
     * before sending.
28
     *
29
     * The returned handler stack can be passed to a client in the "handler"
30
     * option.
31
     *
32
     * @param callable $handler HTTP handler function to use with the stack. If no
33
     *                          handler is provided, the best handler for your
34
     *                          system will be utilized.
35
     *
36
     * @return HandlerStack
37
     */
38
    public static function create(callable $handler = null)
39
    {
40
        $stack = new self($handler ?: choose_handler());
41
        $stack->push(Middleware::httpErrors(), 'http_errors');
42
        $stack->push(Middleware::redirect(), 'allow_redirects');
43
        $stack->push(Middleware::cookies(), 'cookies');
44
        $stack->push(Middleware::prepareBody(), 'prepare_body');
45
 
46
        return $stack;
47
    }
48
 
49
    /**
50
     * @param callable $handler Underlying HTTP handler.
51
     */
52
    public function __construct(callable $handler = null)
53
    {
54
        $this->handler = $handler;
55
    }
56
 
57
    /**
58
     * Invokes the handler stack as a composed handler
59
     *
60
     * @param RequestInterface $request
61
     * @param array            $options
62
     */
63
    public function __invoke(RequestInterface $request, array $options)
64
    {
65
        $handler = $this->resolve();
66
 
67
        return $handler($request, $options);
68
    }
69
 
70
    /**
71
     * Dumps a string representation of the stack.
72
     *
73
     * @return string
74
     */
75
    public function __toString()
76
    {
77
        $depth = 0;
78
        $stack = [];
79
        if ($this->handler) {
80
            $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
81
        }
82
 
83
        $result = '';
84
        foreach (array_reverse($this->stack) as $tuple) {
85
            $depth++;
86
            $str = "{$depth}) Name: '{$tuple[1]}', ";
87
            $str .= "Function: " . $this->debugCallable($tuple[0]);
88
            $result = "> {$str}\n{$result}";
89
            $stack[] = $str;
90
        }
91
 
92
        foreach (array_keys($stack) as $k) {
93
            $result .= "< {$stack[$k]}\n";
94
        }
95
 
96
        return $result;
97
    }
98
 
99
    /**
100
     * Set the HTTP handler that actually returns a promise.
101
     *
102
     * @param callable $handler Accepts a request and array of options and
103
     *                          returns a Promise.
104
     */
105
    public function setHandler(callable $handler)
106
    {
107
        $this->handler = $handler;
108
        $this->cached = null;
109
    }
110
 
111
    /**
112
     * Returns true if the builder has a handler.
113
     *
114
     * @return bool
115
     */
116
    public function hasHandler()
117
    {
118
        return (bool) $this->handler;
119
    }
120
 
121
    /**
122
     * Unshift a middleware to the bottom of the stack.
123
     *
124
     * @param callable $middleware Middleware function
125
     * @param string   $name       Name to register for this middleware.
126
     */
127
    public function unshift(callable $middleware, $name = null)
128
    {
129
        array_unshift($this->stack, [$middleware, $name]);
130
        $this->cached = null;
131
    }
132
 
133
    /**
134
     * Push a middleware to the top of the stack.
135
     *
136
     * @param callable $middleware Middleware function
137
     * @param string   $name       Name to register for this middleware.
138
     */
139
    public function push(callable $middleware, $name = '')
140
    {
141
        $this->stack[] = [$middleware, $name];
142
        $this->cached = null;
143
    }
144
 
145
    /**
146
     * Add a middleware before another middleware by name.
147
     *
148
     * @param string   $findName   Middleware to find
149
     * @param callable $middleware Middleware function
150
     * @param string   $withName   Name to register for this middleware.
151
     */
152
    public function before($findName, callable $middleware, $withName = '')
153
    {
154
        $this->splice($findName, $withName, $middleware, true);
155
    }
156
 
157
    /**
158
     * Add a middleware after another middleware by name.
159
     *
160
     * @param string   $findName   Middleware to find
161
     * @param callable $middleware Middleware function
162
     * @param string   $withName   Name to register for this middleware.
163
     */
164
    public function after($findName, callable $middleware, $withName = '')
165
    {
166
        $this->splice($findName, $withName, $middleware, false);
167
    }
168
 
169
    /**
170
     * Remove a middleware by instance or name from the stack.
171
     *
172
     * @param callable|string $remove Middleware to remove by instance or name.
173
     */
174
    public function remove($remove)
175
    {
176
        $this->cached = null;
177
        $idx = is_callable($remove) ? 0 : 1;
178
        $this->stack = array_values(array_filter(
179
            $this->stack,
180
            function ($tuple) use ($idx, $remove) {
181
                return $tuple[$idx] !== $remove;
182
            }
183
        ));
184
    }
185
 
186
    /**
187
     * Compose the middleware and handler into a single callable function.
188
     *
189
     * @return callable
190
     */
191
    public function resolve()
192
    {
193
        if (!$this->cached) {
194
            if (!($prev = $this->handler)) {
195
                throw new \LogicException('No handler has been specified');
196
            }
197
 
198
            foreach (array_reverse($this->stack) as $fn) {
199
                $prev = $fn[0]($prev);
200
            }
201
 
202
            $this->cached = $prev;
203
        }
204
 
205
        return $this->cached;
206
    }
207
 
208
    /**
209
     * @param $name
210
     * @return int
211
     */
212
    private function findByName($name)
213
    {
214
        foreach ($this->stack as $k => $v) {
215
            if ($v[1] === $name) {
216
                return $k;
217
            }
218
        }
219
 
220
        throw new \InvalidArgumentException("Middleware not found: $name");
221
    }
222
 
223
    /**
224
     * Splices a function into the middleware list at a specific position.
225
     *
226
     * @param          $findName
227
     * @param          $withName
228
     * @param callable $middleware
229
     * @param          $before
230
     */
231
    private function splice($findName, $withName, callable $middleware, $before)
232
    {
233
        $this->cached = null;
234
        $idx = $this->findByName($findName);
235
        $tuple = [$middleware, $withName];
236
 
237
        if ($before) {
238
            if ($idx === 0) {
239
                array_unshift($this->stack, $tuple);
240
            } else {
241
                $replacement = [$tuple, $this->stack[$idx]];
242
                array_splice($this->stack, $idx, 1, $replacement);
243
            }
244
        } elseif ($idx === count($this->stack) - 1) {
245
            $this->stack[] = $tuple;
246
        } else {
247
            $replacement = [$this->stack[$idx], $tuple];
248
            array_splice($this->stack, $idx, 1, $replacement);
249
        }
250
    }
251
 
252
    /**
253
     * Provides a debug string for a given callable.
254
     *
255
     * @param array|callable $fn Function to write as a string.
256
     *
257
     * @return string
258
     */
259
    private function debugCallable($fn)
260
    {
261
        if (is_string($fn)) {
262
            return "callable({$fn})";
263
        }
264
 
265
        if (is_array($fn)) {
266
            return is_string($fn[0])
267
                ? "callable({$fn[0]}::{$fn[1]})"
268
                : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
269
        }
270
 
271
        return 'callable(' . spl_object_hash($fn) . ')';
272
    }
273
}