Browse Source

Merge branch 'master' into 4.next

Corey Taylor 6 years ago
parent
commit
58735641ec

+ 8 - 1
.github/workflows/ci.yml

@@ -1,6 +1,13 @@
 name: CakePHP CI
 
-on: [pull_request]
+on:
+  push:
+    branches:
+      - master
+      - '4.next'
+  pull_request:
+    branches:
+      - '*'
 
 jobs:
   testsuite:

+ 1 - 1
Makefile

@@ -28,7 +28,7 @@ DASH_VERSION=$(shell echo $(VERSION) | sed -e s/\\./-/g)
 # correct tag in that repo.
 # For 3.1.x use 3.1.2
 # For 3.0.x use 3.0.5
-APP_VERSION:=master
+APP_VERSION:=3.x
 
 ALL: help
 .PHONY: help install test need-version bump-version tag-version

+ 1 - 1
src/Collection/CollectionInterface.php

@@ -257,7 +257,7 @@ interface CollectionInterface extends Iterator, JsonSerializable
      * @param callable|string $callback the callback or column name to use for sorting
      * @param int $type the type of comparison to perform, either SORT_STRING
      * SORT_NUMERIC or SORT_NATURAL
-     * @see \Cake\Collection\CollectionIterface::sortBy()
+     * @see \Cake\Collection\CollectionInterface::sortBy()
      * @return mixed The value of the top element in the collection
      */
     public function max($callback, int $type = \SORT_NUMERIC);

+ 2 - 1
src/Console/CommandCollection.php

