Browse Source

Merge branch '4.next' into 5.x

ADmad 2 years ago
parent
commit
5fc5db0a61

+ 2 - 2
.phive/phars.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phive xmlns="https://phar.io/phive">
-  <phar name="phpstan" version="1.10.11" installed="1.10.11" location="./tools/phpstan" copy="false"/>
-  <phar name="psalm" version="5.9.0" installed="5.9.0" location="./tools/psalm" copy="false"/>
+  <phar name="phpstan" version="1.10.14" installed="1.10.14" location="./tools/phpstan" copy="false"/>
+  <phar name="psalm" version="5.10.0" installed="5.10.0" location="./tools/psalm" copy="false"/>
 </phive>

+ 18 - 0
src/Datasource/Paging/NumericPaginator.php

@@ -21,6 +21,7 @@ use Cake\Core\InstanceConfigTrait;
 use Cake\Datasource\Paging\Exception\PageOutOfBoundsException;
 use Cake\Datasource\QueryInterface;
 use Cake\Datasource\RepositoryInterface;
+use function Cake\Core\deprecationWarning;
 
 /**
  * This class is used to handle automatic model data pagination.
@@ -56,6 +57,8 @@ class NumericPaginator implements PaginatorInterface
         'limit' => 20,
         'maxLimit' => 100,
         'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+        'sortableFields' => null,
+        'finder' => 'all',
     ];
 
     /**
@@ -254,6 +257,21 @@ class NumericPaginator implements PaginatorInterface
     {
         $alias = $object->getAlias();
         $defaults = $this->getDefaults($alias, $settings);
+
+        $validSettings = array_merge(
+            array_keys($this->_defaultConfig),
+            ['order', 'scope']
+        );
+        $extraSettings = array_diff_key($defaults, array_flip($validSettings));
+        if ($extraSettings) {
+            deprecationWarning(
+                '4.5.0',
+                'Passing query options as paginator settings is deprecated.'
+                . ' Use a custom finder through `finder` config instead.'
+                . ' Extra keys found are: ' . implode(',', array_keys($extraSettings))
+            );
+        }
+
         $options = $this->mergeOptions($params, $defaults);
         $options = $this->validateSort($object, $options);
         $options = $this->checkLimit($options);

+ 11 - 0
src/Datasource/ResultSetDecorator.php

@@ -17,6 +17,7 @@ declare(strict_types=1);
 namespace Cake\Datasource;
 
 use Cake\Collection\Collection;
+use Cake\Core\Configure;
 
 /**
  * Generic ResultSet decorator. This will make any traversable object appear to
@@ -27,4 +28,14 @@ use Cake\Collection\Collection;
  */
 class ResultSetDecorator extends Collection implements ResultSetInterface
 {
+    /**
+     * @inheritDoc
+     */
+    public function __debugInfo(): array
+    {
+        $parentInfo = parent::__debugInfo();
+        $limit = Configure::read('App.ResultSetDebugLimit', 10);
+
+        return array_merge($parentInfo, ['items' => $this->take($limit)->toArray()]);
+    }
 }

+ 6 - 1
src/ORM/AssociationCollection.php

@@ -70,6 +70,9 @@ class AssociationCollection implements IteratorAggregate
      * @param string $alias The association alias
      * @param \Cake\ORM\Association $association The association to add.
      * @return \Cake\ORM\Association The association object being added.
