Browse Source

Merge branch '4.x' into 4.next

ADmad 4 years ago
parent
commit
ccc6e858d3

+ 1 - 1
composer.json

@@ -111,7 +111,7 @@
             "@psalm"
         ],
         "stan-tests": "phpstan.phar analyze -c tests/phpstan.neon",
-        "stan-setup": "cp composer.json composer.backup && composer require --dev symfony/polyfill-php81 phpstan/phpstan:0.12.99 psalm/phar:~4.11.0 && mv composer.backup composer.json",
+        "stan-setup": "cp composer.json composer.backup && composer require --dev symfony/polyfill-php81 phpstan/phpstan:~1.0.0 psalm/phar:~4.11.0 && mv composer.backup composer.json",
         "test": "phpunit",
         "test-coverage": "phpunit --coverage-clover=clover.xml"
     },

+ 89 - 4
phpstan-baseline.neon

@@ -21,6 +21,11 @@ parameters:
 			path: src/Cache/Engine/MemcachedEngine.php
 
 		-
+			message: "#^Property Cake\\\\Cache\\\\Engine\\\\MemcachedEngine\\:\\:\\$_Memcached \\(Memcached\\) in isset\\(\\) is not nullable\\.$#"
+			count: 1
+			path: src/Cache/Engine/MemcachedEngine.php
+
+		-
 			message: "#^Parameter \\#1 \\$iterator of class LimitIterator constructor expects Iterator, Countable&Traversable\\<mixed, mixed\\> given\\.$#"
 			count: 2
 			path: src/Collection/Collection.php
@@ -136,11 +141,26 @@ parameters:
 			path: src/Console/ConsoleOptionParser.php
 
 		-
+			message: "#^Dead catch \\- Cake\\\\Console\\\\Exception\\\\StopException is never thrown in the try block\\.$#"
+			count: 1
+			path: src/Console/ShellDispatcher.php
+
+		-
+			message: "#^Property Cake\\\\Controller\\\\Controller\\:\\:\\$request \\(Cake\\\\Http\\\\ServerRequest\\) on left side of \\?\\? is not nullable\\.$#"
+			count: 1
+			path: src/Controller/Controller.php
+
+		-
 			message: "#^Property Cake\\\\Controller\\\\Controller\\:\\:\\$response \\(Cake\\\\Http\\\\Response\\) does not accept Psr\\\\Http\\\\Message\\\\ResponseInterface\\.$#"
 			count: 1
 			path: src/Controller/Controller.php
 
 		-
+			message: "#^Property Cake\\\\Controller\\\\Controller\\:\\:\\$response \\(Cake\\\\Http\\\\Response\\) on left side of \\?\\? is not nullable\\.$#"
+			count: 1
+			path: src/Controller/Controller.php
+
+		-
 			message: "#^Parameter \\#1 \\$request of method Cake\\\\Controller\\\\Controller\\:\\:setRequest\\(\\) expects Cake\\\\Http\\\\ServerRequest, Psr\\\\Http\\\\Message\\\\ServerRequestInterface given\\.$#"
 			count: 1
 			path: src/Controller/ControllerFactory.php
@@ -221,17 +241,37 @@ parameters:
 			path: src/Database/Expression/QueryExpression.php
 
 		-
+			message: "#^PHPDoc type array\\<string\\|null\\> of property Cake\\\\Database\\\\Expression\\\\TupleComparison\\:\\:\\$_type is not covariant with PHPDoc type string\\|null of overridden property Cake\\\\Database\\\\Expression\\\\ComparisonExpression\\:\\:\\$_type\\.$#"
+			count: 1
+			path: src/Database/Expression/TupleComparison.php
+
+		-
 			message: "#^Access to an undefined property Exception\\:\\:\\$queryString\\.$#"
 			count: 1
 			path: src/Database/Log/LoggingStatement.php
 
 		-
