ソースを参照

Merge branch 'master' into 3.next

Conflicts:
	VERSION.txt
antograssiot 9 年 前
コミット
b38262caa2

+ 0 - 1
.travis.yml

@@ -43,7 +43,6 @@ matrix:
 
   allow_failures:
     - php: hhvm
-    - php: 7.1
 
 before_install:
   - if [ $HHVM != 1 && $TRAVIS_PHP_VERSION != 7.* ]; then phpenv config-rm xdebug.ini; fi

+ 0 - 32
src/Database/Connection.php

@@ -712,38 +712,6 @@ class Connection implements ConnectionInterface
     }
 
     /**
-     * Check if cross talk is supported between two connections
-     *
-     * @param ConnectionInterface $target Connection to check cross talk with
-     *
-     * @return bool
-     */
-    public function supportsCrossWith(ConnectionInterface $target)
-    {
-        $sourceConfig = $this->config();
-        $targetConfig = $target->config();
-
-        // No need to do report cross support in case the same connection is being used
-        if ($sourceConfig['name'] === $targetConfig['name']) {
-            return false;
-        }
-
-        $configToCheck = [
-            'driver',
-            'host',
-            'port'
-        ];
-
-        foreach ($configToCheck as $config) {
-            if ((isset($sourceConfig[$config])) && ($sourceConfig[$config] !== $targetConfig[$config])) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
      * Returns a new statement object that will log the activity
      * for the passed original statement instance.
      *

+ 0 - 129
src/Database/Expression/CrossSchemaTableExpression.php

@@ -1,129 +0,0 @@
-<?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\Database\Expression;
-
-use Cake\Database\ExpressionInterface;
-use Cake\Database\ValueBinder;
-
-/**
- * An expression object that represents a cross schema table name
- *
- * @internal
- */
-class CrossSchemaTableExpression implements ExpressionInterface
-{
-
-    /**
-     * Name of the schema
-     *
-     * @var \Cake\Database\ExpressionInterface|string
-     */
-    protected $_schema;
-
-    /**
-     * Name of the table
-     *
-     * @var \Cake\Database\ExpressionInterface|string
-     */
-    protected $_table;
-
-    /**
-     * @inheritDoc
-     *
-     * @param string\\Cake\Database\ExpressionInterface $schema Name of the schema
-     * @param string|\Cake\Database\ExpressionInterface $table Name of the table
-     */
-    public function __construct($schema, $table)
-    {
-        $this->_schema = $schema;
-        $this->_table = $table;
-    }
-
-    /**
-     * Get or set the schema to use
-     *
-     * @param null|string|\Cake\Database\ExpressionInterface $schema The schema to set
-     * @return $this|string|\Cake\Database\ExpressionInterface The schema that has been set
-     */
-    public function schema($schema = null)
-    {
-        if ($schema !== null) {
-            $this->_schema = $schema;
-
-            return $this;
-        }
-
-        return $this->_schema;
-    }
-
-    /**
-     * Get or set the schema to use
-     *
-     * @param null|string|\Cake\Database\ExpressionInterface $table The table to set
-     * @return $this|string|\Cake\Database\ExpressionInterface The table that has been set
-     */
-    public function table($table = null)
-    {
-        if ($table !== null) {
-            $this->_table = $table;
-
-            return $this;
-        }
-
-        return $this->_table;
-    }
-
-    /**
-     * Converts the Node into a SQL string fragment.
-     *
-     * @param \Cake\Database\ValueBinder $generator Placeholder generator object
-     * @return string
-     */
-    public function sql(ValueBinder $generator)
-    {
-        $schema = $this->_schema;
-        if ($schema instanceof ExpressionInterface) {
-            $schema = $schema->sql($generator);
-        }
-
-        $table = $this->_table;
-        if ($table instanceof ExpressionInterface) {
-            $table = $table->sql($generator);
-        }
-
-        return sprintf('%s.%s', $schema, $table);
-    }
-
-    /**
-     * Iterates over each part of the expression recursively for every
-     * level of the expressions tree and executes the $visitor callable
-     * passing as first parameter the instance of the expression currently
-     * being iterated.
-     *
-     * @param callable $visitor The callable to apply to all nodes.
-     * @return void
-     */
-    public function traverse(callable $visitor)
-    {
-        if ($this->_schema instanceof ExpressionInterface) {
-            $visitor($this->_schema);
-            $this->_schema->traverse($visitor);
-        }
-        if ($this->_table instanceof ExpressionInterface) {
-            $visitor($this->_table);
-            $this->_table->traverse($visitor);
-        }
-    }
-}

+ 0 - 22
src/Database/IdentifierQuoter.php

@@ -14,7 +14,6 @@
  */
 namespace Cake\Database;
 
-use Cake\Database\Expression\CrossSchemaTableExpression;
 use Cake\Database\Expression\FieldInterface;
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\OrderByExpression;
@@ -95,11 +94,6 @@ class IdentifierQuoter
 
             return;
         }
-        if ($expression instanceof CrossSchemaTableExpression) {
-            $this->_quoteCrossSchemaTableExpression($expression);
-
-            return;
-        }
     }
 
     /**
@@ -267,20 +261,4 @@ class IdentifierQuoter
             $this->_driver->quoteIdentifier($expression->getIdentifier())
         );
     }
-
-    /**
-     * Quotes the cross schema table identifier
-     *
-     * @param CrossSchemaTableExpression $expression The identifier to quote
-     * @return void
-     */
-    protected function _quoteCrossSchemaTableExpression(CrossSchemaTableExpression $expression)
-    {
-        if (!$expression->schema() instanceof ExpressionInterface) {
-            $expression->schema($this->_driver->quoteIdentifier($expression->schema()));
-        }
-        if (!$expression->table() instanceof ExpressionInterface) {
-            $expression->table($this->_driver->quoteIdentifier($expression->table()));
-        }
-    }
 }