@@ -64,7 +64,8 @@ class CommandCollection implements IteratorAggregate, Countable
         if (!is_subclass_of($command, Shell::class) && !is_subclass_of($command, CommandInterface::class)) {
             $class = is_string($command) ? $command : get_class($command);
             throw new InvalidArgumentException(sprintf(
-                "Cannot use '%s' for command '%s' it is not a subclass of Cake\Console\Shell or Cake\Console\Command.",
+                "Cannot use '%s' for command '%s'. " .
+                "It is not a subclass of Cake\Console\Shell or Cake\Command\CommandInterface.",
                 $class,
                 $name
             ));

+ 1 - 1
src/Console/Exception/StopException.php

@@ -20,7 +20,7 @@ namespace Cake\Console\Exception;
  *
  * @see \Cake\Console\Shell::_stop()
  * @see \Cake\Console\Shell::error()
- * @see \Cake\Console\Command::abort()
+ * @see \Cake\Command\BaseCommand::abort()
  */
 class StopException extends ConsoleException
 {

+ 2 - 1
src/Database/Expression/TupleComparison.php

@@ -119,7 +119,8 @@ class TupleComparison extends Comparison
             if ($isMultiOperation) {
                 $bound = [];
                 foreach ($value as $k => $val) {
-                    $valType = $type ? $type[$k] : $type;
+                    /** @var string $valType */
+                    $valType = $type && isset($type[$k]) ? $type[$k] : $type;
                     $bound[] = $this->_bindValue($val, $generator, $valType);
                 }
 

+ 14 - 14
src/Database/FunctionsBuilder.php

@@ -50,7 +50,7 @@ class FunctionsBuilder
      * argument.
      *
      * @param string $name name of the function to build
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @param string $return The return type for the function
      * @return \Cake\Database\Expression\FunctionExpression
@@ -83,7 +83,7 @@ class FunctionsBuilder
     /**
      * Returns a FunctionExpression representing a call to SQL SUM function.
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
@@ -100,7 +100,7 @@ class FunctionsBuilder
     /**
      * Returns a FunctionExpression representing a call to SQL AVG function.
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
@@ -112,7 +112,7 @@ class FunctionsBuilder
     /**
      * Returns a FunctionExpression representing a call to SQL MAX function.
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
@@ -124,7 +124,7 @@ class FunctionsBuilder
     /**
      * Returns a FunctionExpression representing a call to SQL MIN function.
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
@@ -136,7 +136,7 @@ class FunctionsBuilder
     /**
      * Returns a FunctionExpression representing a call to SQL COUNT function.
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
@@ -186,11 +186,11 @@ class FunctionsBuilder
      * Returns the specified date part from the SQL expression.
      *
      * @param string $part Part of the date to return.
-     * @param string $expression Expression to obtain the date part from.
+     * @param string|\Cake\Database\ExpressionInterface $expression Expression to obtain the date part from.
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
-    public function datePart(string $part, string $expression, array $types = []): FunctionExpression
+    public function datePart(string $part, $expression, array $types = []): FunctionExpression
     {
         return $this->extract($part, $expression, $types);
     }
@@ -199,11 +199,11 @@ class FunctionsBuilder
      * Returns the specified date part from the SQL expression.
      *
      * @param string $part Part of the date to return.
-     * @param string $expression Expression to obtain the date part from.
+     * @param string|\Cake\Database\ExpressionInterface $expression Expression to obtain the date part from.
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
-    public function extract(string $part, string $expression, array $types = []): FunctionExpression
+    public function extract(string $part, $expression, array $types = []): FunctionExpression
     {
         $expression = $this->_literalArgumentFunction('EXTRACT', $expression, $types, 'integer');
         $expression->setConjunction(' FROM')->add([$part => 'literal'], [], true);
@@ -214,13 +214,13 @@ class FunctionsBuilder
     /**
      * Add the time unit to the date expression
      *
-     * @param string $expression Expression to obtain the date part from.
+     * @param string|\Cake\Database\ExpressionInterface $expression Expression to obtain the date part from.
      * @param string|int $value Value to be added. Use negative to subtract.
      * @param string $unit Unit of the value e.g. hour or day.
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
-    public function dateAdd(string $expression, $value, string $unit, array $types = []): FunctionExpression
+    public function dateAdd($expression, $value, string $unit, array $types = []): FunctionExpression
     {
         if (!is_numeric($value)) {
             $value = 0;
@@ -236,7 +236,7 @@ class FunctionsBuilder
      * Returns a FunctionExpression representing a call to SQL WEEKDAY function.
      * 1 - Sunday, 2 - Monday, 3 - Tuesday...
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */
@@ -249,7 +249,7 @@ class FunctionsBuilder
      * Returns a FunctionExpression representing a call to SQL WEEKDAY function.
      * 1 - Sunday, 2 - Monday, 3 - Tuesday...
      *
-     * @param mixed $expression the function argument
+     * @param string|\Cake\Database\ExpressionInterface $expression the function argument
      * @param array $types list of types to bind to the arguments
      * @return \Cake\Database\Expression\FunctionExpression
      */

+ 3 - 3
src/Database/Query.php

@@ -637,7 +637,7 @@ class Query implements ExpressionInterface, IteratorAggregate
      * @param array|string $tables list of tables to be joined in the query
      * @param array $types associative array of type names used to bind values to query
      * @param bool $overwrite whether to reset joins with passed list or not
-     * @see \Cake\Database\Type
+     * @see \Cake\Database\TypeFactory
      * @return $this
      */
     public function join($tables, $types = [], $overwrite = false)
@@ -925,7 +925,7 @@ class Query implements ExpressionInterface, IteratorAggregate
      * @param string|array|\Cake\Database\ExpressionInterface|\Closure|null $conditions The conditions to filter on.
      * @param array $types associative array of type names used to bind values to query
      * @param bool $overwrite whether to reset conditions with passed list or not
-     * @see \Cake\Database\Type
+     * @see \Cake\Database\TypeFactory
      * @see \Cake\Database\Expression\QueryExpression
      * @return $this
      */
@@ -1095,7 +1095,7 @@ class Query implements ExpressionInterface, IteratorAggregate
      * @param string|array|\Cake\Database\ExpressionInterface|\Closure $conditions The conditions to add with AND.
      * @param array $types associative array of type names used to bind values to query
      * @see \Cake\Database\Query::where()
-     * @see \Cake\Database\Type
+     * @see \Cake\Database\TypeFactory
      * @return $this
      */
     public function andWhere($conditions, array $types = [])

+ 5 - 1
src/Error/ErrorLogger.php

@@ -111,7 +111,11 @@ class ErrorLogger
             $trace = Debugger::formatTrace($exception, ['format' => 'points']);
             $message .= "\nStack Trace:\n";
             foreach ($trace as $line) {
-                $message .= "- {$line['file']}:{$line['line']}\n";
+                if (is_string($line)) {
+                    $message .= '- ' . $line;
+                } else {
+                    $message .= "- {$line['file']}:{$line['line']}\n";
+                }
             }
         }
 

+ 1 - 1
src/I18n/Number.php

@@ -44,7 +44,7 @@ class Number
 
     /**
      * ICU Constant for accounting format; not yet widely supported by INTL library.
-     * This will be able to go away once CakePHP minimum PHP requirement is 7.5 or higher.
+     * This will be able to go away once CakePHP minimum PHP requirement is 7.4.1 or higher.
      * See UNUM_CURRENCY_ACCOUNTING in https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/unum_8h.html
      */
     public const CURRENCY_ACCOUNTING = 12;

+ 4 - 1
src/I18n/Parser/PoFileParser.php

@@ -95,6 +95,7 @@ class PoFileParser
             } elseif (substr($line, 0, 7) === 'msgid "') {
                 // We start a new msg so save previous
                 $this->_addMessage($messages, $item);
+                /** @psalm-suppress InvalidArrayOffset */
                 $item['ids']['singular'] = substr($line, 7, -1);
                 $stage = ['ids', 'singular'];
             } elseif (substr($line, 0, 8) === 'msgstr "') {
@@ -108,7 +109,7 @@ class PoFileParser
                     case 2:
                         /**
                          * @psalm-suppress PossiblyUndefinedArrayOffset
-                         * @psalm-suppress PossiblyInvalidArrayOffset
+                         * @psalm-suppress InvalidArrayOffset
                          * @psalm-suppress PossiblyNullArrayAccess
                          */
                         $item[$stage[0]][$stage[1]] .= substr($line, 1, -1);
@@ -117,6 +118,7 @@ class PoFileParser
                     case 1:
                         /**
                          * @psalm-suppress PossiblyUndefinedArrayOffset
+                         * @psalm-suppress InvalidArrayOffset
                          * @psalm-suppress PossiblyInvalidOperand
                          * @psalm-suppress PossiblyNullOperand
                          */
@@ -124,6 +126,7 @@ class PoFileParser
                         break;
                 }
             } elseif (substr($line, 0, 14) === 'msgid_plural "') {
+                /** @psalm-suppress InvalidArrayOffset */
                 $item['ids']['plural'] = substr($line, 14, -1);
                 $stage = ['ids', 'plural'];
             } elseif (substr($line, 0, 7) === 'msgstr[') {

+ 11 - 5
src/Routing/Route/Route.php

@@ -714,6 +714,14 @@ class Route
         }
         $url += $hostOptions;
 