+     * @template T of \Cake\ORM\Association
+     * @psalm-param T $association
+     * @psalm-return T
      */
     public function add(string $alias, Association $association): Association
     {
@@ -86,7 +89,9 @@ class AssociationCollection implements IteratorAggregate
      * @param array<string, mixed> $options List of options to configure the association definition.
      * @return \Cake\ORM\Association
      * @throws \InvalidArgumentException
-     * @psalm-param class-string<\Cake\ORM\Association> $className
+     * @template T of \Cake\ORM\Association
+     * @psalm-param class-string<T> $className
+     * @psalm-return T
      */
     public function load(string $className, string $associated, array $options = []): Association
     {

+ 5 - 11
src/ORM/Table.php

@@ -630,9 +630,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     public function hasField(string $field): bool
     {
-        $schema = $this->getSchema();
-
-        return $schema->getColumn($field) !== null;
+        return $this->getSchema()->getColumn($field) !== null;
     }
 
     /**
@@ -2939,9 +2937,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function newEntity(array $data, array $options = []): EntityInterface
     {
         $options['associated'] ??= $this->_associations->keys();
-        $marshaller = $this->marshaller();
 
-        return $marshaller->one($data, $options);
+        return $this->marshaller()->one($data, $options);
     }
 
     /**
@@ -2979,9 +2976,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function newEntities(array $data, array $options = []): array
     {
         $options['associated'] ??= $this->_associations->keys();
-        $marshaller = $this->marshaller();
 
-        return $marshaller->many($data, $options);
+        return $this->marshaller()->many($data, $options);
     }
 
     /**
@@ -3038,9 +3034,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface
     {
         $options['associated'] ??= $this->_associations->keys();
-        $marshaller = $this->marshaller();
 
-        return $marshaller->merge($entity, $data, $options);
+        return $this->marshaller()->merge($entity, $data, $options);
     }
 
     /**
@@ -3077,9 +3072,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function patchEntities(iterable $entities, array $data, array $options = []): array
     {
         $options['associated'] ??= $this->_associations->keys();
-        $marshaller = $this->marshaller();
 
-        return $marshaller->mergeMany($entities, $data, $options);
+        return $this->marshaller()->mergeMany($entities, $data, $options);
     }
 
     /**

+ 3 - 3
src/Routing/RouteBuilder.php

@@ -983,13 +983,13 @@ class RouteBuilder
     }
 
     /**
-     * Apply a middleware to the current route scope.
+     * Apply one or many middleware to the current route scope.
      *
-     * Requires middleware to be registered via `registerMiddleware()`
+     * Requires middleware to be registered via `registerMiddleware()`.
      *
      * @param string ...$names The names of the middleware to apply to the current scope.
      * @return $this
-     * @throws \InvalidArgumentException
+     * @throws \InvalidArgumentException If it cannot apply one of the given middleware or middleware groups.
      * @see \Cake\Routing\RouteCollection::addMiddlewareToScope()
      */
     public function applyMiddleware(string ...$names)

+ 1 - 1
src/Validation/Validation.php

@@ -1544,7 +1544,7 @@ class Validation
         }
         $options += ['extended' => false];
         if ($options['extended']) {
-            return true;
+            return preg_match('//u', $value) === 1;
         }
 
         return preg_match('/[\x{10000}-\x{10FFFF}]/u', $value) === 0;

+ 26 - 25
tests/TestCase/Datasource/Paging/NumericPaginatorTest.php

@@ -100,30 +100,32 @@ class NumericPaginatorTest extends TestCase
      */
     public function testPaginateCustomFindFieldsArray(): void
     {
-        $table = $this->getTableLocator()->get('PaginatorPosts');
-        $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
-        $table->save(new Entity($data));
-
-        $settings = [
-            'finder' => 'list',
-            'conditions' => ['PaginatorPosts.published' => 'Y'],
-            'limit' => 2,
-        ];
-        $results = $this->Paginator->paginate($table, [], $settings);
-
-        $result = $results->toArray();
-        $expected = [
-            1 => 'First Post',
-            2 => 'Second Post',
-        ];
-        $this->assertEquals($expected, $result);
-
-        $result = $results->pagingParams();
-        $this->assertSame(2, $result['count']);
-        $this->assertSame(3, $result['totalCount']);
-        $this->assertSame(2, $result['pageCount']);
-        $this->assertTrue($result['hasNextPage']);
-        $this->assertFalse($result['hasPrevPage']);
+        $this->deprecated(function () {
+            $table = $this->getTableLocator()->get('PaginatorPosts');
+            $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
+            $table->save(new Entity($data));
+
+            $settings = [
+                'finder' => 'list',
+                'conditions' => ['PaginatorPosts.published' => 'Y'],
+                'limit' => 2,
+            ];
+            $results = $this->Paginator->paginate($table, [], $settings);
+
+            $result = $results->toArray();
+            $expected = [
+                1 => 'First Post',
+                2 => 'Second Post',
+            ];
+            $this->assertEquals($expected, $result);
+
+            $result = $results->pagingParams();
+            $this->assertSame(2, $result['count']);
+            $this->assertSame(3, $result['totalCount']);
+            $this->assertSame(2, $result['pageCount']);
+            $this->assertTrue($result['hasNextPage']);
+            $this->assertFalse($result['hasPrevPage']);
+        });
     }
 
     /**
@@ -134,7 +136,6 @@ class NumericPaginatorTest extends TestCase
         $settings = [
             'PaginatorPosts' => [
                 'finder' => 'published',
-                'fields' => ['id', 'title'],
                 'maxLimit' => 10,
             ],
         ];

+ 29 - 10
tests/TestCase/Datasource/Paging/PaginatorTestTrait.php

@@ -111,7 +111,10 @@ trait PaginatorTestTrait
                 'order' => ['PaginatorPosts.id' => 'ASC'],
                 'page' => 1,
             ]);
-        $this->Paginator->paginate($table, $params, $settings);
+
+        $this->deprecated(function () use ($table, $params, $settings) {
+            $this->Paginator->paginate($table, $params, $settings);
+        });
     }
 
     /**
@@ -272,7 +275,10 @@ trait PaginatorTestTrait
         ];
         $defaults = $this->Paginator->getDefaults('Silly', $settings);
         $result = $this->Paginator->mergeOptions([], $defaults);
-        $this->assertEquals($settings, $result);
+        $this->assertEquals($settings + [
+            'sortableFields' => null,
+            'finder' => 'all',
+        ], $result);
 
         $defaults = $this->Paginator->getDefaults('Posts', $settings);
         $result = $this->Paginator->mergeOptions([], $defaults);
@@ -281,6 +287,8 @@ trait PaginatorTestTrait
             'limit' => 10,
             'maxLimit' => 50,
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
     }
@@ -313,6 +321,7 @@ trait PaginatorTestTrait
             'maxLimit' => 100,
             'finder' => 'myCustomFind',
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
         ];
         $this->assertEquals($expected, $result);
 
@@ -332,6 +341,7 @@ trait PaginatorTestTrait
             'finder' => 'myCustomFind',
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
             'scope' => 'nonexistent',
+            'sortableFields' => null,
         ];
         $this->assertEquals($expected, $result);
 
@@ -351,6 +361,7 @@ trait PaginatorTestTrait
             'finder' => 'myCustomFind',
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
             'scope' => 'scope',
+            'sortableFields' => null,
         ];
         $this->assertEquals($expected, $result);
     }
@@ -378,6 +389,7 @@ trait PaginatorTestTrait
             'maxLimit' => 100,
             'finder' => 'myCustomFind',
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
         ];
         $this->assertEquals($expected, $result);
     }
@@ -403,6 +415,8 @@ trait PaginatorTestTrait
             'limit' => 75,
             'maxLimit' => 100,
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
     }
@@ -432,6 +446,8 @@ trait PaginatorTestTrait
             'limit' => 10,
             'maxLimit' => 100,
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
     }
@@ -454,6 +470,7 @@ trait PaginatorTestTrait
             'limit' => 20,
             'maxLimit' => 100,
         ];
+
         $this->Paginator->setConfig('allowedParameters', ['fields']);
         $defaults = $this->Paginator->getDefaults('Post', $settings);
         $result = $this->Paginator->mergeOptions($params, $defaults);
@@ -463,6 +480,8 @@ trait PaginatorTestTrait
             'maxLimit' => 100,
             'fields' => ['bad.stuff'],
             'allowedParameters' => ['limit', 'sort', 'page', 'direction', 'fields'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
     }
@@ -484,6 +503,8 @@ trait PaginatorTestTrait
             'maxLimit' => 100,
             'paramType' => 'named',
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
 
@@ -499,6 +520,8 @@ trait PaginatorTestTrait
             'maxLimit' => 10,
             'paramType' => 'named',
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
     }
@@ -526,6 +549,8 @@ trait PaginatorTestTrait
                 'Users.username' => 'asc',
             ],
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
 
@@ -547,6 +572,8 @@ trait PaginatorTestTrait
                 'Users.username' => 'asc',
             ],
             'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
+            'sortableFields' => null,
+            'finder' => 'all',
         ];
         $this->assertEquals($expected, $result);
     }
@@ -1134,9 +1161,7 @@ trait PaginatorTestTrait
         $params = ['page' => '-1'];
         $settings = [
             'PaginatorPosts' => [
-                'contain' => ['PaginatorAuthor'],
                 'maxLimit' => 10,
-                'group' => 'PaginatorPosts.published',
                 'order' => ['PaginatorPosts.id' => 'ASC'],
             ],
         ];
@@ -1146,8 +1171,6 @@ trait PaginatorTestTrait
         $query->expects($this->once())
             ->method('applyOptions')
             ->with([
-                'contain' => ['PaginatorAuthor'],
-                'group' => 'PaginatorPosts.published',
                 'limit' => 10,
                 'order' => ['PaginatorPosts.id' => 'ASC'],
                 'page' => 1,
@@ -1185,10 +1208,8 @@ trait PaginatorTestTrait
         $params = ['page' => '-1'];
         $settings = [
             'PaginatorPosts' => [
-                'contain' => ['PaginatorAuthor'],
                 'maxLimit' => 10,
                 'limit' => 5,
-                'group' => 'PaginatorPosts.published',
                 'order' => ['PaginatorPosts.id' => 'ASC'],
             ],
         ];
@@ -1199,8 +1220,6 @@ trait PaginatorTestTrait
         $query->expects($this->once())
             ->method('applyOptions')
             ->with([
-                'contain' => ['PaginatorAuthor'],
-                'group' => 'PaginatorPosts.published',
                 'limit' => 5,
                 'order' => ['PaginatorPosts.id' => 'ASC'],
                 'page' => 1,

+ 26 - 25
tests/TestCase/Datasource/Paging/SimplePaginatorTest.php

@@ -97,30 +97,32 @@ class SimplePaginatorTest extends NumericPaginatorTest
      */
     public function testPaginateCustomFindFieldsArray(): void
     {
-        $table = $this->getTableLocator()->get('PaginatorPosts');
-        $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
-        $table->save(new Entity($data));
-
-        $settings = [
-            'finder' => 'list',
-            'conditions' => ['PaginatorPosts.published' => 'Y'],
-            'limit' => 2,
-        ];
-        $results = $this->Paginator->paginate($table, [], $settings);
-
-        $result = $results->toArray();
-        $expected = [
-            1 => 'First Post',
-            2 => 'Second Post',
-        ];
-        $this->assertEquals($expected, $result);
-
-        $result = $results->pagingParams();
-        $this->assertSame(1, $result['currentPage']);
-        $this->assertNull($result['totalCount']);
-        $this->assertNull($result['pageCount']);
-        $this->assertTrue($result['hasNextPage']);
-        $this->assertFalse($result['hasPrevPage']);
+        $this->deprecated(function () {
+            $table = $this->getTableLocator()->get('PaginatorPosts');
+            $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
+            $table->save(new Entity($data));
+
+            $settings = [
+                'finder' => 'list',
+                'conditions' => ['PaginatorPosts.published' => 'Y'],
+                'limit' => 2,
+            ];
+            $results = $this->Paginator->paginate($table, [], $settings);
+
+            $result = $results->toArray();
+            $expected = [
+                1 => 'First Post',
+                2 => 'Second Post',
+            ];
+            $this->assertEquals($expected, $result);
+
+            $result = $results->pagingParams();
+            $this->assertSame(1, $result['currentPage']);
+            $this->assertNull($result['totalCount']);
+            $this->assertNull($result['pageCount']);
+            $this->assertTrue($result['hasNextPage']);
+            $this->assertFalse($result['hasPrevPage']);
+        });
     }
 
     /**
@@ -131,7 +133,6 @@ class SimplePaginatorTest extends NumericPaginatorTest
         $settings = [
             'PaginatorPosts' => [
                 'finder' => 'published',
-                'fields' => ['id', 'title'],
                 'maxLimit' => 10,
             ],
         ];

+ 15 - 0
tests/TestCase/Datasource/ResultSetDecoratorTest.php

@@ -17,6 +17,7 @@ declare(strict_types=1);
 namespace Cake\Test\TestCase\Datasource;
 
 use ArrayIterator;
+use Cake\Core\Configure;
 use Cake\Datasource\ResultSetDecorator;
 use Cake\TestSuite\TestCase;
 
@@ -89,4 +90,18 @@ class ResultSetDecoratorTest extends TestCase
         $this->assertSame(3, $decorator->count());
         $this->assertCount(3, $decorator);
     }
+
+    /**
+     * Test the __debugInfo() method which is used by DebugKit
+     */
+    public function testDebugInfo(): void
+    {
+        Configure::write('App.ResultSetDebugLimit', 2);
+        $data = new ArrayIterator([1, 2, 3]);
+        $decorator = new ResultSetDecorator($data);
+        $this->assertEquals([
+            'count' => 3,
+            'items' => [1, 2],
+        ], $decorator->__debugInfo());
+    }
 }

+ 6 - 0
tests/TestCase/Validation/ValidationTest.php

@@ -2869,6 +2869,9 @@ class ValidationTest extends TestCase
 
         // Grinning face
         $this->assertFalse(Validation::utf8('some' . "\xf0\x9f\x98\x80" . 'value'));
+
+        // incomplete character
+        $this->assertFalse(Validation::utf8("\xfe\xfe"));
     }
 
     /**
@@ -2895,6 +2898,9 @@ class ValidationTest extends TestCase
 
         // Grinning face
         $this->assertTrue(Validation::utf8('some' . "\xf0\x9f\x98\x80" . 'value', ['extended' => true]));
+
+        // incomplete characters
+        $this->assertFalse(Validation::utf8("\xfe\xfe", ['extended' => true]));
     }
 
     /**