+ 5 - 6
src/Http/ActionDispatcher.php

@@ -53,17 +53,16 @@ class ActionDispatcher
     /**
      * Constructor
      *
-     * @param \Cake\Http\ControllerFactory $factory A controller factory instance.
-     * @param \Cake\Event\EventManager $eventManager An event manager if you want to inject one.
+     * @param \Cake\Http\ControllerFactory|null $factory A controller factory instance.
+     * @param \Cake\Event\EventManager|null $eventManager An event manager if you want to inject one.
+     * @param array $filters The list of filters to include.
      */
-    public function __construct($factory = null, $eventManager = null)
+    public function __construct($factory = null, $eventManager = null, array $filters = [])
     {
         if ($eventManager) {
             $this->eventManager($eventManager);
         }
-
-        // Compatibility with DispatcherFilters.
-        foreach (DispatcherFactory::filters() as $filter) {
+        foreach ($filters as $filter) {
             $this->addFilter($filter);
         }
         $this->factory = $factory ?: new ControllerFactory();

+ 2 - 1
src/Http/BaseApplication.php

@@ -14,6 +14,7 @@
  */
 namespace Cake\Http;
 
+use Cake\Routing\DispatcherFactory;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 
@@ -92,6 +93,6 @@ abstract class BaseApplication
      */
     protected function getDispatcher()
     {
-        return new ActionDispatcher();
+        return new ActionDispatcher(null, null, DispatcherFactory::filters());
     }
 }

+ 14 - 2
src/Http/ResponseTransformer.php

@@ -162,8 +162,20 @@ class ResponseTransformer
         if (!isset($headers['Content-Type'])) {
             $headers['Content-Type'] = $response->type();
         }
-        if ($response->cookie()) {
-            $headers['Set-Cookie'] = static::buildCookieHeader($response->cookie());
+        $cookies = $response->cookie();
+        if ($cookies) {
+            $sessionCookie = session_get_cookie_params();
+            $sessionName = session_name();
+            $cookies[$sessionName] = [
+                'name' => $sessionName,
+                'value' => session_id(),
+                'expire' => $sessionCookie['lifetime'],
+                'path' => $sessionCookie['path'],
+                'secure' => $sessionCookie['secure'],
+                'domain' => $sessionCookie['domain'],
+                'httpOnly' => $sessionCookie['httponly'],
+            ];
+            $headers['Set-Cookie'] = static::buildCookieHeader($cookies);
         }
         $stream = static::getStream($response);
 

+ 5 - 1
src/Mailer/Email.php

@@ -1386,9 +1386,13 @@ class Email implements JsonSerializable, Serializable
      * @return \Cake\Mailer\Email Instance of Cake\Mailer\Email
      * @throws \InvalidArgumentException
      */
-    public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'fast', $send = true)
+    public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'default', $send = true)
     {
         $class = __CLASS__;
+
+        if (is_array($transportConfig)) {
+            $transportConfig += ['transport' => 'default'];
+        }
         $instance = new $class($transportConfig);
         if ($to !== null) {
             $instance->to($to);

+ 0 - 8
src/ORM/Association.php

@@ -16,7 +16,6 @@ namespace Cake\ORM;
 
 use Cake\Collection\Collection;
 use Cake\Core\ConventionsTrait;
-use Cake\Database\Expression\CrossSchemaTableExpression;
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Datasource\EntityInterface;
 use Cake\Datasource\ResultSetDecorator;
@@ -557,14 +556,7 @@ abstract class Association
     {
         $target = $this->target();
         $joinType = empty($options['joinType']) ? $this->joinType() : $options['joinType'];
-
         $table = $target->table();
-        if ($this->source()->connection()->supportsCrossWith($target->connection())) {
-            $table = new CrossSchemaTableExpression(
-                $target->connection()->driver()->schema(),
-                $table
-            );
-        }
 
         $options += [
             'includeFields' => true,

+ 1 - 1
src/ORM/Marshaller.php

@@ -122,7 +122,7 @@ class Marshaller
         foreach ($behaviors->loaded() as $name) {
             $behavior = $behaviors->get($name);
             if ($behavior instanceof PropertyMarshalInterface) {
-                $map = $behavior->buildMarhshalMap($this, $map, $options);
+                $map += $behavior->buildMarhshalMap($this, $map, $options);
             }
         }
 

+ 1 - 4
src/Routing/Dispatcher.php

@@ -58,10 +58,7 @@ class Dispatcher
      */
     public function dispatch(Request $request, Response $response)
     {
-        $actionDispatcher = new ActionDispatcher(null, $this->eventManager());
-        foreach ($this->_filters as $filter) {
-            $actionDispatcher->addFilter($filter);
-        }
+        $actionDispatcher = new ActionDispatcher(null, $this->eventManager(), $this->_filters);
         $response = $actionDispatcher->dispatch($request, $response);
         if (isset($request->params['return'])) {
             return $response->body();

+ 38 - 0
src/Routing/Exception/DuplicateNamedRouteException.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @since         3.3.1
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Routing\Exception;
+
+use Cake\Core\Exception\Exception;
+
+/**
+ * Exception raised when a route names used twice.
+ */
+class DuplicateNamedRouteException extends Exception
+{
+
+    /**
+     * {@inheritDoc}
+     */
+    protected $_messageTemplate = 'A route named "%s" has already been connected to "%s".';
+
+    /**
+     * {@inheritDoc}
+     */
+    public function __construct($message, $code = 404)
+    {
+        if (is_array($message) && isset($message['message'])) {
+            $this->_messageTemplate = $message['message'];
+        }
+        parent::__construct($message, $code);
+    }
+}

+ 9 - 0
src/Routing/RouteCollection.php

@@ -14,6 +14,7 @@
  */
 namespace Cake\Routing;
 
+use Cake\Routing\Exception\DuplicateNamedRouteException;
 use Cake\Routing\Exception\MissingRouteException;
 use Cake\Routing\Route\Route;
 
@@ -77,6 +78,14 @@ class RouteCollection
 
         // Explicit names
         if (isset($options['_name'])) {
+            if (isset($this->_named[$options['_name']])) {
+                $matched = $this->_named[$options['_name']];
+                throw new DuplicateNamedRouteException([
+                    'name' => $options['_name'],
+                    'url' => $matched->template,
+                    'duplicate' => $matched,
+                ]);
+            }
             $this->_named[$options['_name']] = $route;
         }
 

+ 62 - 0
src/Template/Error/duplicate_named_route.ctp

@@ -0,0 +1,62 @@
+<?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.1
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Cake\Error\Debugger;
+use Cake\Routing\Router;
+
+$this->layout = 'dev_error';
+
+$this->assign('title', 'Duplicate Named Route');
+$this->assign('templateName', 'duplicate_named_route.ctp');
+
+$attributes = $error->getAttributes();
+
+$this->start('subheading');
+?>
+    <strong>Error: </strong>
+    <?= $error->getMessage(); ?>
+<?php $this->end() ?>
+
+<?php $this->start('file') ?>
+<p>Route names must be unique across your entire application.
+The same <code>_name</code> option cannot be used twice,
+even if the names occur in different routing scopes.
+Remove duplicate route names in your route configuration.</p>
+
+<?php if (!empty($attributes['context'])) : ?>
+<p>The passed context was:</p>
+<pre>
+<?= Debugger::exportVar($attributes['context']); ?>
+</pre>
+<?php endif; ?>
+
+<?php if (isset($attributes['duplicate'])): ?>
+    <h3>Duplicate Route</h3>
+    <table cellspacing="0" cellpadding="0">
+    <tr><th>Template</th><th>Defaults</th><th>Options</th></tr>
+    <?php
+    $other = $attributes['duplicate'];
+    echo '<tr>';
+    printf(
+        '<td width="25%%">%s</td><td>%s</td><td width="20%%">%s</td>',
+        $other->template,
+        Debugger::exportVar($other->defaults),
+        Debugger::exportVar($other->options)
+    );
+    echo '</tr>';
+    ?>
+    </table>
+<?php endif; ?>
+<?php $this->end() ?>

+ 0 - 28
tests/TestCase/Database/ConnectionTest.php

@@ -987,32 +987,4 @@ class ConnectionTest extends TestCase
         $connection->schemaCollection($schema);
         $this->assertSame($schema, $connection->schemaCollection());
     }
-
-    /**
-     * Tests supportsCrossWith
-     *
-     * @return void
-     */
-    public function testSupportsCrossWith()
-    {
-        $connection = new Connection(ConnectionManager::config('test'));
-        $targetConnection = new Connection(ConnectionManager::config('test'));
-
-        $this->assertFalse($connection->supportsCrossWith($targetConnection), 'The same connection can\'t used in cross');
-
-        $connection = new Connection(ConnectionManager::config('test'));
-        $targetConnection = new Connection(['name' => 'test2'] + ConnectionManager::config('test'));
-
-        $this->assertTrue($connection->supportsCrossWith($targetConnection), 'Cross should be supported on databases on the same server');
-
-        $connection = new Connection(ConnectionManager::config('test'));
-        $targetConnection = new Connection(['port' => 999999] + ConnectionManager::config('test'));
-
-        $this->assertFalse($connection->supportsCrossWith($targetConnection), 'Cross is not supported across different server instances');
-
-        $connection = new Connection(ConnectionManager::config('test'));
-        $targetConnection = new Connection(['host' => 'db2.example.com'] + ConnectionManager::config('test'));
-
-        $this->assertFalse($connection->supportsCrossWith($targetConnection), 'Cross is not supported across different server instances');
-    }
 }

+ 0 - 71
tests/TestCase/Database/Expression/CrossSchemaTableExpressionTest.php

@@ -1,71 +0,0 @@
-<?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\Database\Expression;
-
-use Cake\Database\Expression\CrossSchemaTableExpression;
-use Cake\Database\Expression\IdentifierExpression;
-use Cake\Database\ValueBinder;
-use Cake\TestSuite\TestCase;
-
-/**
- * Tests CrossSchemaTableExpression class
- */
-class CrossSchemaTableExpressionTest extends TestCase
-{
-
-    /**
-     * Test sql method with ExpressionInterfaces passed and without
-     */
-    public function testSql()
-    {
-        $expression = new CrossSchemaTableExpression(
-            new IdentifierExpression('schema'),
-            new IdentifierExpression('table')
-        );
-
-        $this->assertEquals('schema.table', $expression->sql(new ValueBinder()));
-
-        $expression = new CrossSchemaTableExpression('schema', 'table');
-
-        $this->assertEquals('schema.table', $expression->sql(new ValueBinder()));
-    }
-
-    /**
-     * Test traverse method with ExpressionInterfaces passed and without
-     */
-    public function testTraverse()
-    {
-        $expressions = [];
-
-        $collector = function ($e) use (&$expressions) {
-            $expressions[] = $e;
-        };
-
-        $expression = new CrossSchemaTableExpression(
-            new IdentifierExpression('schema'),
-            new IdentifierExpression('table')
-        );
-        $expression->traverse($collector);
-        $this->assertEquals([
-            new IdentifierExpression('schema'),
-            new IdentifierExpression('table')
-        ], $expressions);
-
-        $expressions = [];
-        $expression = new CrossSchemaTableExpression('schema', 'table');
-        $expression->traverse($collector);
-        $this->assertEquals([], $expressions);
-    }
-}

+ 1 - 1
tests/TestCase/Http/ActionDispatcherTest.php

@@ -79,7 +79,7 @@ class ActionDispatcherTest extends TestCase
             ->setMethods(['beforeDispatch', 'afterDispatch'])
             ->getMock();
         DispatcherFactory::add($filter);
-        $dispatcher = new ActionDispatcher();
+        $dispatcher = new ActionDispatcher(null, null, DispatcherFactory::filters());
         $this->assertCount(1, $dispatcher->getFilters());
         $this->assertSame($filter, $dispatcher->getFilters()[0]);
     }

+ 23 - 0
tests/TestCase/Http/ResponseTransformerTest.php

@@ -16,6 +16,7 @@ namespace Cake\Test\TestCase\Http;
 
 use Cake\Http\ResponseTransformer;
 use Cake\Network\Response as CakeResponse;
+use Cake\Network\Session;
 use Cake\TestSuite\TestCase;
 use Zend\Diactoros\Response as PsrResponse;
 use Zend\Diactoros\Stream;
@@ -205,6 +206,28 @@ class ResponseTransformerTest extends TestCase
     }
 
     /**
+     * Test conversion setting cookies including the session cookie
+     *
+     * @return void
+     */
+    public function testToPsrCookieWithSession()
+    {
+        $session = new Session();
+        $session->write('things', 'things');
+        $cake = new CakeResponse(['status' => 200]);
+        $cake->cookie([
+            'name' => 'remember_me',
+            'value' => 1
+        ]);
+        $result = ResponseTransformer::toPsr($cake);
+        $this->assertEquals(
+            'remember_me=1; Path=/,CAKEPHP=; Path=/; HttpOnly',
+            $result->getHeaderLine('Set-Cookie'),
+            'Session cookie data was not retained.'
+        );
+    }
+
+    /**
      * Test conversion setting multiple cookies
      *
      * @return void

+ 6 - 0
tests/TestCase/Mailer/EmailTest.php

@@ -1906,11 +1906,15 @@ class EmailTest extends TestCase
      */
     public function testDeliver()
     {
+        Email::dropTransport('default');
+        Email::configTransport('default', ['className' => 'Debug']);
+
         $instance = Email::deliver('all@cakephp.org', 'About', 'Everything ok', ['from' => 'root@cakephp.org'], false);
         $this->assertInstanceOf('Cake\Mailer\Email', $instance);
         $this->assertSame($instance->to(), ['all@cakephp.org' => 'all@cakephp.org']);
         $this->assertSame($instance->subject(), 'About');
         $this->assertSame($instance->from(), ['root@cakephp.org' => 'root@cakephp.org']);
+        $this->assertInstanceOf('Cake\Mailer\AbstractTransport', $instance->transport());
 
         $config = [
             'from' => 'cake@cakephp.org',
@@ -2668,6 +2672,8 @@ HTML;
      */
     public function testMockTransport()
     {
+        Email::dropTransport('default');
+
         $mock = $this->getMockBuilder('\Cake\Mailer\AbstractTransport')->getMock();
         $config = ['from' => 'tester@example.org', 'transport' => 'default'];
 

+ 8 - 1
tests/TestCase/ORM/MarshallerTest.php

@@ -2435,12 +2435,19 @@ class MarshallerTest extends TestCase
                     'title' => 'Titulo Español',
                     'body' => 'Contenido Español'
                 ]
+            ],
+            'user' => [
+                'id' => 1,
+                'username' => 'mark'
             ]
         ];
 
         $marshall = new Marshaller($this->articles);
-        $result = $marshall->one($data, []);
+        $result = $marshall->one($data, ['associated' => ['Users']]);
         $this->assertEmpty($result->errors());
+        $this->assertEquals(1, $result->author_id);
+        $this->assertInstanceOf(__NAMESPACE__ . '\OpenEntity', $result->user);
+        $this->assertEquals('mark', $result->user->username);
 
         $translations = $result->get('_translations');
         $this->assertCount(2, $translations);

+ 31 - 0
tests/TestCase/Routing/DispatcherFactoryTest.php

@@ -14,6 +14,8 @@
  */
 namespace Cake\Test\TestCase\Routing;
 
+use Cake\Core\Configure;
+use Cake\Network\Request;
 use Cake\Routing\DispatcherFactory;
 use Cake\TestSuite\TestCase;
 
@@ -31,6 +33,7 @@ class DispatcherFactoryTest extends TestCase
     public function setUp()
     {
         parent::setUp();
+        Configure::write('App.namespace', 'TestApp');
         DispatcherFactory::clear();
     }
 
@@ -99,4 +102,32 @@ class DispatcherFactoryTest extends TestCase
         $this->assertInstanceOf('Cake\Routing\Dispatcher', $result);
         $this->assertCount(1, $result->filters());
     }
+
+    /**
+     * test create() -> dispatch() -> response flow.
+     *
+     * @return void
+     */
+    public function testCreateDispatchWithFilters()
+    {
+        $url = new Request([
+            'url' => 'posts',
+            'params' => [
+                'controller' => 'Posts',
+                'action' => 'index',
+                'pass' => [],
+                'bare' => true,
+            ]
+        ]);
+        $response = $this->getMockBuilder('Cake\Network\Response')
+            ->setMethods(['send'])
+            ->getMock();
+        DispatcherFactory::add('ControllerFactory');
+        DispatcherFactory::add('Append');
+
+        $dispatcher = DispatcherFactory::create();
+        $result = $dispatcher->dispatch($url, $response);
+        $this->assertNull($result);
+        $this->assertEquals('posts index appended content', $response->body());
+    }
 }

