1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.3.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Http;
16:
17: use Cake\Core\App;
18: use Countable;
19: use LogicException;
20: use RuntimeException;
21:
22: /**
23: * Provides methods for creating and manipulating a "queue" of middleware callables.
24: * This queue is used to process a request and response via \Cake\Http\Runner.
25: */
26: class MiddlewareQueue implements Countable
27: {
28: /**
29: * The queue of middlewares.
30: *
31: * @var array
32: */
33: protected $queue = [];
34:
35: /**
36: * The queue of middleware callables.
37: *
38: * @var callable[]
39: */
40: protected $callables = [];
41:
42: /**
43: * Constructor
44: *
45: * @param array $middleware The list of middleware to append.
46: */
47: public function __construct(array $middleware = [])
48: {
49: $this->queue = $middleware;
50: }
51:
52: /**
53: * Get the middleware at the provided index.
54: *
55: * @param int $index The index to fetch.
56: * @return callable|null Either the callable middleware or null
57: * if the index is undefined.
58: */
59: public function get($index)
60: {
61: if (isset($this->callables[$index])) {
62: return $this->callables[$index];
63: }
64:
65: return $this->resolve($index);
66: }
67:
68: /**
69: * Resolve middleware name to callable.
70: *
71: * @param int $index The index to fetch.
72: * @return callable|null Either the callable middleware or null
73: * if the index is undefined.
74: */
75: protected function resolve($index)
76: {
77: if (!isset($this->queue[$index])) {
78: return null;
79: }
80:
81: if (is_string($this->queue[$index])) {
82: $class = $this->queue[$index];
83: $className = App::className($class, 'Middleware', 'Middleware');
84: if (!$className || !class_exists($className)) {
85: throw new RuntimeException(sprintf(
86: 'Middleware "%s" was not found.',
87: $class
88: ));
89: }
90: $callable = new $className;
91: } else {
92: $callable = $this->queue[$index];
93: }
94:
95: return $this->callables[$index] = $callable;
96: }
97:
98: /**
99: * Append a middleware callable to the end of the queue.
100: *
101: * @param callable|string|array $middleware The middleware(s) to append.
102: * @return $this
103: */
104: public function add($middleware)
105: {
106: if (is_array($middleware)) {
107: $this->queue = array_merge($this->queue, $middleware);
108:
109: return $this;
110: }
111: $this->queue[] = $middleware;
112:
113: return $this;
114: }
115:
116: /**
117: * Alias for MiddlewareQueue::add().
118: *
119: * @param callable|string|array $middleware The middleware(s) to append.
120: * @return $this
121: * @see MiddlewareQueue::add()
122: */
123: public function push($middleware)
124: {
125: return $this->add($middleware);
126: }
127:
128: /**
129: * Prepend a middleware to the start of the queue.
130: *
131: * @param callable|string|array $middleware The middleware(s) to prepend.
132: * @return $this
133: */
134: public function prepend($middleware)
135: {
136: if (is_array($middleware)) {
137: $this->queue = array_merge($middleware, $this->queue);
138:
139: return $this;
140: }
141: array_unshift($this->queue, $middleware);
142:
143: return $this;
144: }
145:
146: /**
147: * Insert a middleware callable at a specific index.
148: *
149: * If the index already exists, the new callable will be inserted,
150: * and the existing element will be shifted one index greater.
151: *
152: * @param int $index The index to insert at.
153: * @param callable|string $middleware The middleware to insert.
154: * @return $this
155: */
156: public function insertAt($index, $middleware)
157: {
158: array_splice($this->queue, $index, 0, [$middleware]);
159:
160: return $this;
161: }
162:
163: /**
164: * Insert a middleware object before the first matching class.
165: *
166: * Finds the index of the first middleware that matches the provided class,
167: * and inserts the supplied callable before it.
168: *
169: * @param string $class The classname to insert the middleware before.
170: * @param callable|string $middleware The middleware to insert.
171: * @return $this
172: * @throws \LogicException If middleware to insert before is not found.
173: */
174: public function insertBefore($class, $middleware)
175: {
176: $found = false;
177: $i = null;
178: foreach ($this->queue as $i => $object) {
179: if ((is_string($object) && $object === $class)
180: || is_a($object, $class)
181: ) {
182: $found = true;
183: break;
184: }
185: }
186: if ($found) {
187: return $this->insertAt($i, $middleware);
188: }
189: throw new LogicException(sprintf("No middleware matching '%s' could be found.", $class));
190: }
191:
192: /**
193: * Insert a middleware object after the first matching class.
194: *
195: * Finds the index of the first middleware that matches the provided class,
196: * and inserts the supplied callable after it. If the class is not found,
197: * this method will behave like add().
198: *
199: * @param string $class The classname to insert the middleware before.
200: * @param callable|string $middleware The middleware to insert.
201: * @return $this
202: */
203: public function insertAfter($class, $middleware)
204: {
205: $found = false;
206: $i = null;
207: foreach ($this->queue as $i => $object) {
208: if ((is_string($object) && $object === $class)
209: || is_a($object, $class)
210: ) {
211: $found = true;
212: break;
213: }
214: }
215: if ($found) {
216: return $this->insertAt($i + 1, $middleware);
217: }
218:
219: return $this->add($middleware);
220: }
221:
222: /**
223: * Get the number of connected middleware layers.
224: *
225: * Implement the Countable interface.
226: *
227: * @return int
228: */
229: public function count()
230: {
231: return count($this->queue);
232: }
233: }
234: