Browse Source

Merge pull request #8590 from cakephp/middleware-stack

Add MiddlewareStack.
José Lorenzo Rodríguez 10 years ago
parent
commit
4e685e658f

+ 151 - 0
src/Http/MiddlewareStack.php

@@ -0,0 +1,151 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http;
+
+use Countable;
+
+/**
+ * Provides methods for creating and manipulating a 'stack' of
+ * middleware callables. This stack is used to process a request and response
+ * via \Cake\Http\Runner.
+ */
+class MiddlewareStack implements Countable
+{
+    /**
+     * The stack of middleware callables.
+     *
+     * @var array
+     */
+    protected $stack = [];
+
+    /**
+     * Get the middleware object at the provided index.
+     *
+     * @param int $index The index to fetch.
+     * @return callable|null Either the callable middleware or null
+     *   if the index is undefined.
+     */
+    public function get($index)
+    {
+        if (isset($this->stack[$index])) {
+            return $this->stack[$index];
+        }
+        return null;
+    }
+
+    /**
+     * Append a middleware callable to the end of the stack.
+     *
+     * @param callable $callable The middleware callable to append.
+     * @return $this
+     */
+    public function push(callable $callable)
+    {
+        $this->stack[] = $callable;
+        return $this;
+    }
+
+    /**
+     * Prepend a middleware callable to the start of the stack.
+     *
+     * @param callable $callable The middleware callable to prepend.
+     * @return $this
+     */
+    public function prepend(callable $callable)
+    {
+        array_unshift($this->stack, $callable);
+        return $this;
+    }
+
+    /**
+     * Insert a middleware callable at a specific index.
+     *
+     * If the index already exists, the new callable will be inserted,
+     * and the existing element will be shifted one index greater.
+     *
+     * @param int $index The index to insert at.
+     * @param callable $callable The callable to insert.
+     * @return $this
+     */
+    public function insertAt($index, callable $callable)
+    {
+        array_splice($this->stack, $index, 0, $callable);
+        return $this;
+    }
+
+    /**
+     * Insert a middleware object before the first matching class.
+     *
+     * Finds the index of the first middleware that matches the provided class,
+     * and inserts the supplied callable before it. If the class is not found,
+     * this method will behave like push().
+     *
+     * @param string $class The classname to insert the middleware before.
+     * @param callable $callable The middleware to insert
+     * @return $this
+     */
+    public function insertBefore($class, $callable)
+    {
+        $found = false;
+        foreach ($this->stack as $i => $object) {
+            if (is_a($object, $class)) {
+                $found = true;
+                break;
+            }
+        }
+        if ($found) {
+            return $this->insertAt($i, $callable);
+        }
+        return $this->push($callable);
+    }
+
+    /**
+     * Insert a middleware object after the first matching class.
+     *
+     * Finds the index of the first middleware that matches the provided class,
+     * and inserts the supplied callable after it. If the class is not found,
+     * this method will behave like push().
+     *
+     * @param string $class The classname to insert the middleware before.
+     * @param callable $callable The middleware to insert
+     * @return $this
+     */
+    public function insertAfter($class, $callable)
+    {
+        $found = false;
+        foreach ($this->stack as $i => $object) {
+            if (is_a($object, $class)) {
+                $found = true;
+                break;
+            }
+        }
+        if ($found) {
+            return $this->insertAt($i + 1, $callable);
+        }
+        return $this->push($callable);
+    }
+
+    /**
+     * Get the number of connected middleware layers.
+     *
+     * Implement the Countable interface.
+     *
+     * @return int
+     */
+    public function count()
+    {
+        return count($this->stack);
+    }
+}

+ 268 - 0
tests/TestCase/Http/MiddlewareStackTest.php