+        // Ensure controller/action keys are not null.
+        if (
+            (isset($keyNames['controller']) && !isset($url['controller'])) ||
+            (isset($keyNames['action']) && !isset($url['action']))
+        ) {
+            return null;
+        }
+
         return $this->_writeUrl($url, $pass, $query);
     }
 
@@ -759,12 +767,10 @@ class Route
 
         $search = $replace = [];
         foreach ($this->keys as $key) {
-            $string = null;
-            if (isset($params[$key])) {
-                $string = $params[$key];
-            } elseif (strpos($out, $key) !== strlen($out) - strlen($key)) {
-                $key .= '/';
+            if (!array_key_exists($key, $params)) {
+                throw new InvalidArgumentException("Missing required route key `{$key}`");
             }
+            $string = $params[$key];
             if ($this->braceKeys) {
                 $search[] = "{{$key}}";
             } else {

+ 1 - 1
src/View/Form/ArrayContext.php

@@ -292,7 +292,7 @@ class ArrayContext implements ContextInterface
      *
      * @param string $field A dot separated path to get a schema type for.
      * @return string|null An abstract data type or null.
-     * @see \Cake\Database\Type
+     * @see \Cake\Database\TypeFactory
      */
     public function type(string $field): ?string
     {

+ 1 - 1
src/View/Form/ContextInterface.php

@@ -100,7 +100,7 @@ interface ContextInterface
      *
      * @param string $field A dot separated path to get a schema type for.
      * @return string|null An abstract data type or null.
-     * @see \Cake\Database\Type
+     * @see \Cake\Database\TypeFactory
      */
     public function type(string $field): ?string;
 

+ 1 - 1
src/View/Form/EntityContext.php

@@ -693,7 +693,7 @@ class EntityContext implements ContextInterface
      *
      * @param string $field A dot separated path to get a schema type for.
      * @return string|null An abstract data type or null.
-     * @see \Cake\Database\Type
+     * @see \Cake\Database\TypeFactory
      */
     public function type(string $field): ?string
     {

+ 3 - 3
tests/TestCase/Console/CommandCollectionTest.php

@@ -61,7 +61,7 @@ class CommandCollectionTest extends TestCase
     public function testConstructorInvalidClass()
     {
         $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'nope\' it is not a subclass of Cake\Console\Shell');
+        $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'nope\'. It is not a subclass of Cake\Console\Shell');
         new CommandCollection([
             'sample' => SampleShell::class,
             'nope' => stdClass::class,
@@ -133,7 +133,7 @@ class CommandCollectionTest extends TestCase
     public function testAddInvalidInstance()
     {
         $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\' it is not a subclass of Cake\Console\Shell');
+        $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\'. It is not a subclass of Cake\Console\Shell');
         $collection = new CommandCollection();
         $shell = new stdClass();
         $collection->add('routes', $shell);
@@ -179,7 +179,7 @@ class CommandCollectionTest extends TestCase
     public function testInvalidShellClassName()
     {
         $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\' it is not a subclass of Cake\Console\Shell');
+        $this->expectExceptionMessage('Cannot use \'stdClass\' for command \'routes\'. It is not a subclass of Cake\Console\Shell');
         $collection = new CommandCollection();
         $collection->add('routes', stdClass::class);
     }

+ 5 - 0
tests/TestCase/Database/FunctionsBuilderTest.php

@@ -15,6 +15,7 @@ declare(strict_types=1);
  */
 namespace Cake\Test\TestCase\Database;
 
+use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\FunctionsBuilder;
 use Cake\Database\ValueBinder;
 use Cake\TestSuite\TestCase;
@@ -199,6 +200,10 @@ class FunctionsBuilderTest extends TestCase
         $this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
         $this->assertSame('DATE_ADD(created, INTERVAL -3 day)', $function->sql(new ValueBinder()));
         $this->assertSame('datetime', $function->getReturnType());
+
+        $function = $this->functions->dateAdd(new IdentifierExpression('created'), -3, 'day');
+        $this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
+        $this->assertSame('DATE_ADD(created, INTERVAL -3 day)', $function->sql(new ValueBinder()));
     }
 
     /**

+ 31 - 0
tests/TestCase/Routing/Route/RouteTest.php

@@ -1011,6 +1011,9 @@ class RouteTest extends TestCase
         $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 9]);
         $this->assertSame('/posts/view/9', $result);
 
+        $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 9]);
+        $this->assertSame('/posts/view/9', $result);
+
         $result = $route->match(['plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '9']);
         $this->assertSame('/posts/view/9', $result);
 
@@ -1445,6 +1448,34 @@ class RouteTest extends TestCase
     }
 
     /**
+     * Test match handles optional keys
+     *
+     * @return void
+     */
+    public function testMatchNullValueOptionalKey()
+    {
+        $route = new Route('/path/:optional/fixed');
+        $this->assertSame('/path/fixed', $route->match(['optional' => null]));
+
+        $route = new Route('/path/{optional}/fixed');
+        $this->assertSame('/path/fixed', $route->match(['optional' => null]));
+    }
+
+    /**
+     * Test matching fails on required keys (controller/action)
+     *
+     * @return void
+     */
+    public function testMatchControllerRequiredKeys()
+    {
+        $route = new Route('/:controller/', ['action' => 'index']);
+        $this->assertNull($route->match(['controller' => null, 'action' => 'index']));
+
+        $route = new Route('/test/:action', ['controller' => 'thing']);
+        $this->assertNull($route->match(['action' => null, 'controller' => 'thing']));
+    }
+
+    /**
      * Test restructuring args with pass key
      *
      * @return void

+ 6 - 14
tests/TestCase/Routing/RouterTest.php

@@ -68,7 +68,7 @@ class RouterTest extends TestCase
         });
         $this->assertRegExp('/^http(s)?:\/\//', Router::url('/', true));
         $this->assertRegExp('/^http(s)?:\/\//', Router::url(null, true));
-        $this->assertRegExp('/^http(s)?:\/\//', Router::url(['_full' => true]));
+        $this->assertRegExp('/^http(s)?:\/\//', Router::url(['controller' => 'test', '_full' => true]));
     }
 
     /**
@@ -156,6 +156,7 @@ class RouterTest extends TestCase
     }
 
     /**
+<<<<<<< HEAD
      * Test that full base URL can be generated from request context too if
      * App.fullBaseUrl is not set.
      *
@@ -174,25 +175,16 @@ class RouterTest extends TestCase
     }
 
     /**
-     * testRouteDefaultParams method
-     *
-     * @return void
-     */
-    public function testRouteDefaultParams()
-    {
-        Router::connect('/:controller', ['controller' => 'posts']);
-        $this->assertEquals(Router::url(['action' => 'index']), '/');
-    }
-
-    /**
+=======
+>>>>>>> 3.next
      * testRouteExists method
      *
      * @return void
      */
     public function testRouteExists()
     {
-        Router::connect('/:controller/:action', ['controller' => 'posts']);
-        $this->assertTrue(Router::routeExists(['action' => 'view']));
+        Router::connect('/posts/:action', ['controller' => 'posts']);
+        $this->assertTrue(Router::routeExists(['controller' => 'posts', 'action' => 'view']));
 
         $this->assertFalse(Router::routeExists(['action' => 'view', 'controller' => 'users', 'plugin' => 'test']));
     }

+ 5 - 8
tests/TestCase/View/Helper/HtmlHelperTest.php

@@ -107,7 +107,9 @@ class HtmlHelperTest extends TestCase
     public function testLink()
     {
         Router::reload();
+        Router::connect('/:controller', ['action' => 'index']);
         Router::connect('/:controller/:action/*');
+        Router::setRequest(new ServerRequest());
 
         $this->View->setRequest($this->View->getRequest()->withAttribute('webroot', ''));
 
@@ -115,19 +117,14 @@ class HtmlHelperTest extends TestCase
         $expected = ['a' => ['href' => '/home'], 'preg:/\/home/', '/a'];
         $this->assertHtml($expected, $result);
 
-        $result = $this->Html->link(['action' => 'login', '<[You]>']);
+        $result = $this->Html->link(['controller' => 'users', 'action' => 'login', '<[You]>']);
         $expected = [
-            'a' => ['href' => '/login/%3C%5BYou%5D%3E'],
-            'preg:/\/login\/&lt;\[You\]&gt;/',
+            'a' => ['href' => '/users/login/%3C%5BYou%5D%3E'],
+            'preg:/\/users\/login\/&lt;\[You\]&gt;/',
             '/a',
         ];
         $this->assertHtml($expected, $result);
 
-        Router::reload();
-        Router::setRequest(new ServerRequest());
-        Router::connect('/:controller', ['action' => 'index']);
-        Router::connect('/:controller/:action/*');
-
         $result = $this->Html->link('Posts', ['controller' => 'posts', 'action' => 'index', '_full' => true]);
         $expected = ['a' => ['href' => Router::fullBaseUrl() . '/posts'], 'Posts', '/a'];
         $this->assertHtml($expected, $result);

+ 35 - 20
tests/TestCase/View/Helper/PaginatorHelperTest.php

@@ -56,6 +56,11 @@ class PaginatorHelperTest extends TestCase
         Configure::write('Config.language', 'eng');
         $request = new ServerRequest([
             'url' => '/',
+            'params' => [
+                'plugin' => null,
+                'controller' => '',
+                'action' => 'index',
+            ],
         ]);
         $request = $request->withAttribute('paging', [
             'Article' => [
@@ -76,6 +81,7 @@ class PaginatorHelperTest extends TestCase
         Router::reload();
         Router::connect('/:controller/:action/*');
         Router::connect('/:plugin/:controller/:action/*');
+        Router::setRequest($request);
 
         $this->locale = I18n::getLocale();
     }
@@ -1877,41 +1883,50 @@ class PaginatorHelperTest extends TestCase
     {
         Router::reload();
         Router::connect('/:controller/:action/:page');
-
-        $this->View->setRequest($this->View->getRequest()->withAttribute('paging', [
-            'Client' => [
-                'page' => 8,
-                'current' => 3,
-                'count' => 30,
-                'prevPage' => false,
-                'nextPage' => 2,
-                'pageCount' => 15,
-            ],
-        ]));
+        $request = $this->View
+            ->getRequest()
+            ->withAttribute('params', [
+                'plugin' => null,
+                'controller' => 'clients',
+                'action' => 'index',
+            ])
+            ->withAttribute('paging', [
+                'Client' => [
+                    'page' => 8,
+                    'current' => 3,
+                    'count' => 30,
+                    'prevPage' => false,
+                    'nextPage' => 2,
+                    'pageCount' => 15,
+                ],
+            ]);
+        $this->View->setRequest($request);
+        Router::setRequest($request);
 
         $this->Paginator->options(['routePlaceholders' => ['page']]);
 
         $result = $this->Paginator->numbers();
         $expected = [
-            ['li' => []], ['a' => ['href' => '/index/4']], '4', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/5']], '5', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/6']], '6', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/7']], '7', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/4']], '4', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/5']], '5', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/6']], '6', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/7']], '7', '/a', '/li',
             ['li' => ['class' => 'active']], '<a href=""', '8', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/9']], '9', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/10']], '10', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/11']], '11', '/a', '/li',
-            ['li' => []], ['a' => ['href' => '/index/12']], '12', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/9']], '9', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/10']], '10', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/11']], '11', '/a', '/li',
+            ['li' => []], ['a' => ['href' => '/clients/index/12']], '12', '/a', '/li',
         ];
         $this->assertHtml($expected, $result);
 
         Router::reload();
         Router::connect('/:controller/:action/:sort/:direction');
+        Router::setRequest($request);
 
         $this->Paginator->options(['routePlaceholders' => ['sort', 'direction']]);
         $result = $this->Paginator->sort('title');
         $expected = [
-            'a' => ['href' => '/index/title/asc'],
+            'a' => ['href' => '/clients/index/title/asc'],
             'Title',
             '/a',
         ];