-			message: "#^Cannot unset offset 'args' on array\\('path' \\=\\> string, 'reference' \\=\\> mixed\\)\\.$#"
+			message: "#^PHPDoc type PDOStatement of property Cake\\\\Database\\\\Statement\\\\PDOStatement\\:\\:\\$_statement is not covariant with PHPDoc type Cake\\\\Database\\\\StatementInterface of overridden property Cake\\\\Database\\\\Statement\\\\StatementDecorator\\:\\:\\$_statement\\.$#"
+			count: 1
+			path: src/Database/Statement/PDOStatement.php
+
+		-
+			message: "#^Property PDOStatement\\:\\:\\$queryString \\(string\\) in isset\\(\\) is not nullable\\.$#"
+			count: 1
+			path: src/Database/Statement/PDOStatement.php
+
+		-
+			message: "#^Static property Cake\\\\Datasource\\\\ConnectionManager\\:\\:\\$_registry \\(Cake\\\\Datasource\\\\ConnectionRegistry\\) in isset\\(\\) is not nullable\\.$#"
+			count: 2
+			path: src/Datasource/ConnectionManager.php
+
+		-
+			message: "#^Cannot unset offset 'args' on array\\{path\\: string, reference\\: mixed\\}\\.$#"
 			count: 1
 			path: src/Error/Debugger.php
 
 		-
-			message: "#^Cannot unset offset 'object' on array\\('path' \\=\\> string, 'reference' \\=\\> mixed\\)\\.$#"
+			message: "#^Cannot unset offset 'object' on array\\{path\\: string, reference\\: mixed\\}\\.$#"
 			count: 1
 			path: src/Error/Debugger.php
 
@@ -261,12 +301,12 @@ parameters:
 			path: src/Http/Client.php
 
 		-
-			message: "#^Parameter \\$ch of method Cake\\\\Http\\\\Client\\\\Adapter\\\\Curl\\:\\:exec\\(\\) has invalid typehint type CurlHandle\\.$#"
+			message: "#^Parameter \\$ch of method Cake\\\\Http\\\\Client\\\\Adapter\\\\Curl\\:\\:exec\\(\\) has invalid type CurlHandle\\.$#"
 			count: 1
 			path: src/Http/Client/Adapter/Curl.php
 
 		-
-			message: "#^Parameter \\$handle of method Cake\\\\Http\\\\Client\\\\Adapter\\\\Curl\\:\\:createResponse\\(\\) has invalid typehint type CurlHandle\\.$#"
+			message: "#^Parameter \\$handle of method Cake\\\\Http\\\\Client\\\\Adapter\\\\Curl\\:\\:createResponse\\(\\) has invalid type CurlHandle\\.$#"
 			count: 1
 			path: src/Http/Client/Adapter/Curl.php
 
@@ -351,6 +391,11 @@ parameters:
 			path: src/I18n/Time.php
 
 		-
+			message: "#^Static property Cake\\\\Log\\\\Log\\:\\:\\$_registry \\(Cake\\\\Log\\\\LogEngineRegistry\\) in isset\\(\\) is not nullable\\.$#"
+			count: 3
+			path: src/Log/Log.php
+
+		-
 			message: "#^Unsafe usage of new static\\(\\)\\.$#"
 			count: 1
 			path: src/Mailer/Email.php
@@ -471,6 +516,11 @@ parameters:
 			path: src/TestSuite/Fixture/TestFixture.php
 
 		-
+			message: "#^Property Cake\\\\TestSuite\\\\Fixture\\\\TestFixture\\:\\:\\$_schema \\(Cake\\\\Database\\\\Schema\\\\SqlGeneratorInterface&Cake\\\\Database\\\\Schema\\\\TableSchemaInterface\\) in isset\\(\\) is not nullable\\.$#"
+			count: 2
+			path: src/TestSuite/Fixture/TestFixture.php
+
+		-
 			message: "#^Parameter \\#1 \\$response of class Cake\\\\TestSuite\\\\Constraint\\\\Response\\\\CookieEncryptedEquals constructor expects Cake\\\\Http\\\\Response\\|null, Psr\\\\Http\\\\Message\\\\ResponseInterface\\|null given\\.$#"
 			count: 1
 			path: src/TestSuite/IntegrationTestCase.php
@@ -496,6 +546,41 @@ parameters:
 			path: src/TestSuite/IntegrationTestCase.php
 
 		-