@@ -0,0 +1,268 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Http;
+
+use Cake\Http\MiddlewareStack;
+use Cake\TestSuite\TestCase;
+use TestApp\Middleware\SampleMiddleware;
+
+/**
+ * Test case for the MiddlewareStack
+ */
+class MiddlewareStackTest extends TestCase
+{
+    /**
+     * Test get()
+     *
+     * @return void
+     */
+    public function testGet()
+    {
+        $stack = new MiddlewareStack();
+        $cb = function () {
+        };
+        $stack->push($cb);
+        $this->assertSame($cb, $stack->get(0));
+        $this->assertNull($stack->get(1));
+    }
+
+
+    /**
+     * Test the return value of push()
+     *
+     * @return void
+     */
+    public function testPushReturn()
+    {
+        $stack = new MiddlewareStack();
+        $cb = function () {
+        };
+        $this->assertSame($stack, $stack->push($cb));
+    }
+
+    /**
+     * Test the push orders correctly
+     *
+     * @return void
+     */
+    public function testPushOrdering()
+    {
+        $one = function () {
+        };
+        $two = function () {
+        };
+
+        $stack = new MiddlewareStack();
+        $this->assertCount(0, $stack);
+
+        $stack->push($one);
+        $this->assertCount(1, $stack);
+
+        $stack->push($two);
+        $this->assertCount(2, $stack);
+
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($two, $stack->get(1));
+    }
+
+    /**
+     * Test the prepend can be chained
+     *
+     * @return void
+     */
+    public function testPrependReturn()
+    {
+        $cb = function () {
+        };
+        $stack = new MiddlewareStack();
+        $this->assertSame($stack, $stack->prepend($cb));
+    }
+
+    /**
+     * Test the prepend orders correctly.
+     *
+     * @return void
+     */
+    public function testPrependOrdering()
+    {
+        $one = function () {
+        };
+        $two = function () {
+        };
+
+        $stack = new MiddlewareStack();
+        $this->assertCount(0, $stack);
+
+        $stack->push($one);
+        $this->assertCount(1, $stack);
+
+        $stack->prepend($two);
+        $this->assertCount(2, $stack);
+
+        $this->assertSame($two, $stack->get(0));
+        $this->assertSame($one, $stack->get(1));
+    }
+
+    /**
+     * Test insertAt ordering
+     *
+     * @return void
+     */
+    public function testInsertAt()
+    {
+        $one = function () {
+        };
+        $two = function () {
+        };
+        $three = function () {
+        };
+
+        $stack = new MiddlewareStack();
+        $stack->push($one)->push($two)->insertAt(0, $three);
+        $this->assertSame($three, $stack->get(0));
+        $this->assertSame($one, $stack->get(1));
+        $this->assertSame($two, $stack->get(2));
+
+        $stack = new MiddlewareStack();
+        $stack->push($one)->push($two)->insertAt(1, $three);
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($three, $stack->get(1));
+        $this->assertSame($two, $stack->get(2));
+    }
+
+    /**
+     * Test insertAt out of the existing range
+     *
+     * @return void
+     */
+    public function testInsertAtOutOfBounds()
+    {
+        $one = function () {
+        };
+        $two = function () {
+        };
+
+        $stack = new MiddlewareStack();
+        $stack->push($one)->insertAt(99, $two);
+
+        $this->assertCount(2, $stack);
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($two, $stack->get(1));
+    }
+
+    /**
+     * Test insertAt with a negative index
+     *
+     * @return void
+     */
+    public function testInsertAtNegative()
+    {
+        $one = function () {
+        };
+        $two = function () {
+        };
+
+        $stack = new MiddlewareStack();
+        $stack->push($one)->insertAt(-1, $two);
+
+        $this->assertCount(2, $stack);
+        $this->assertSame($two, $stack->get(0));
+        $this->assertSame($one, $stack->get(1));
+    }
+
+    /**
+     * Test insertBefore
+     *
+     * @return void
+     */
+    public function testInsertBefore()
+    {
+        $one = function () {
+        };
+        $two = new SampleMiddleware();
+        $three = function () {
+        };
+        $stack = new MiddlewareStack();
+        $stack->push($one)->push($two)->insertBefore(SampleMiddleware::class, $three);
+
+        $this->assertCount(3, $stack);
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($three, $stack->get(1));
+        $this->assertSame($two, $stack->get(2));
+    }
+
+    /**
+     * Test insertBefore an invalid classname
+     *
+     * @return void
+     */
+    public function testInsertBeforeInvalid()
+    {
+        $one = function () {
+        };
+        $two = new SampleMiddleware();
+        $three = function () {
+        };
+        $stack = new MiddlewareStack();
+        $stack->push($one)->push($two)->insertBefore('InvalidClassName', $three);
+
+        $this->assertCount(3, $stack);
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($two, $stack->get(1));
+        $this->assertSame($three, $stack->get(2));
+    }
+
+    /**
+     * Test insertAfter
+     *
+     * @return void
+     */
+    public function testInsertAfter()
+    {
+        $one = new SampleMiddleware();
+        $two = function () {
+        };
+        $three = function () {
+        };
+        $stack = new MiddlewareStack();
+        $stack->push($one)->push($two)->insertAfter(SampleMiddleware::class, $three);
+
+        $this->assertCount(3, $stack);
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($three, $stack->get(1));
+        $this->assertSame($two, $stack->get(2));
+    }
+
+    /**
+     * Test insertAfter an invalid classname
+     *
+     * @return void
+     */
+    public function testInsertAfterInvalid()
+    {
+        $one = new SampleMiddleware();
+        $two = function () {
+        };
+        $three = function () {
+        };
+        $stack = new MiddlewareStack();
+        $stack->push($one)->push($two)->insertAfter('InvalidClass', $three);
+
+        $this->assertCount(3, $stack);
+        $this->assertSame($one, $stack->get(0));
+        $this->assertSame($two, $stack->get(1));
+        $this->assertSame($three, $stack->get(2));
+    }
+}

+ 25 - 0
tests/test_app/TestApp/Middleware/SampleMiddleware.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace TestApp\Middleware;
+
+/**
+ * Testing stub for middleware tests.
+ */
+class SampleMiddleware
+{
+    public function __invoke($req, $res, $next)
+    {
+    }
+}