+ 15 - 0
tests/TestCase/Routing/RouteCollectionTest.php

@@ -356,6 +356,21 @@ class RouteCollectionTest extends TestCase
     }
 
     /**
+     * Test the add() with some _name.
+     *
+     * @expectedException \Cake\Routing\Exception\DuplicateNamedRouteException
+     *
+     * @return void
+     */
+    public function testAddingDuplicateNamedRoutes()
+    {
+        $one = new Route('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
+        $two = new Route('/', ['controller' => 'Dashboards', 'action' => 'display']);
+        $this->collection->add($one, ['_name' => 'test']);
+        $this->collection->add($two, ['_name' => 'test']);
+    }
+
+    /**
      * Test basic get/set of extensions.
      *
      * @return void

+ 25 - 0
tests/TestCase/Routing/RouterTest.php

@@ -1192,6 +1192,31 @@ class RouterTest extends TestCase
     }
 
     /**
+     * Test that using duplicate names causes exceptions.
+     *
+     * @expectedException \Cake\Routing\Exception\DuplicateNamedRouteException
+     * @return void
+     */
+    public function testDuplicateNamedRouteException()
+    {
+        Router::connect(
+            '/users/:name',
+            ['controller' => 'users', 'action' => 'view'],
+            ['_name' => 'test']
+        );
+        Router::connect(
+            '/users/:name',
+            ['controller' => 'users', 'action' => 'view'],
+            ['_name' => 'otherName']
+        );
+        Router::connect(
+            '/users/:name',
+            ['controller' => 'users', 'action' => 'view'],
+            ['_name' => 'test']
+        );
+    }
+
+    /**
      * Test that url filters are applied to url params.
      *
      * @return void

+ 1 - 1
tests/TestCase/Utility/Crypto/McryptTest.php

@@ -31,7 +31,7 @@ class McryptTest extends TestCase
     public function setUp()
     {
         parent::setUp();
-        $this->skipIf(!function_exists('mcrypt_encrypt'), 'No mcrypt skipping tests');
+        $this->skipIf(!function_exists('mcrypt_encrypt') || version_compare(PHP_VERSION, '7.1', '>='), 'No mcrypt skipping tests');
         $this->crypt = new Mcrypt();
     }
 

+ 2 - 2
tests/TestCase/Utility/SecurityTest.php

@@ -76,7 +76,7 @@ class SecurityTest extends TestCase
      */
     public function testRijndael()
     {
-        $this->skipIf(!function_exists('mcrypt_encrypt'));
+        $this->skipIf(!function_exists('mcrypt_encrypt') || version_compare(PHP_VERSION, '7.1', '>='));
         $engine = Security::engine();
 
         Security::engine(new Mcrypt());
@@ -259,7 +259,7 @@ class SecurityTest extends TestCase
      */
     public function testEngineEquivalence()
     {
-        $this->skipIf(!defined('MCRYPT_RIJNDAEL_128'), 'This needs mcrypt extension to be loaded.');
+        $this->skipIf(!function_exists('mcrypt_encrypt') || version_compare(PHP_VERSION, '7.1', '>='), 'This needs mcrypt extension to be loaded.');
 
         $restore = Security::engine();
         $txt = "Obi-wan you're our only hope";

+ 15 - 0
tests/test_app/TestApp/Routing/Filter/AppendFilter.php

@@ -0,0 +1,15 @@
+<?php
+namespace TestApp\Routing\Filter;
+
+use Cake\Event\Event;
+use Cake\Network\Response;
+use Cake\Routing\DispatcherFilter;
+
+class AppendFilter extends DispatcherFilter
+{
+    public function afterDispatch(Event $event)
+    {
+        $response = $event->data['response'];
+        $response->body($response->body() . ' appended content');
+    }
+}