+			message: "#^Offset 0 does not exist on array\\<string, mixed\\>\\.$#"
+			count: 1
+			path: src/TestSuite/TestCase.php
+
+		-
+			message: "#^Offset 1 does not exist on array\\<string, mixed\\>\\.$#"
+			count: 1
+			path: src/TestSuite/TestCase.php
+
+		-
+			message: "#^Offset 2 does not exist on array\\<string, mixed\\>\\.$#"
+			count: 1
+			path: src/TestSuite/TestCase.php
+
+		-
+			message: "#^Unsafe access to private property Cake\\\\TestSuite\\\\TestEmailTransport\\:\\:\\$messages through static\\:\\:\\.$#"
+			count: 3
+			path: src/TestSuite/TestEmailTransport.php
+
+		-
+			message: "#^Dead catch \\- Error is never thrown in the try block\\.$#"
+			count: 1
+			path: src/View/Cell.php
+
+		-
+			message: "#^Property Cake\\\\View\\\\Cell\\:\\:\\$request \\(Cake\\\\Http\\\\ServerRequest\\) on left side of \\?\\? is not nullable\\.$#"
+			count: 1
+			path: src/View/Cell.php
+
+		-
+			message: "#^Property Cake\\\\View\\\\Cell\\:\\:\\$response \\(Cake\\\\Http\\\\Response\\) on left side of \\?\\? is not nullable\\.$#"
+			count: 1
+			path: src/View/Cell.php
+
+		-
 			message: "#^Unsafe usage of new static\\(\\)\\.$#"
 			count: 1
 			path: src/View/Form/ContextFactory.php

+ 0 - 5
psalm-baseline.xml

@@ -399,9 +399,4 @@
       <code>defaultCurrency</code>
     </DeprecatedMethod>
   </file>
-  <file src="src/View/View.php">
-    <ArgumentTypeCoercion occurrences="1">
-      <code>$options</code>
-    </ArgumentTypeCoercion>
-  </file>
 </files>

+ 1 - 1
src/Database/Expression/CaseExpression.php

