浏览代码

Merge branch 'master' into 4.next

Mark Story 6 年之前
父节点
当前提交
826a4356af

+ 1 - 1
composer.json

@@ -110,7 +110,7 @@
             "@phpstan",
             "@psalm"
         ],
-        "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^0.12 psalm/phar:~3.8.0 && mv composer.backup composer.json"
+        "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^0.12.7 psalm/phar:~3.8.0 && mv composer.backup composer.json"
     },
     "config": {
         "sort-packages": true,

+ 14 - 14
phpstan-baseline.neon

@@ -111,11 +111,6 @@ parameters:
 			path: src/ORM/ResultSet.php
 
 		-
-			message: "#^Method Cake\\\\ORM\\\\ResultSet\\:\\:first\\(\\) should return array\\|object but return statement is missing\\.$#"
-			count: 1
-			path: src/ORM/ResultSet.php
-
-		-
 			message: "#^Call to an undefined method Traversable\\:\\:getArrayCopy\\(\\)\\.$#"
 			count: 1
 			path: src/Collection/Iterator/ExtractIterator.php
@@ -521,19 +516,24 @@ parameters:
 			path: src/I18n/Time.php
 
 		-
+			message: "#^Access to undefined constant NumberFormatter\\:\\:CURRENCY_ACCOUNTING\\.$#"
+			count: 1
+			path: src/I18n/Number.php
+
+		-
 			message: "#^PHPDoc tag @var has invalid value \\(\\\\Psr\\\\SimpleCache\\\\CacheInterface&\\\\Cake\\\\Cache\\\\CacheEngineInterface\\|null\\)\\: Unexpected token \"\\|\", expected TOKEN_OTHER at offset 175$#"
 			count: 1
 			path: src/I18n/TranslatorRegistry.php
 
 		-
-			message: "#^Method Cake\\\\Mailer\\\\Email\\:\\:deliver\\(\\) should return static\\(Cake\\\\Mailer\\\\Email\\) but returns Cake\\\\Mailer\\\\Email\\.$#"
+			message: "#^Property Cake\\\\I18n\\\\TranslatorRegistry\\:\\:\\$_cacher has no typehint specified\\.$#"
 			count: 1
-			path: src/Mailer/Email.php
+			path: src/I18n/TranslatorRegistry.php
 
 		-
-			message: "#^Call to sprintf contains 0 placeholders, 1 value given\\.$#"
+			message: "#^Method Cake\\\\Mailer\\\\Email\\:\\:deliver\\(\\) should return static\\(Cake\\\\Mailer\\\\Email\\) but returns Cake\\\\Mailer\\\\Email\\.$#"
 			count: 1
-			path: src/Mailer/Message.php
+			path: src/Mailer/Email.php
 
 		-
 			message: "#^Method Cake\\\\Mailer\\\\Renderer\\:\\:render\\(\\) should return array\\(\\)\\|array\\('html' \\=\\> string, 'text' \\=\\> string\\) but returns array\\<string, string\\>\\.$#"
@@ -731,6 +731,11 @@ parameters:
 			path: src/TestSuite/IntegrationTestCase.php
 
 		-
+			message: "#^Unable to resolve the template type RealInstanceType in call to method PHPUnit\\\\Framework\\\\TestCase\\:\\:getMockBuilder\\(\\)$#"
+			count: 1
+			path: src/TestSuite/TestCase.php
+
+		-
 			message: "#^Result of \\|\\| is always true\\.$#"
 			count: 2
 			path: src/Utility/Hash.php
@@ -751,11 +756,6 @@ parameters:
 			path: src/View/Form/NullContext.php
 
 		-
-			message: "#^Method Cake\\\\View\\\\Helper\\:\\:__get\\(\\) should return Cake\\\\View\\\\Helper\\|null but return statement is missing\\.$#"
-			count: 1
-			path: src/View/Helper.php
-
-		-
 			message: "#^Offset 'debug' does not exist on array\\('fields' \\=\\> string, 'unlocked' \\=\\> string\\)\\.$#"
 			count: 1
 			path: src/View/Helper/FormHelper.php

+ 3 - 3
phpstan.neon

@@ -2,11 +2,11 @@ includes:
     - phpstan-baseline.neon
 
 parameters:
-    level: 5
+    level: 6
+    checkMissingIterableValueType: false
+    checkGenericClassInNonGenericObjectType: false
     autoload_files:
         - tests/bootstrap.php
-    ignoreErrors:
-        - '#Access to undefined constant NumberFormatter::CURRENCY_ACCOUNTING#'
     earlyTerminatingMethodCalls:
         Cake\Console\Shell:
             - abort

+ 1 - 0
src/Cache/Cache.php

@@ -72,6 +72,7 @@ class Cache
      * class names.
      *
      * @var string[]
+     * @psalm-var array<string, class-string>
      */
     protected static $_dsnClassMap = [
         'array' => Engine\ArrayEngine::class,

+ 2 - 0
src/Core/StaticConfigTrait.php

@@ -304,6 +304,7 @@ REGEXP;
      *
      * @param string[] $map Additions/edits to the class map to apply.
      * @return void
+     * @psalm-param array<string, class-string> $map
      */
     public static function setDsnClassMap(array $map): void
     {
@@ -314,6 +315,7 @@ REGEXP;
      * Returns the DSN class map for this class.
      *
      * @return string[]
+     * @psalm-return array<string, class-string>
      */
     public static function getDsnClassMap(): array
     {

+ 12 - 0
src/Database/Driver/Postgres.php

@@ -18,6 +18,8 @@ namespace Cake\Database\Driver;
 
 use Cake\Database\Driver;
 use Cake\Database\Expression\FunctionExpression;
+use Cake\Database\PostgresCompiler;
+use Cake\Database\QueryCompiler;
 use Cake\Database\Query;
 use Cake\Database\Schema\PostgresSchemaDialect;
 use Cake\Database\Schema\SchemaDialect;
@@ -285,4 +287,14 @@ class Postgres extends Driver
                 break;
         }
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return \Cake\Database\PostgresCompiler
+     */
+    public function newCompiler(): QueryCompiler
+    {
+        return new PostgresCompiler();
+    }
 }

+ 35 - 0
src/Database/PostgresCompiler.php

@@ -0,0 +1,35 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         4.0.3
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Database;
+
+/**
+ * Responsible for compiling a Query object into its SQL representation
+ * for Postgres
+ *
+ * @internal
+ */
+class PostgresCompiler extends QueryCompiler
+{
+    /**
+     * Always quote aliases in SELECT clause.
+     *
+     * Postgres auto converts unquoted identifiers to lower case.
+     *
+     * @var bool
+     */
+    protected $_quotedSelectAliases = true;
+}

+ 15 - 2
src/Database/QueryCompiler.php

@@ -86,6 +86,13 @@ class QueryCompiler
     protected $_orderedUnion = true;
 
     /**
+     * Indicate whether aliases in SELECT clause need to be always quoted.
+     *
+     * @var bool
+     */
+    protected $_quotedSelectAliases = false;
+
+    /**
      * Returns the SQL representation of the provided query after generating
      * the placeholders for the bound values using the provided generator
      *
@@ -163,7 +170,6 @@ class QueryCompiler
      */
     protected function _buildSelectPart(array $parts, Query $query, ValueBinder $generator): string
     {
-        $driver = $query->getConnection()->getDriver();
         $select = 'SELECT%s %s%s';
         if ($this->_orderedUnion && $query->clause('union')) {
             $select = '(SELECT%s %s%s';
@@ -171,11 +177,18 @@ class QueryCompiler
         $distinct = $query->clause('distinct');
         $modifiers = $this->_buildModifierPart($query->clause('modifier'), $query, $generator);
 
+        $driver = $query->getConnection()->getDriver();
+        $quoteIdentifiers = $driver->isAutoQuotingEnabled() || $this->_quotedSelectAliases;
         $normalized = [];
         $parts = $this->_stringifyExpressions($parts, $generator);
         foreach ($parts as $k => $p) {
             if (!is_numeric($k)) {
-                $p = $p . ' AS ' . $driver->quoteIdentifier($k);
+                $p = $p . ' AS ';
+                if ($quoteIdentifiers) {
+                    $p .= $driver->quoteIdentifier($k);
+                } else {
+                    $p .= $k;
+                }
             }
             $normalized[] = $p;
         }

+ 2 - 1
src/Datasource/ConnectionManager.php

@@ -50,7 +50,8 @@ class ConnectionManager
     /**
      * An array mapping url schemes to fully qualified driver class names
      *
-     * @return string[]
+     * @var string[]
+     * @psalm-var array<string, class-string>
      */
     protected static $_dsnClassMap = [
         'mysql' => Mysql::class,

+ 1 - 1
src/Datasource/EntityTrait.php

@@ -200,7 +200,7 @@ trait EntityTrait
      * the guarding for a single set call with the `guard` option:
      *
      * ```
-     * $entity->set(['name' => 'Andrew', 'id' => 1], ['guard' => true]);
+     * $entity->set(['name' => 'Andrew', 'id' => 1], ['guard' => false]);
      * ```
      *
      * You do not need to use the guard option when assigning fields individually:

+ 1 - 0
src/Log/Log.php

@@ -114,6 +114,7 @@ class Log
      * An array mapping url schemes to fully qualified Log engine class names
      *
      * @var string[]
+     * @psalm-var array<string, class-string>
      */
     protected static $_dsnClassMap = [
         'console' => Engine\ConsoleLog::class,

+ 1 - 0
src/Mailer/Mailer.php

@@ -182,6 +182,7 @@ class Mailer implements EventListenerInterface
      * Mailer driver class map.
      *
      * @var array
+     * @psalm-var array<string, class-string>
      */
     protected static $_dsnClassMap = [];
 

+ 1 - 1
src/Mailer/Message.php

@@ -1496,7 +1496,7 @@ class Message implements JsonSerializable, Serializable
         foreach ($content as $type => $text) {
             if (!in_array($type, $this->emailFormatAvailable, true)) {
                 throw new InvalidArgumentException(sprintf(
-                    'Invalid message type: "$s". Valid types are: "text", "html".',
+                    'Invalid message type: "%s". Valid types are: "text", "html".',
                     $type
                 ));
             }

+ 1 - 0
src/Mailer/TransportFactory.php

@@ -37,6 +37,7 @@ class TransportFactory
      * An array mapping url schemes to fully qualified Transport class names
      *
      * @var string[]
+     * @psalm-var array<string, class-string>
      */
     protected static $_dsnClassMap = [
         'debug' => Transport\DebugTransport::class,

+ 3 - 2
src/ORM/ResultSet.php

@@ -276,8 +276,7 @@ class ResultSet implements ResultSetInterface
      *
      * This method will also close the underlying statement cursor.
      *
-     * @return array|object
-     * @psalm-suppress InvalidNullableReturnType
+     * @return array|object|null
      */
     public function first()
     {
@@ -288,6 +287,8 @@ class ResultSet implements ResultSetInterface
 
             return $result;
         }
+
+        return null;
     }
 
     /**

+ 1 - 1
src/View/Helper.php

@@ -106,7 +106,7 @@ class Helper implements EventListenerInterface
      * Lazy loads helpers.
      *
      * @param string $name Name of the property being accessed.
-     * @return \Cake\View\Helper|null Helper instance if helper with provided name exists
+     * @return \Cake\View\Helper|null|void Helper instance if helper with provided name exists
      */
     public function __get(string $name)
     {

+ 1 - 1
src/View/View.php

@@ -794,7 +794,7 @@ class View implements EventDispatcherInterface
 
         $title = $this->Blocks->get('title');
         if ($title === '') {
-            $title = Inflector::humanize((string)$this->templatePath);
+            $title = Inflector::humanize(str_replace(DIRECTORY_SEPARATOR, '/', (string)$this->templatePath));
             $this->Blocks->set('title', $title);
         }
 

+ 7 - 3
tests/TestCase/Database/Driver/SqlserverTest.php

@@ -330,7 +330,11 @@ class SqlserverTest extends TestCase
         $query->select(['id', 'title'])
             ->from('articles')
             ->offset(10);
-        $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS [_cake_page_rownum_] ' .
+        $identifier = '_cake_page_rownum_';
+        if ($connection->getDriver()->isAutoQuotingEnabled()) {
+            $identifier = $connection->getDriver()->quoteIdentifier($identifier);
+        }
+        $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS ' . $identifier . ' ' .
             'FROM articles) _cake_paging_ ' .
             'WHERE _cake_paging_._cake_page_rownum_ > 10';
         $this->assertEquals($expected, $query->sql());
@@ -340,7 +344,7 @@ class SqlserverTest extends TestCase
             ->from('articles')
             ->order(['id'])
             ->offset(10);
-        $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS [_cake_page_rownum_] ' .
+        $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS ' . $identifier . ' ' .
             'FROM articles) _cake_paging_ ' .
             'WHERE _cake_paging_._cake_page_rownum_ > 10';
         $this->assertEquals($expected, $query->sql());
@@ -352,7 +356,7 @@ class SqlserverTest extends TestCase
             ->where(['title' => 'Something'])
             ->limit(10)
             ->offset(50);
-        $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS [_cake_page_rownum_] ' .
+        $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS ' . $identifier . ' ' .
             'FROM articles WHERE title = :c0) _cake_paging_ ' .
             'WHERE (_cake_paging_._cake_page_rownum_ > 50 AND _cake_paging_._cake_page_rownum_ <= 60)';
         $this->assertEquals($expected, $query->sql());

+ 2 - 2
tests/TestCase/ORM/QueryTest.php

@@ -2794,7 +2794,7 @@ class QueryTest extends TestCase
             ->enableHydration(false)
             ->contain([
                 'Authors' => function ($q) {
-                    return $q->select(['compute' => '(SELECT 2 + 20)'])
+                    return $q->select(['computed' => '(SELECT 2 + 20)'])
                         ->enableAutoFields();
                 },
             ])
@@ -2805,7 +2805,7 @@ class QueryTest extends TestCase
         $this->assertArrayHasKey('author', $result);
         $this->assertNotNull($result['author']);
         $this->assertArrayHasKey('name', $result['author']);
-        $this->assertArrayHasKey('compute', $result);
+        $this->assertArrayHasKey('computed', $result);
     }
 
     /**