@@ -24,7 +24,7 @@ use Closure;
 /**
  * This class represents a SQL Case statement
  *
- * @deprecated 4.3.0 Use CaseStatementExpression instead or Query::case()
+ * @deprecated 4.3.0 Use QueryExpression::case() or CaseStatementExpression instead
  */
 class CaseExpression implements ExpressionInterface
 {

+ 2 - 2
src/Database/Query.php

@@ -1300,7 +1300,7 @@ class Query implements ExpressionInterface, IteratorAggregate
      * Order fields are not suitable for use with user supplied data as they are
      * not sanitized by the query builder.
      *
-     * @param \Cake\Database\Expression\QueryExpression|\Closure|string $field The field to order on.
+     * @param \Cake\Database\ExpressionInterface|\Closure|string $field The field to order on.
      * @param bool $overwrite Whether to reset the order clauses.
      * @return $this
      */
@@ -1334,7 +1334,7 @@ class Query implements ExpressionInterface, IteratorAggregate
      * Order fields are not suitable for use with user supplied data as they are
      * not sanitized by the query builder.
      *
-     * @param \Cake\Database\Expression\QueryExpression|\Closure|string $field The field to order on.
+     * @param \Cake\Database\ExpressionInterface|\Closure|string $field The field to order on.
      * @param bool $overwrite Whether to reset the order clauses.
      * @return $this
      */

+ 2 - 1
src/Datasource/ConnectionManager.php

@@ -195,7 +195,8 @@ class ConnectionManager
         if (empty(static::$_config[$name])) {
             throw new MissingDatasourceConfigException(['name' => $name]);
         }
-        if (empty(static::$_registry)) {
+        /** @psalm-suppress RedundantPropertyInitializationCheck */
+        if (!isset(static::$_registry)) {
             static::$_registry = new ConnectionRegistry();
         }
 

+ 1 - 4
src/Error/ExceptionRenderer.php

@@ -396,10 +396,7 @@ class ExceptionRenderer implements ExceptionRendererInterface
             $attributes = $e->getAttributes();
             if (
                 $e instanceof MissingLayoutException ||
-                (
-                    isset($attributes['file']) &&
-                    strpos($attributes['file'], 'error500') !== false
-                )
+                strpos($attributes['file'], 'error500') !== false
             ) {
                 return $this->_outputMessageSafe('error500');
             }

+ 0 - 5
src/Http/Client/Adapter/Stream.php

@@ -168,11 +168,6 @@ class Stream implements AdapterInterface
     protected function _buildContent(RequestInterface $request, array $options): void
     {
         $body = $request->getBody();
-        if (empty($body)) {
-            $this->_contextOptions['content'] = '';
-
-            return;
-        }
         $body->rewind();
         $this->_contextOptions['content'] = $body->getContents();
     }

+ 4 - 2
src/Log/Log.php

@@ -176,7 +176,8 @@ class Log
      */
     protected static function _init(): void
     {
-        if (empty(static::$_registry)) {
+        /** @psalm-suppress RedundantPropertyInitializationCheck */
+        if (!isset(static::$_registry)) {
             static::$_registry = new LogEngineRegistry();
         }
         if (static::$_dirtyConfig) {
@@ -215,7 +216,8 @@ class Log
      */
     public static function reset(): void
     {
-        if (!empty(static::$_registry)) {
+        /** @psalm-suppress RedundantPropertyInitializationCheck */
+        if (isset(static::$_registry)) {
             static::$_registry->reset();
         }
         static::$_config = [];

+ 4 - 2
src/TestSuite/Fixture/TestFixture.php

@@ -267,7 +267,8 @@ class TestFixture implements ConstraintsInterface, FixtureInterface, TableSchema
      */
     public function create(ConnectionInterface $connection): bool
     {
-        if (empty($this->_schema)) {
+        /** @psalm-suppress RedundantPropertyInitializationCheck */
+        if (!isset($this->_schema)) {
             return false;
         }
 
@@ -303,7 +304,8 @@ class TestFixture implements ConstraintsInterface, FixtureInterface, TableSchema
      */
     public function drop(ConnectionInterface $connection): bool
     {
-        if (empty($this->_schema)) {
+        /** @psalm-suppress RedundantPropertyInitializationCheck */
+        if (!isset($this->_schema)) {
             return false;
         }
 

+ 14 - 9
src/View/View.php

@@ -636,12 +636,17 @@ class View implements EventDispatcherInterface
      * @return string Rendered Element
      * @throws \Cake\View\Exception\MissingElementException When an element is missing and `ignoreMissing`
      *   is false.
+     * @psalm-param array{cache?:array|true, callbacks?:bool, plugin?:string|false, ignoreMissing?:bool} $options
      */
     public function element(string $name, array $data = [], array $options = []): string
     {
-        $options += ['callbacks' => false, 'cache' => null, 'plugin' => null];
+        $options += ['callbacks' => false, 'cache' => null, 'plugin' => null, 'ignoreMissing' => false];
         if (isset($options['cache'])) {
-            $options['cache'] = $this->_elementCache($name, $data, $options);
+            $options['cache'] = $this->_elementCache(
+                $name,
+                $data,
+                array_diff_key($options, ['callbacks' => false, 'plugin' => null, 'ignoreMissing' => null])
+            );
         }
 
         $pluginCheck = $options['plugin'] !== false;
@@ -655,13 +660,13 @@ class View implements EventDispatcherInterface
             return $this->_renderElement($file, $data, $options);
         }
 
-        if (empty($options['ignoreMissing'])) {
-            [$plugin, $elementName] = $this->pluginSplit($name, $pluginCheck);
-            $paths = iterator_to_array($this->getElementPaths($plugin));
-            throw new MissingElementException([$name . $this->_ext, $elementName . $this->_ext], $paths);
+        if ($options['ignoreMissing']) {
+            return '';
         }
 
-        return '';
+        [$plugin, $elementName] = $this->pluginSplit($name, $pluginCheck);
+        $paths = iterator_to_array($this->getElementPaths($plugin));
+        throw new MissingElementException([$name . $this->_ext, $elementName . $this->_ext], $paths);
     }
 
     /**
@@ -1605,12 +1610,12 @@ class View implements EventDispatcherInterface
      * @param array $data Data
      * @param array<string, mixed> $options Element options
      * @return array Element Cache configuration.
-     * @psalm-param array{cache:(array{key:string, config:string}|string|null), callbacks:mixed, plugin:mixed} $options
      * @psalm-return array{key:string, config:string}
      */
     protected function _elementCache(string $name, array $data, array $options): array
     {
         if (isset($options['cache']['key'], $options['cache']['config'])) {
+            /** @psalm-var array{key:string, config:string}*/
             $cache = $options['cache'];
             $cache['key'] = 'element_' . $cache['key'];
 
@@ -1626,7 +1631,7 @@ class View implements EventDispatcherInterface
         $elementKey = str_replace(['\\', '/'], '_', $name);
 
         $cache = $options['cache'];
-        unset($options['cache'], $options['callbacks'], $options['plugin']);
+        unset($options['cache']);
         $keys = array_merge(
             [$pluginKey, $elementKey],
             array_keys($options),

+ 54 - 41
tests/TestCase/Database/QueryTest.php

@@ -1874,18 +1874,16 @@ class QueryTest extends TestCase
         $this->assertEquals($expected, $result);
 
         $query = new Query($this->connection);
-        $this->deprecated(function () use ($query) {
-            $query->select(['id'])
-                ->from('articles')
-                ->orderAsc(function (QueryExpression $exp, Query $query) {
-                    return $exp->addCase(
-                        [$query->newExpr()->add(['author_id' => 1])],
-                        [1, $query->identifier('id')],
-                        ['integer', null]
-                    );
-                })
-                ->orderAsc('id');
-        });
+        $query->select(['id'])
+            ->from('articles')
+            ->orderAsc(function (QueryExpression $exp, Query $query) {
+                return $exp
+                    ->case()
+                    ->when(['author_id' => 1])
+                    ->then(1)
+                    ->else($query->identifier('id'));
+            })
+            ->orderAsc('id');
         $sql = $query->sql();
         $result = $query->execute()->fetchAll('assoc');
         $expected = [
@@ -1895,7 +1893,7 @@ class QueryTest extends TestCase
         ];
         $this->assertEquals($expected, $result);
         $this->assertQuotedQuery(
-            'SELECT <id> FROM <articles> ORDER BY CASE WHEN <author_id> = :c0 THEN :param1 ELSE <id> END ASC, <id> ASC',
+            'SELECT <id> FROM <articles> ORDER BY CASE WHEN <author_id> = :c0 THEN :c1 ELSE <id> END ASC, <id> ASC',
             $sql,
             !$this->autoQuote
         );
@@ -1938,18 +1936,16 @@ class QueryTest extends TestCase
         $this->assertEquals($expected, $result);
 
         $query = new Query($this->connection);
-        $this->deprecated(function () use ($query) {
-            $query->select(['id'])
-                ->from('articles')
-                ->orderDesc(function (QueryExpression $exp, Query $query) {
-                    return $exp->addCase(
-                        [$query->newExpr()->add(['author_id' => 1])],
-                        [1, $query->identifier('id')],
-                        ['integer', null]
-                    );
-                })
-                ->orderDesc('id');
-        });
+        $query->select(['id'])
+            ->from('articles')
+            ->orderDesc(function (QueryExpression $exp, Query $query) {
+                return $exp
+                    ->case()
+                    ->when(['author_id' => 1])
+                    ->then(1)
+                    ->else($query->identifier('id'));
+            })
+            ->orderDesc('id');
         $sql = $query->sql();
         $result = $query->execute()->fetchAll('assoc');
         $expected = [
@@ -1959,7 +1955,7 @@ class QueryTest extends TestCase
         ];
         $this->assertEquals($expected, $result);
         $this->assertQuotedQuery(
-            'SELECT <id> FROM <articles> ORDER BY CASE WHEN <author_id> = :c0 THEN :param1 ELSE <id> END DESC, <id> DESC',
+            'SELECT <id> FROM <articles> ORDER BY CASE WHEN <author_id> = :c0 THEN :c1 ELSE <id> END DESC, <id> DESC',
             $sql,
             !$this->autoQuote
         );
@@ -4120,6 +4116,8 @@ class QueryTest extends TestCase
 
     /**
      * Tests that case statements work correctly for various use-cases.
+     *
+     * @deprecated
      */
     public function testSqlCaseStatement(): void
     {
@@ -4208,6 +4206,27 @@ class QueryTest extends TestCase
         $this->assertSame('Published', $results[2]['status']);
         $this->assertSame('Not published', $results[3]['status']);
         $this->assertSame('None', $results[6]['status']);
+
+        $query = new Query($this->connection);
+        $this->deprecated(function () use ($query) {
+            $query->select(['id'])
+                ->from('articles')
+                ->orderAsc(function (QueryExpression $exp, Query $query) {
+                    return $exp->addCase(
+                        [$query->newExpr()->add(['author_id' => 1])],
+                        [1, $query->identifier('id')],
+                        ['integer', null]
+                    );
+                })
+                ->orderAsc('id');
+        });
+        $result = $query->execute()->fetchAll('assoc');
+        $expected = [
+            ['id' => 1],
+            ['id' => 3],
+            ['id' => 2],
+        ];
+        $this->assertEquals($expected, $result);
     }
 
     /**
@@ -5102,20 +5121,14 @@ class QueryTest extends TestCase
         $stmt->closeCursor();
 
         $subquery = new Query($connection);
-        $this->deprecated(function () use ($subquery) {
-            $subquery
-                ->select(
-                    $subquery->newExpr()->addCase(
-                        [$subquery->newExpr()->add(['a.published' => 'N'])],
-                        [1, 0],
-                        ['integer', 'integer']
-                    )
-                )
-                ->from(['a' => 'articles'])
-                ->where([
-                    'a.id = articles.id',
-                ]);
-        });
+        $subquery
+            ->select(
+                $subquery->newExpr()->case()->when(['a.published' => 'N'])->then(1)->else(0)
+            )
+            ->from(['a' => 'articles'])
+            ->where([
+                'a.id = articles.id',
+            ]);
 
         $query
             ->select(['id'])
@@ -5128,7 +5141,7 @@ class QueryTest extends TestCase
 
         $this->assertQuotedQuery(
             'SELECT <id> FROM <articles> ORDER BY \(' .
-                'SELECT \(CASE WHEN <a>\.<published> = \:c0 THEN \:param1 ELSE \:param2 END\) ' .
+                'SELECT \(CASE WHEN <a>\.<published> = \:c0 THEN \:c1 ELSE \:c2 END\) ' .
                 'FROM <articles> <a> ' .
                 'WHERE a\.id = articles\.id' .
             '\) DESC, <id> ASC',

+ 6 - 12
tests/TestCase/ORM/QueryRegressionTest.php

@@ -1482,12 +1482,9 @@ class QueryRegressionTest extends TestCase
     {
         $table = $this->getTableLocator()->get('Articles');
         $query = $table->find();
-        $this->deprecated(function () use ($query) {
-            $query->orderDesc($query->newExpr()->addCase(
-                [$query->newExpr()->add(['id' => 3])],
-                [1, 0]
-            ));
-        });
+        $query->orderDesc(
+            $query->newExpr()->case()->when(['id' => 3])->then(1)->else(0)
+        );
         $query->order(['title' => 'desc']);
         // Executing the normal query before getting the count
         $query->all();
@@ -1495,12 +1492,9 @@ class QueryRegressionTest extends TestCase
 
         $table = $this->getTableLocator()->get('Articles');
         $query = $table->find();
-        $this->deprecated(function () use ($query) {
-            $query->orderDesc($query->newExpr()->addCase(
-                [$query->newExpr()->add(['id' => 3])],
-                [1, 0]
-            ));
-        });
+        $query->orderDesc(
+            $query->newExpr()->case()->when(['id' => 3])->then(1)->else(0)
+        );
         $query->orderDesc($query->newExpr()->add(['id' => 3]));
         // Not executing the query first, just getting the count
         $this->assertSame(3, $query->count());