From c2b402e28170d2e331293b561bf56ecacf708b28 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 7 Apr 2025 17:17:28 +0530 Subject: [PATCH 01/17] Fix method and finder map cleanup when unloading a behavior. Closes #18311 --- src/ORM/BehaviorRegistry.php | 4 ++-- tests/TestCase/ORM/BehaviorRegistryTest.php | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ORM/BehaviorRegistry.php b/src/ORM/BehaviorRegistry.php index 82db5cffec2..cca7bbcf24f 100644 --- a/src/ORM/BehaviorRegistry.php +++ b/src/ORM/BehaviorRegistry.php @@ -242,11 +242,11 @@ public function unload(string $name) $instance = $this->get($name); $result = parent::unload($name); - $methods = $instance->implementedMethods(); + $methods = array_map('strtolower', array_keys($instance->implementedMethods())); foreach ($methods as $method) { unset($this->_methodMap[$method]); } - $finders = $instance->implementedFinders(); + $finders = array_map('strtolower', array_keys($instance->implementedFinders())); foreach ($finders as $finder) { unset($this->_finderMap[$finder]); } diff --git a/tests/TestCase/ORM/BehaviorRegistryTest.php b/tests/TestCase/ORM/BehaviorRegistryTest.php index f977c6aa842..e82d9791573 100644 --- a/tests/TestCase/ORM/BehaviorRegistryTest.php +++ b/tests/TestCase/ORM/BehaviorRegistryTest.php @@ -303,8 +303,13 @@ public function testCallFinder(): void { $this->Behaviors->load('Sluggable'); $mockedBehavior = Mockery::mock(Behavior::class) - ->shouldAllowMockingMethod('findNoSlug') + ->shouldAllowMockingMethod('findNoSlug', 'implementedFinders') ->makePartial(); + $mockedBehavior->shouldReceive('implementedFinders') + ->once() + ->andReturn([ + 'noslug' => 'findNoSlug', + ]); $this->Behaviors->set('Sluggable', $mockedBehavior); $query = new SelectQuery($this->Table); @@ -379,10 +384,20 @@ public function testUnloadBehaviorThenReload(): void public function testUnload(): void { $this->Behaviors->load('Sluggable'); + $this->assertTrue($this->Behaviors->hasFinder('noSlug')); + + $this->Behaviors->load('Validation'); + $this->assertTrue($this->Behaviors->hasMethod('customValidationRule')); + + $this->Behaviors->unload('Validation'); $this->Behaviors->unload('Sluggable'); $this->assertEmpty($this->Behaviors->loaded()); $this->assertCount(0, $this->EventManager->listeners('Model.beforeFind')); + $this->assertFalse($this->Behaviors->hasFinder('noSlug')); + $this->assertFalse($this->Behaviors->hasFinder('noslug')); + $this->assertFalse($this->Behaviors->hasMethod('customValidationRule')); + $this->assertFalse($this->Behaviors->hasMethod('customvalidationrule')); } /** From e70645cd1b4fe633f818c571922167aa4497d04a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 7 Apr 2025 15:42:35 -0400 Subject: [PATCH 02/17] Update release tooling to clean up branches (#18310) Don't leave branches & remotes around for the .next branches that are updated at the end of a release. --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 2abbe90dd87..b668dd31ebc 100644 --- a/Makefile +++ b/Makefile @@ -174,6 +174,7 @@ components-next: exit 0; \ fi; make CURRENT_BRANCH=$(NEXT_BRANCH) components + make clean-components-branches .PHONY: components-next component-%: @@ -190,6 +191,13 @@ tag-component-%: component-% guard-VERSION guard-GITHUB_TOKEN git checkout $* curl $(AUTH) -XPOST $(API_HOST)/repos/$(OWNER)/$*/git/refs -d '{"ref": "refs\/tags\/$(VERSION)", "sha": "$(shell git rev-parse $*)"}' git checkout $(CURRENT_BRANCH) > /dev/null + make clean-component-branch-$* + +# Task for cleaning up branches and remotes after updating split packages +clean-components-branches: $(foreach component, $(COMPONENTS), clean-component-branch-$(component)) +.PHONY: clean-component-branches + +clean-component-branch-%: git branch -D $* git remote rm $* @@ -199,6 +207,7 @@ clean-component-%: - (git remote add $* git@github.com:$(OWNER)/$*.git -f 2> /dev/null) - (git branch -D $* 2> /dev/null) - git push -f $* :$(CURRENT_BRANCH) +.PHONY: components-clean # Top level alias for doing a release. release: guard-VERSION tag-release components-tag package publish components-next From c4472bb20651e683a79ae388034dadf9178ef979 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 8 Apr 2025 08:22:32 +0530 Subject: [PATCH 03/17] Add class_exists()/interface_exists() checks. (#18309) These classes/interface are not available when using the standalone validation package. --- src/Validation/Validation.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Validation/Validation.php b/src/Validation/Validation.php index 7333ef49e5d..cf22446e04d 100644 --- a/src/Validation/Validation.php +++ b/src/Validation/Validation.php @@ -451,7 +451,10 @@ public static function custom(mixed $check, ?string $regex = null): bool */ public static function date(mixed $check, array|string $format = 'ymd', ?string $regex = null): bool { - if ($check instanceof ChronosDate || $check instanceof DateTimeInterface) { + if ( + (class_exists(ChronosDate::class) && $check instanceof ChronosDate) + || $check instanceof DateTimeInterface + ) { return true; } if (is_object($check)) { @@ -616,7 +619,10 @@ public static function iso8601(mixed $check): bool */ public static function time(mixed $check): bool { - if ($check instanceof ChronosTime || $check instanceof DateTimeInterface) { + if ( + (class_exists(ChronosTime::class) && $check instanceof ChronosTime) + || $check instanceof DateTimeInterface + ) { return true; } if (is_array($check)) { @@ -648,7 +654,16 @@ public static function time(mixed $check): bool */ public static function localizedTime(mixed $check, string $type = 'datetime', string|int|null $format = null): bool { - if ($check instanceof ChronosTime || $check instanceof DateTimeInterface) { + if (!class_exists(DateTime::class)) { + throw new CakeException( + 'The Cake\I18n\DateTime class is not available. Install the cakephp/i18n package.', + ); + } + + if ( + (class_exists(ChronosTime::class) && $check instanceof ChronosTime) + || $check instanceof DateTimeInterface + ) { return true; } if (!is_string($check)) { @@ -990,7 +1005,7 @@ public static function equalTo(mixed $check, mixed $comparedTo): bool */ public static function extension(mixed $check, array $extensions = ['gif', 'jpeg', 'png', 'jpg']): bool { - if ($check instanceof UploadedFileInterface) { + if (interface_exists(UploadedFileInterface::class) && $check instanceof UploadedFileInterface) { $check = $check->getClientFilename(); } elseif (is_array($check) && isset($check['name'])) { $check = $check['name']; From c01a5f4b7716af3168c9674bb20ee99e4b399f3e Mon Sep 17 00:00:00 2001 From: Fiona Wille Date: Wed, 9 Apr 2025 11:55:15 +0200 Subject: [PATCH 04/17] Merge pull request #18315 from fwille/uppercase-get-method Use upper case HTTP GET method --- src/Http/Client/Adapter/Curl.php | 2 +- tests/TestCase/Http/Client/Adapter/CurlTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Client/Adapter/Curl.php b/src/Http/Client/Adapter/Curl.php index 0ad82575a38..d32687ece42 100644 --- a/src/Http/Client/Adapter/Curl.php +++ b/src/Http/Client/Adapter/Curl.php @@ -119,7 +119,7 @@ public function buildOptions(RequestInterface $request, array $options): array $out[CURLOPT_POSTFIELDS] = $body->getContents(); // GET requests with bodies require custom request to be used. if ($out[CURLOPT_POSTFIELDS] !== '' && isset($out[CURLOPT_HTTPGET])) { - $out[CURLOPT_CUSTOMREQUEST] = 'get'; + $out[CURLOPT_CUSTOMREQUEST] = 'GET'; } if ($out[CURLOPT_POSTFIELDS] === '') { unset($out[CURLOPT_POSTFIELDS]); diff --git a/tests/TestCase/Http/Client/Adapter/CurlTest.php b/tests/TestCase/Http/Client/Adapter/CurlTest.php index 15abf5bb2e3..8341877bb38 100644 --- a/tests/TestCase/Http/Client/Adapter/CurlTest.php +++ b/tests/TestCase/Http/Client/Adapter/CurlTest.php @@ -149,7 +149,7 @@ public function testBuildOptionsGetWithBody(): void ], CURLOPT_HTTPGET => true, CURLOPT_POSTFIELDS => '{"some":"body"}', - CURLOPT_CUSTOMREQUEST => 'get', + CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_TIMEOUT => 5, CURLOPT_CAINFO => $this->caFile, ]; From 8e96e9cef60b7d4e44344bd5729fb8f2ccb22e08 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Thu, 10 Apr 2025 05:11:16 +0200 Subject: [PATCH 05/17] Revert "Allow null for Number helper methods that are commonly used for bake." (#18317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Allow null for Number helper methods that are commonly used for bake.…" This reverts commit 90b9411ba2a9db713a8f3974a250d2fbd1d246a3. --- src/View/Helper/NumberHelper.php | 22 +++------- .../TestCase/View/Helper/NumberHelperTest.php | 40 ------------------- 2 files changed, 6 insertions(+), 56 deletions(-) diff --git a/src/View/Helper/NumberHelper.php b/src/View/Helper/NumberHelper.php index a46c4c805e0..081fa1c6824 100644 --- a/src/View/Helper/NumberHelper.php +++ b/src/View/Helper/NumberHelper.php @@ -56,20 +56,15 @@ public function __call(string $method, array $params): mixed * - `locale` - The locale name to use for formatting the number, e.g. fr_FR * - `before` - The string to place before whole numbers, e.g. '[' * - `after` - The string to place after decimal numbers, e.g. ']' - * - `escape` - Whether to escape HTML in resulting string - * - `default` - The default value in case passed value is null + * - `escape` - Whether to escape html in resulting string * - * @param string|float|int|null $number A floating point number. + * @param string|float|int $number A floating point number. * @param array $options An array with options. * @return string Formatted number * @link https://book.cakephp.org/5/en/views/helpers/number.html#formatting-numbers */ - public function format(string|float|int|null $number, array $options = []): string + public function format(string|float|int $number, array $options = []): string { - if ($number === null) { - return $options['default'] ?? ''; - } - $formatted = Number::format($number, $options); $options += ['escape' => true]; @@ -95,20 +90,15 @@ public function format(string|float|int|null $number, array $options = []): stri * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,##0.00 * - `useIntlCode` - Whether to replace the currency symbol with the international * currency code. - * - `escape` - Whether to escape HTML in resulting string - * - `default` - The default value in case passed value is null + * - `escape` - Whether to escape html in resulting string * - * @param string|float|null $number Value to format. + * @param string|float $number Value to format. * @param string|null $currency International currency name such as 'USD', 'EUR', 'JPY', 'CAD' * @param array $options Options list. * @return string Number formatted as a currency. */ - public function currency(string|float|null $number, ?string $currency = null, array $options = []): string + public function currency(string|float $number, ?string $currency = null, array $options = []): string { - if ($number === null) { - return $options['default'] ?? ''; - } - $formatted = Number::currency($number, $currency, $options); $options += ['escape' => true]; diff --git a/tests/TestCase/View/Helper/NumberHelperTest.php b/tests/TestCase/View/Helper/NumberHelperTest.php index 1ffef5ad53b..753f5cd7768 100644 --- a/tests/TestCase/View/Helper/NumberHelperTest.php +++ b/tests/TestCase/View/Helper/NumberHelperTest.php @@ -77,44 +77,4 @@ public function testMethodProxying(string $method, mixed $arg): void $helper = new NumberHelper($this->View); $this->assertNotNull($helper->{$method}($arg)); } - - /** - * test format() and empty values - */ - public function testFormatEmpty(): void - { - $helper = new NumberHelper($this->View); - - $value = null; - $result = $helper->format($value); - $this->assertSame('', $result); - - $result = $helper->format($value, ['default' => '-']); - $this->assertSame('-', $result); - - // We should revisit this for 6.x - $value = ''; - $result = $helper->format($value); - $this->assertSame('0', $result); - } - - /** - * test currency() and empty values - */ - public function testCurrencyEmpty(): void - { - $helper = new NumberHelper($this->View); - - $value = null; - $result = $helper->currency($value); - $this->assertSame('', $result); - - $result = $helper->currency($value, null, ['default' => '-']); - $this->assertSame('-', $result); - - // We should revisit this for 6.x - $value = ''; - $result = $helper->currency($value); - $this->assertNotEmpty($result); - } } From 656255a1645e47429c6bfcbcc7c349cfb1f237e6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 12 Apr 2025 00:24:17 -0400 Subject: [PATCH 06/17] Fix mysql column generation (#18320) * Add support for default value expressions in geospatial types * Add test coverage for expression defaults and json types * Add default value parsing for complex types Mysql will return this values with encoding prefixes. Strip those prefixes off so that developers see the actual default value. * Parse collation prefixes out of default values more * Align function name with maria * Fix phpstan/phpcs --- src/Database/Schema/MysqlSchemaDialect.php | 55 ++++++++++++++- .../Schema/MysqlSchemaDialectTest.php | 69 +++++++++++++++---- 2 files changed, 110 insertions(+), 14 deletions(-) diff --git a/src/Database/Schema/MysqlSchemaDialect.php b/src/Database/Schema/MysqlSchemaDialect.php index 817aa485d69..dd6abeecbfb 100644 --- a/src/Database/Schema/MysqlSchemaDialect.php +++ b/src/Database/Schema/MysqlSchemaDialect.php @@ -125,10 +125,12 @@ public function describeColumns(string $tableName): array } foreach ($statement->fetchAll('assoc') as $row) { $field = $this->_convertColumn($row['Type']); + $default = $this->parseDefault($field['type'], $row); + $field += [ 'name' => $row['Field'], 'null' => $row['Null'] === 'YES', - 'default' => $row['Default'], + 'default' => $default, 'collate' => $row['Collation'], 'comment' => $row['Comment'], 'length' => null, @@ -147,6 +149,44 @@ public function describeColumns(string $tableName): array return $columns; } + /** + * Parse the default value if required. + * + * @param string $type The type of column + * @param array $row a Row of schema reflection data + * @return ?string The default value of a column. + */ + protected function parseDefault(string $type, array $row): ?string + { + $default = $row['Default']; + if ( + is_string($default) && + in_array( + $type, + array_merge( + TableSchema::GEOSPATIAL_TYPES, + [TableSchema::TYPE_BINARY, TableSchema::TYPE_JSON, TableSchema::TYPE_TEXT], + ), + ) + ) { + // The default that comes back from MySQL for these types prefixes the collation type and + // surrounds the value with escaped single quotes, for example "_utf8mbf4\'abc\'", and so + // this converts that then down to the default value of "abc" to correspond to what the user + // would have specified in a migration. + $default = (string)preg_replace("/^_(?:[a-zA-Z0-9]+?)\\\'(.*)\\\'$/", '\1', $default); + + // If the default is wrapped in a function, and has a collation marker on it, strip + // the collation marker out + $default = (string)preg_replace( + "/^(?[a-zA-Z0-9_]*\()(?_[a-zA-Z0-9]+)\\\'(?.*)\\\'\)$/", + "\\1'\\3')", + $default, + ); + } + + return $default; + } + /** * @inheritDoc */ @@ -377,9 +417,10 @@ protected function _convertColumn(string $column): array public function convertColumnDescription(TableSchema $schema, array $row): void { $field = $this->_convertColumn($row['Type']); + $default = $this->parseDefault($field['type'], $row); $field += [ 'null' => $row['Null'] === 'YES', - 'default' => $row['Default'], + 'default' => $default, 'collate' => $row['Collation'], 'comment' => $row['Comment'], ]; @@ -725,6 +766,16 @@ public function columnDefinitionSql(array $column): string $out .= " SRID {$column['srid']}"; } + $defaultExpressionTypes = array_merge( + TableSchemaInterface::GEOSPATIAL_TYPES, + [TableSchemaInterface::TYPE_BINARY, TableSchemaInterface::TYPE_TEXT, TableSchemaInterface::TYPE_JSON], + ); + if (in_array($column['type'], $defaultExpressionTypes) && isset($column['default'])) { + // Geospatial, blob and text types need to be wrapped in () to create an expression. + $out .= ' DEFAULT (' . $this->_driver->schemaValue($column['default']) . ')'; + unset($column['default']); + } + $dateTimeTypes = [ TableSchemaInterface::TYPE_DATETIME, TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, diff --git a/tests/TestCase/Database/Schema/MysqlSchemaDialectTest.php b/tests/TestCase/Database/Schema/MysqlSchemaDialectTest.php index c9c642cca0d..c033b648fc4 100644 --- a/tests/TestCase/Database/Schema/MysqlSchemaDialectTest.php +++ b/tests/TestCase/Database/Schema/MysqlSchemaDialectTest.php @@ -275,6 +275,26 @@ public function testConvertColumn(string $type, array $expected): void $this->assertSame($expected, $actual); } + public function testConvertColumnBlobDefault(): void + { + $field = [ + 'Field' => 'field', + 'Type' => 'binary', + 'Null' => 'YES', + 'Default' => "_utf8mb4\\'abc\\'", + 'Collation' => 'utf8_general_ci', + 'Comment' => 'Comment section', + ]; + $driver = $this->getMockBuilder(Mysql::class)->getMock(); + $dialect = new MysqlSchemaDialect($driver); + + $table = new TableSchema('table'); + $dialect->convertColumnDescription($table, $field); + + $actual = $table->getColumn('field'); + $this->assertSame('abc', $actual['default']); + } + /** * Helper method for testing methods. * @@ -536,7 +556,7 @@ public function testDescribeTableGeometry(): void id INTEGER, geo_line LINESTRING, geo_geometry GEOMETRY, - geo_point POINT, + geo_point POINT DEFAULT (ST_GeometryFromText('POINT(10 10)')), geo_polygon POLYGON ) SQL; @@ -577,7 +597,7 @@ public function testDescribeTableGeometry(): void 'geo_point' => [ 'type' => 'point', 'null' => true, - 'default' => null, + 'default' => "st_geometryfromtext('POINT(10 10)')", 'precision' => null, 'length' => null, 'comment' => '', @@ -586,7 +606,7 @@ public function testDescribeTableGeometry(): void 'geo_polygon' => [ 'type' => 'polygon', 'null' => true, - 'default' => null, + 'default' => '', 'precision' => null, 'length' => null, 'comment' => '', @@ -876,6 +896,11 @@ public static function columnSqlProvider(): array ['type' => 'text', 'null' => false], '`body` TEXT NOT NULL', ], + [ + 'body', + ['type' => 'text', 'null' => false, 'default' => 'abc'], + '`body` TEXT NOT NULL DEFAULT (\'abc\')', + ], [ 'body', ['type' => 'text', 'length' => TableSchema::LENGTH_TINY, 'null' => false], @@ -896,12 +921,28 @@ public static function columnSqlProvider(): array ['type' => 'text', 'null' => false, 'collate' => 'utf8_unicode_ci'], '`body` TEXT COLLATE utf8_unicode_ci NOT NULL', ], + // JSON + [ + 'config', + ['type' => 'json', 'null' => false], + '`config` JSON NOT NULL', + ], + [ + 'config', + ['type' => 'json', 'null' => false, 'default' => '{"key":"val"}'], + '`config` JSON NOT NULL DEFAULT (\'{"key":"val"}\')', + ], // Blob / binary [ 'body', ['type' => 'binary', 'null' => false], '`body` BLOB NOT NULL', ], + [ + 'body', + ['type' => 'binary', 'null' => false, 'default' => 'abc'], + "`body` BLOB NOT NULL DEFAULT ('abc')", + ], [ 'body', ['type' => 'binary', 'length' => TableSchema::LENGTH_TINY, 'null' => false], @@ -1145,6 +1186,11 @@ public static function columnSqlProvider(): array ['type' => 'polygon'], '`p` POLYGON', ], + [ + 'p', + ['type' => 'polygon', 'default' => 'POLYGON((30 10,40 40,20 40,10 20,30 10))'], + "`p` POLYGON DEFAULT ('POLYGON((30 10,40 40,20 40,10 20,30 10))')", + ], [ 'p', ['type' => 'polygon', 'null' => false, 'srid' => 4326], @@ -1424,18 +1470,13 @@ public function testColumnSqlPrimaryKey(): void */ public function testCreateSql(): void { - $driver = $this->_getMockedDriver(); + $driver = $this->_getMockedDriver('5.6.0'); $connection = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor() ->getMock(); $connection->expects($this->any())->method('getDriver') ->willReturn($driver); - $this->pdo - ->expects($this->any()) - ->method('getAttribute') - ->willReturn('5.6.0'); - $table = (new TableSchema('posts'))->addColumn('id', [ 'type' => 'integer', 'null' => false, @@ -1695,7 +1736,7 @@ public function testDescribeJson(): void /** * Get a schema instance with a mocked driver/pdo instances */ - protected function _getMockedDriver(): Driver + protected function _getMockedDriver($version = '8.0.7'): Driver { $this->_needsConnection(); @@ -1703,20 +1744,24 @@ protected function _getMockedDriver(): Driver ->onlyMethods(['quote', 'getAttribute', 'quoteIdentifier']) ->disableOriginalConstructor() ->getMock(); - $this->pdo->expects($this->any()) + $this->pdo->expects($this->any()) ->method('quote') ->willReturnCallback(function ($value) { return "'{$value}'"; }); $driver = $this->getMockBuilder(Mysql::class) - ->onlyMethods(['createPdo']) + ->onlyMethods(['createPdo', 'version']) ->getMock(); $driver->expects($this->any()) ->method('createPdo') ->willReturn($this->pdo); + $driver->expects($this->any()) + ->method('version') + ->willReturn($version); + $driver->connect(); return $driver; From 417b76937fe67384d6413781ce581138fb946d67 Mon Sep 17 00:00:00 2001 From: Marcelo Rocha Date: Sun, 13 Apr 2025 17:42:43 -0300 Subject: [PATCH 07/17] =?UTF-8?q?Update=20getBehavior=20annotation=20to=20?= =?UTF-8?q?allow=20better=20code=20analyse=20on=20applica=E2=80=A6=20(#183?= =?UTF-8?q?23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update getBehavior annotation to allow better code analyse on application. * Update annotation to use phpstan prefixed phpdoc * Don't use prefix phpstan for template phpdoc * use base return type for psalm --- src/ORM/Table.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ORM/Table.php b/src/ORM/Table.php index fc4c6012e7e..dacc731f95c 100644 --- a/src/ORM/Table.php +++ b/src/ORM/Table.php @@ -155,6 +155,7 @@ * * @see \Cake\Event\EventManager for reference on the events system. * @link https://book.cakephp.org/5/en/orm/table-objects.html#event-list + * @template TBehaviors of array * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface @@ -844,6 +845,10 @@ public function behaviors(): BehaviorRegistry * * @param string $name The behavior alias to get from the registry. * @return \Cake\ORM\Behavior + * @template TName of key-of + * @phpstan-param TName $name The behavior alias to get from the registry. + * @phpstan-return TBehaviors[TName] + * @psalm-return \Cake\ORM\Behavior * @throws \InvalidArgumentException If the behavior does not exist. */ public function getBehavior(string $name): Behavior From 740d8e49db073f03919c3e15d5f058f5270dfb8c Mon Sep 17 00:00:00 2001 From: Adam Halfar <47174548+Harfusha@users.noreply.github.com> Date: Tue, 15 Apr 2025 06:15:46 +0200 Subject: [PATCH 08/17] Add error context (#17976) * Add error context * Revert original trace, fix annotation * CsFix, fix annotation * Change getErrorMessage from private to protected * CsFix * Remove redundant error logging context with error logging context being added by ErrorTrap, we don't need to do it within `triggerError` which is now a simple alias. * Fix test for functionsglobaltest as well * Fix phpcs --------- Co-authored-by: Mark Story --- src/Core/functions.php | 11 ------- src/Error/ErrorLogger.php | 34 ++++++++++++++++++--- tests/TestCase/Core/FunctionsGlobalTest.php | 2 +- tests/TestCase/Core/FunctionsTest.php | 15 +-------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Core/functions.php b/src/Core/functions.php index 28f55bbe7a3..7b30e90986f 100644 --- a/src/Core/functions.php +++ b/src/Core/functions.php @@ -308,17 +308,6 @@ function env(string $key, string|float|int|bool|null $default = null): string|fl */ function triggerWarning(string $message): void { - $trace = debug_backtrace(); - if (isset($trace[1])) { - $frame = $trace[1]; - $frame += ['file' => '[internal]', 'line' => '??']; - $message = sprintf( - '%s - %s, line: %s', - $message, - $frame['file'], - $frame['line'], - ); - } trigger_error($message, E_USER_WARNING); } } diff --git a/src/Error/ErrorLogger.php b/src/Error/ErrorLogger.php index 41e201b0f9b..5eba2f46054 100644 --- a/src/Error/ErrorLogger.php +++ b/src/Error/ErrorLogger.php @@ -68,13 +68,12 @@ public function log($level, Stringable|string $message, array $context = []): vo */ public function logError(PhpError $error, ?ServerRequestInterface $request = null, bool $includeTrace = false): void { - $message = $error->getMessage(); - if ($request) { + $message = $this->getErrorMessage($error, $includeTrace); + + if ($request instanceof ServerRequestInterface) { $message .= $this->getRequestContext($request); } - if ($includeTrace) { - $message .= "\nTrace:\n" . $error->getTraceAsString() . "\n"; - } + $label = $error->getLabel(); $level = match ($label) { 'strict' => LOG_NOTICE, @@ -85,6 +84,31 @@ public function logError(PhpError $error, ?ServerRequestInterface $request = nul $this->log($level, $message); } + /** + * Generate the message for the error + * + * @param \Cake\Error\PhpError $error The exception to log a message for. + * @param bool $includeTrace Whether or not to include a stack trace. + * @return string Error message + */ + protected function getErrorMessage(PhpError $error, bool $includeTrace = false): string + { + $message = sprintf( + '%s in %s on line %s', + $error->getMessage(), + $error->getFile(), + $error->getLine(), + ); + + if (!$includeTrace) { + return $message; + } + + $message .= "\nTrace:\n" . $error->getTraceAsString() . "\n"; + + return $message; + } + /** * @inheritDoc */ diff --git a/tests/TestCase/Core/FunctionsGlobalTest.php b/tests/TestCase/Core/FunctionsGlobalTest.php index c352d432687..929341a0112 100644 --- a/tests/TestCase/Core/FunctionsGlobalTest.php +++ b/tests/TestCase/Core/FunctionsGlobalTest.php @@ -327,7 +327,7 @@ public function testTriggerWarningEnabled(): void triggerWarning('This will be gone one day'); $this->assertTrue(true); }); - $this->assertMatchesRegularExpression('/This will be gone one day - (.*?)[\/\\\]FunctionsGlobalTest.php, line\: \d+/', $error->getMessage()); + $this->assertMatchesRegularExpression('/This will be gone one day/', $error->getMessage()); } /** diff --git a/tests/TestCase/Core/FunctionsTest.php b/tests/TestCase/Core/FunctionsTest.php index ff7f7c10610..4b0edf2cc8a 100644 --- a/tests/TestCase/Core/FunctionsTest.php +++ b/tests/TestCase/Core/FunctionsTest.php @@ -20,7 +20,6 @@ use Cake\Http\Response; use Cake\TestSuite\TestCase; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\WithoutErrorHandler; use stdClass; use Stringable; use function Cake\Core\deprecationWarning; @@ -378,7 +377,7 @@ public function testDeprecationWarningLevelDisabled(): void */ public function testTriggerWarningEnabled(): void { - $this->expectWarningMessageMatches('/This will be gone one day - (.*?)[\/\\\]TestCase.php, line\: \d+/', function (): void { + $this->expectWarningMessageMatches('/This will be gone one day/', function (): void { $this->withErrorReporting(E_ALL, function (): void { triggerWarning('This will be gone one day'); $this->assertTrue(true); @@ -386,18 +385,6 @@ public function testTriggerWarningEnabled(): void }); } - /** - * Test no error when warning level is off. - */ - #[WithoutErrorHandler] - public function testTriggerWarningLevelDisabled(): void - { - $this->withErrorReporting(E_ALL ^ E_USER_WARNING, function (): void { - triggerWarning('This was a mistake.'); - $this->assertTrue(true); - }); - } - #[DataProvider('toStringProvider')] public function testToString(mixed $rawValue, ?string $expected): void { From 546b6cd7ccc821146a44bb150d99c5df50436ae6 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 15 Apr 2025 12:41:23 +0530 Subject: [PATCH 09/17] Update phpstan --- .phive/phars.xml | 2 +- phpstan-baseline.neon | 18 ------------------ src/ORM/phpstan.neon.dist | 1 - src/View/ViewBuilder.php | 1 + 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index c70ff296ea2..3d13d7f8053 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,5 @@ - + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a9726915c67..5cac2a2fd04 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -114,12 +114,6 @@ parameters: count: 1 path: src/Datasource/QueryInterface.php - - - message: '#^Method Cake\\Error\\ErrorLogger\:\:log\(\) has parameter \$level with no type specified\.$#' - identifier: missingType.parameter - count: 1 - path: src/Error/ErrorLogger.php - - message: '#^Constructor of class Cake\\Error\\Renderer\\ConsoleExceptionRenderer has an unused parameter \$request\.$#' identifier: constructor.unusedParameter @@ -264,12 +258,6 @@ parameters: count: 1 path: src/ORM/Query/SelectQuery.php - - - message: '#^PHPDoc tag @return with type Cake\\ORM\\Query\\SelectQuery\ is not subtype of native type static\(Cake\\ORM\\Query\\SelectQuery\\)\.$#' - identifier: return.phpDocType - count: 1 - path: src/ORM/Query/SelectQuery.php - - message: '#^Call to function method_exists\(\) with Cake\\Datasource\\EntityInterface and ''patch'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType @@ -431,9 +419,3 @@ parameters: identifier: function.alreadyNarrowedType count: 1 path: src/View/Widget/DateTimeWidget.php - - - - message: '#^PHPDoc tag @var with type array\\|bool\|int\|string\|null is not subtype of native type mixed\.$#' - identifier: varTag.nativeType - count: 1 - path: src/View/XmlView.php diff --git a/src/ORM/phpstan.neon.dist b/src/ORM/phpstan.neon.dist index 239541a0f84..a2621ab144c 100644 --- a/src/ORM/phpstan.neon.dist +++ b/src/ORM/phpstan.neon.dist @@ -15,7 +15,6 @@ parameters: - identifier: missingType.generics - '#Unsafe usage of new static\(\).#' - - "#^PHPDoc tag @return with type Cake\\\\ORM\\\\Query\\\\SelectQuery\\ is not subtype of native type static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\)\\.$#" - "#^Method Cake\\\\ORM\\\\Query\\\\SelectQuery\\:\\:find\\(\\) should return static\\(Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\) but returns Cake\\\\ORM\\\\Query\\\\SelectQuery\\\\.$#" - '#^PHPDoc tag @var with type callable\(\): mixed is not subtype of native type Closure\(string\): string\.$#' - diff --git a/src/View/ViewBuilder.php b/src/View/ViewBuilder.php index ab3741c18af..2b9ad02652e 100644 --- a/src/View/ViewBuilder.php +++ b/src/View/ViewBuilder.php @@ -621,6 +621,7 @@ public function jsonSerialize(): array $array[$property] = $this->{$property}; } + /** @phpstan-ignore-next-line argument.type */ array_walk_recursive($array['_vars'], $this->_checkViewVars(...)); return array_filter($array, function ($i) { From b2c5e3f47b85d77b2407ff9d6d950fcf04d7b1d6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 15 Apr 2025 10:37:42 -0400 Subject: [PATCH 10/17] Implement columnDefinitionSql for postgres (#18325) * Implement columnDefinitionSql() for postgres. Move and refactor to expose schema generation with lower level data. * Implement columnDefinitionSql for sqlserver * Only automatically promote id columns to autoIncrement. * Restore condition on id => autoIncrement convention Don't convert all single column integer primary keys to be autoincrement. --- src/Database/Schema/PostgresSchemaDialect.php | 96 ++++++++++------ .../Schema/SqlserverSchemaDialect.php | 103 +++++++++++------- .../Schema/PostgresSchemaDialectTest.php | 3 + .../Schema/SqlserverSchemaDialectTest.php | 3 + 4 files changed, 132 insertions(+), 73 deletions(-) diff --git a/src/Database/Schema/PostgresSchemaDialect.php b/src/Database/Schema/PostgresSchemaDialect.php index 39f81b981e8..2007ce4a3ab 100644 --- a/src/Database/Schema/PostgresSchemaDialect.php +++ b/src/Database/Schema/PostgresSchemaDialect.php @@ -609,12 +609,39 @@ public function columnSql(TableSchema $schema, string $name): string { $data = $schema->getColumn($name); assert($data !== null); + $data['name'] = $name; $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); if ($sql !== null) { return $sql; } + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $primaryKey = $schema->getPrimaryKey(); + if ( + in_array($data['type'], $autoIncrementTypes, true) && + $primaryKey === [$name] && $name === 'id' + ) { + $data['autoIncrement'] = true; + } + return $this->columnDefinitionSql($data); + } + + /** + * @inheritDoc + */ + public function columnDefinitionSql(array $column): string + { + $name = $column['name']; + $column += [ + 'length' => null, + 'precision' => null, + ]; $out = $this->_driver->quoteIdentifier($name); $typeMap = [ TableSchemaInterface::TYPE_TINYINTEGER => ' SMALLINT', @@ -648,41 +675,40 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_INTEGER, TableSchemaInterface::TYPE_BIGINTEGER, ]; + $autoIncrement = (bool)($column['autoIncrement'] ?? false); if ( - in_array($data['type'], $autoIncrementTypes, true) && - ( - ($schema->getPrimaryKey() === [$name] && $name === 'id') || $data['autoIncrement'] - ) + in_array($column['type'], $autoIncrementTypes, true) && + $autoIncrement ) { - $typeMap[$data['type']] = str_replace('INT', 'SERIAL', $typeMap[$data['type']]); - unset($data['default']); + $typeMap[$column['type']] = str_replace('INT', 'SERIAL', $typeMap[$column['type']]); + unset($column['default']); } - if (isset($typeMap[$data['type']])) { - $out .= $typeMap[$data['type']]; + if (isset($typeMap[$column['type']])) { + $out .= $typeMap[$column['type']]; } - if ($data['type'] === TableSchemaInterface::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) { + if ($column['type'] === TableSchemaInterface::TYPE_TEXT && $column['length'] !== TableSchema::LENGTH_TINY) { $out .= ' TEXT'; } - if ($data['type'] === TableSchemaInterface::TYPE_BINARY) { + if ($column['type'] === TableSchemaInterface::TYPE_BINARY) { $out .= ' BYTEA'; } - if ($data['type'] === TableSchemaInterface::TYPE_CHAR) { - $out .= '(' . $data['length'] . ')'; + if ($column['type'] === TableSchemaInterface::TYPE_CHAR) { + $out .= '(' . $column['length'] . ')'; } if ( - $data['type'] === TableSchemaInterface::TYPE_STRING || + $column['type'] === TableSchemaInterface::TYPE_STRING || ( - $data['type'] === TableSchemaInterface::TYPE_TEXT && - $data['length'] === TableSchema::LENGTH_TINY + $column['type'] === TableSchemaInterface::TYPE_TEXT && + $column['length'] === TableSchema::LENGTH_TINY ) ) { $out .= ' VARCHAR'; - if (isset($data['length']) && $data['length'] !== '') { - $out .= '(' . $data['length'] . ')'; + if (isset($column['length']) && $column['length'] !== '') { + $out .= '(' . $column['length'] . ')'; } } @@ -691,8 +717,8 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_STRING, TableSchemaInterface::TYPE_CHAR, ]; - if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') { - $out .= ' COLLATE "' . $data['collate'] . '"'; + if (in_array($column['type'], $hasCollate, true) && isset($column['collate']) && $column['collate'] !== '') { + $out .= ' COLLATE "' . $column['collate'] . '"'; } $hasPrecision = [ @@ -703,24 +729,24 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; - if (in_array($data['type'], $hasPrecision) && isset($data['precision'])) { - $out .= '(' . $data['precision'] . ')'; + if (in_array($column['type'], $hasPrecision) && isset($column['precision'])) { + $out .= '(' . $column['precision'] . ')'; } if ( - $data['type'] === TableSchemaInterface::TYPE_DECIMAL && + $column['type'] === TableSchemaInterface::TYPE_DECIMAL && ( - isset($data['length']) || - isset($data['precision']) + isset($column['length']) || + isset($column['precision']) ) ) { - $out .= '(' . $data['length'] . ',' . (int)$data['precision'] . ')'; + $out .= '(' . $column['length'] . ',' . (int)$column['precision'] . ')'; } - if (in_array($data['type'], TableSchemaInterface::GEOSPATIAL_TYPES)) { - $out = sprintf($out, $data['srid'] ?? self::DEFAULT_SRID); + if (in_array($column['type'], TableSchemaInterface::GEOSPATIAL_TYPES)) { + $out = sprintf($out, $column['srid'] ?? self::DEFAULT_SRID); } - if (isset($data['null']) && $data['null'] === false) { + if (isset($column['null']) && $column['null'] === false) { $out .= ' NOT NULL'; } @@ -732,18 +758,18 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; if ( - isset($data['default']) && - in_array($data['type'], $datetimeTypes) && - strtolower($data['default']) === 'current_timestamp' + isset($column['default']) && + in_array($column['type'], $datetimeTypes) && + strtolower($column['default']) === 'current_timestamp' ) { $out .= ' DEFAULT CURRENT_TIMESTAMP'; - } elseif (isset($data['default'])) { - $defaultValue = $data['default']; - if ($data['type'] === 'boolean') { + } elseif (isset($column['default'])) { + $defaultValue = $column['default']; + if ($column['type'] === 'boolean') { $defaultValue = (bool)$defaultValue; } $out .= ' DEFAULT ' . $this->_driver->schemaValue($defaultValue); - } elseif (isset($data['null']) && $data['null'] !== false) { + } elseif (isset($column['null']) && $column['null'] !== false) { $out .= ' DEFAULT NULL'; } diff --git a/src/Database/Schema/SqlserverSchemaDialect.php b/src/Database/Schema/SqlserverSchemaDialect.php index 09b5f06e645..0158014ef80 100644 --- a/src/Database/Schema/SqlserverSchemaDialect.php +++ b/src/Database/Schema/SqlserverSchemaDialect.php @@ -581,12 +581,40 @@ public function columnSql(TableSchema $schema, string $name): string { $data = $schema->getColumn($name); assert($data !== null); + $data['name'] = $name; $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); if ($sql !== null) { return $sql; } + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $primaryKey = $schema->getPrimaryKey(); + if ( + in_array($data['type'], $autoIncrementTypes, true) && + $primaryKey === [$name] && + $name === 'id' + ) { + $data['autoIncrement'] = true; + } + return $this->columnDefinitionSql($data); + } + + /** + * @inheritDoc + */ + public function columnDefinitionSql(array $column): string + { + $name = $column['name']; + $column += [ + 'length' => null, + 'precision' => null, + ]; $out = $this->_driver->quoteIdentifier($name); $typeMap = [ TableSchemaInterface::TYPE_TINYINTEGER => ' TINYINT', @@ -614,8 +642,8 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_POLYGON => ' GEOGRAPHY', ]; - if (isset($typeMap[$data['type']])) { - $out .= $typeMap[$data['type']]; + if (isset($typeMap[$column['type']])) { + $out .= $typeMap[$column['type']]; } $autoIncrementTypes = [ @@ -624,50 +652,49 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_INTEGER, TableSchemaInterface::TYPE_BIGINTEGER, ]; + $autoIncrement = (bool)($column['autoIncrement'] ?? false); if ( - in_array($data['type'], $autoIncrementTypes, true) && - ( - ($schema->getPrimaryKey() === [$name] && $name === 'id') || $data['autoIncrement'] - ) + in_array($column['type'], $autoIncrementTypes, true) && + $autoIncrement ) { $out .= ' IDENTITY(1, 1)'; - unset($data['default']); + unset($column['default']); } - if ($data['type'] === TableSchemaInterface::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) { + if ($column['type'] === TableSchemaInterface::TYPE_TEXT && $column['length'] !== TableSchema::LENGTH_TINY) { $out .= ' NVARCHAR(MAX)'; } - if ($data['type'] === TableSchemaInterface::TYPE_CHAR) { - $out .= '(' . $data['length'] . ')'; + if ($column['type'] === TableSchemaInterface::TYPE_CHAR) { + $out .= '(' . $column['length'] . ')'; } - if ($data['type'] === TableSchemaInterface::TYPE_BINARY) { + if ($column['type'] === TableSchemaInterface::TYPE_BINARY) { if ( - !isset($data['length']) - || in_array($data['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true) + !isset($column['length']) + || in_array($column['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true) ) { - $data['length'] = 'MAX'; + $column['length'] = 'MAX'; } - if ($data['length'] === 1) { + if ($column['length'] === 1) { $out .= ' BINARY(1)'; } else { $out .= ' VARBINARY'; - $out .= sprintf('(%s)', $data['length']); + $out .= sprintf('(%s)', $column['length']); } } if ( - $data['type'] === TableSchemaInterface::TYPE_STRING || + $column['type'] === TableSchemaInterface::TYPE_STRING || ( - $data['type'] === TableSchemaInterface::TYPE_TEXT && - $data['length'] === TableSchema::LENGTH_TINY + $column['type'] === TableSchemaInterface::TYPE_TEXT && + $column['length'] === TableSchema::LENGTH_TINY ) ) { $type = ' NVARCHAR'; - $length = $data['length'] ?? TableSchema::LENGTH_TINY; + $length = $column['length'] ?? TableSchema::LENGTH_TINY; $out .= sprintf('%s(%d)', $type, $length); } @@ -676,8 +703,8 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_STRING, TableSchemaInterface::TYPE_CHAR, ]; - if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') { - $out .= ' COLLATE ' . $data['collate']; + if (in_array($column['type'], $hasCollate, true) && isset($column['collate']) && $column['collate'] !== '') { + $out .= ' COLLATE ' . $column['collate']; } $precisionTypes = [ @@ -687,21 +714,21 @@ public function columnSql(TableSchema $schema, string $name): string TableSchemaInterface::TYPE_TIMESTAMP, TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, ]; - if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) { - $out .= '(' . (int)$data['precision'] . ')'; + if (in_array($column['type'], $precisionTypes, true) && isset($column['precision'])) { + $out .= '(' . (int)$column['precision'] . ')'; } if ( - $data['type'] === TableSchemaInterface::TYPE_DECIMAL && + $column['type'] === TableSchemaInterface::TYPE_DECIMAL && ( - isset($data['length']) || - isset($data['precision']) + isset($column['length']) || + isset($column['precision']) ) ) { - $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')'; + $out .= '(' . (int)$column['length'] . ',' . (int)$column['precision'] . ')'; } - if (isset($data['null']) && $data['null'] === false) { + if (isset($column['null']) && $column['null'] === false) { $out .= ' NOT NULL'; } @@ -720,17 +747,17 @@ public function columnSql(TableSchema $schema, string $name): string 'sysdatetimeoffset()', ]; if ( - isset($data['default']) && - in_array($data['type'], $dateTimeTypes, true) && - in_array(strtolower($data['default']), $dateTimeDefaults, true) + isset($column['default']) && + in_array($column['type'], $dateTimeTypes, true) && + in_array(strtolower($column['default']), $dateTimeDefaults, true) ) { - $out .= ' DEFAULT ' . strtoupper($data['default']); - } elseif (isset($data['default'])) { - $default = is_bool($data['default']) - ? (int)$data['default'] - : $this->_driver->schemaValue($data['default']); + $out .= ' DEFAULT ' . strtoupper($column['default']); + } elseif (isset($column['default'])) { + $default = is_bool($column['default']) + ? (int)$column['default'] + : $this->_driver->schemaValue($column['default']); $out .= ' DEFAULT ' . $default; - } elseif (isset($data['null']) && $data['null'] !== false) { + } elseif (isset($column['null']) && $column['null'] !== false) { $out .= ' DEFAULT NULL'; } diff --git a/tests/TestCase/Database/Schema/PostgresSchemaDialectTest.php b/tests/TestCase/Database/Schema/PostgresSchemaDialectTest.php index ec49e4080b4..b1424bb4d9c 100644 --- a/tests/TestCase/Database/Schema/PostgresSchemaDialectTest.php +++ b/tests/TestCase/Database/Schema/PostgresSchemaDialectTest.php @@ -1120,6 +1120,9 @@ public function testColumnSql(string $name, array $data, string $expected): void $table = (new TableSchema('schema_articles'))->addColumn($name, $data); $this->assertEquals($expected, $schema->columnSql($table, $name)); + + $data['name'] = $name; + $this->assertEquals($expected, $schema->columnDefinitionSql($data)); } /** diff --git a/tests/TestCase/Database/Schema/SqlserverSchemaDialectTest.php b/tests/TestCase/Database/Schema/SqlserverSchemaDialectTest.php index abff44109be..8e9b7aa2965 100644 --- a/tests/TestCase/Database/Schema/SqlserverSchemaDialectTest.php +++ b/tests/TestCase/Database/Schema/SqlserverSchemaDialectTest.php @@ -969,6 +969,9 @@ public function testColumnSql(string $name, array $data, string $expected): void $table = (new TableSchema('schema_articles'))->addColumn($name, $data); $this->assertEquals($expected, $schema->columnSql($table, $name)); + + $data['name'] = $name; + $this->assertEquals($expected, $schema->columnDefinitionSql($data)); } /** From d55804353ff9cb8e6bead40b37252b80e23ae3d3 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 15 Apr 2025 21:22:17 +0530 Subject: [PATCH 11/17] Drop psalm --- .github/workflows/ci.yml | 4 ---- .phive/phars.xml | 1 - src/Cache/Cache.php | 2 +- src/Cache/Engine/FileEngine.php | 2 -- src/Collection/CollectionTrait.php | 3 --- src/Collection/Iterator/BufferedIterator.php | 1 - src/Collection/Iterator/TreeIterator.php | 6 +++--- src/Collection/Iterator/TreePrinter.php | 2 +- src/Command/Helper/TableHelper.php | 1 - src/Command/I18nExtractCommand.php | 3 --- src/Command/PluginAssetsCopyCommand.php | 2 -- src/Command/PluginAssetsRemoveCommand.php | 2 -- src/Command/PluginAssetsSymlinkCommand.php | 2 -- src/Command/PluginLoadCommand.php | 2 -- src/Console/BaseCommand.php | 1 - src/Console/Command/HelpCommand.php | 2 +- src/Console/CommandCollection.php | 2 +- src/Console/ConsoleInput.php | 1 - src/Console/ConsoleOutput.php | 1 - .../TestSuite/ConsoleIntegrationTestTrait.php | 1 - .../Component/FormProtectionComponent.php | 2 +- src/Controller/ComponentRegistry.php | 1 - src/Controller/Controller.php | 4 ++-- src/Core/ObjectRegistry.php | 20 +++++++++---------- src/Core/PluginCollection.php | 2 +- src/Core/StaticConfigTrait.php | 2 +- src/Core/TestSuite/ContainerStubTrait.php | 4 ++-- src/Core/functions.php | 4 ++-- src/Core/functions_global.php | 2 +- src/Database/Connection.php | 4 +--- src/Database/Driver.php | 5 ++--- .../Expression/CaseExpressionTrait.php | 1 - .../Expression/FunctionExpression.php | 1 - src/Database/Expression/QueryExpression.php | 1 - src/Database/Expression/TupleComparison.php | 1 - .../Expression/WhenThenExpression.php | 1 - src/Database/Expression/WindowInterface.php | 6 +++--- src/Database/Query.php | 1 - src/Database/Schema/SqliteSchemaDialect.php | 1 - src/Database/Statement/Statement.php | 4 +--- src/Database/TypeFactory.php | 6 +++--- src/Datasource/ConnectionManager.php | 2 +- src/Datasource/EntityTrait.php | 1 - src/Datasource/ModelAwareTrait.php | 1 - src/Datasource/RulesAwareTrait.php | 1 - src/Error/Debugger.php | 1 - src/Error/functions.php | 1 - src/Error/functions_global.php | 1 - src/Event/Event.php | 6 +++--- src/Event/EventInterface.php | 2 +- src/Form/Form.php | 2 +- src/Form/FormProtector.php | 4 ++-- src/Http/Client.php | 2 +- src/Http/Client/Adapter/Stream.php | 1 - src/Http/ControllerFactoryInterface.php | 4 ++-- src/Http/Cookie/Cookie.php | 1 - src/Http/MiddlewareQueue.php | 2 -- src/Http/Response.php | 1 - src/Http/Session.php | 2 -- src/Http/UriFactory.php | 4 ++-- src/I18n/Date.php | 2 +- src/I18n/DateTime.php | 2 +- src/I18n/Parser/PoFileParser.php | 3 --- src/I18n/RelativeTimeFormatter.php | 2 +- src/I18n/Time.php | 2 +- src/Log/Engine/BaseLog.php | 1 - src/Log/Log.php | 3 +-- src/Mailer/AbstractTransport.php | 2 +- src/Mailer/Mailer.php | 10 +++++----- src/Mailer/Renderer.php | 4 ++-- src/Mailer/TransportFactory.php | 2 +- src/Network/Socket.php | 2 -- src/ORM/Association/BelongsToMany.php | 1 - src/ORM/Association/HasMany.php | 1 - src/ORM/AssociationCollection.php | 8 ++++---- src/ORM/Behavior/TranslateBehavior.php | 6 +++--- src/ORM/Behavior/TreeBehavior.php | 4 ++-- src/ORM/BehaviorRegistry.php | 2 +- src/ORM/EagerLoader.php | 1 - src/ORM/Locator/LocatorInterface.php | 1 - src/ORM/Locator/TableLocator.php | 6 ++---- src/ORM/Query/SelectQuery.php | 2 -- src/ORM/Table.php | 3 +-- src/Routing/Asset.php | 2 +- src/Routing/Route/Route.php | 2 -- .../Constraint/Response/StatusCodeBase.php | 2 -- .../Constraint/Session/FlashParamEquals.php | 2 -- .../Constraint/Session/SessionEquals.php | 1 - .../Constraint/Session/SessionHasKey.php | 1 - src/TestSuite/Fixture/TestFixture.php | 1 - src/TestSuite/IntegrationTestTrait.php | 2 -- src/TestSuite/TestCase.php | 3 --- src/Utility/Filesystem.php | 3 --- src/Utility/Hash.php | 10 +++++----- src/Utility/Xml.php | 4 +--- src/Validation/RulesProvider.php | 2 +- src/Validation/Validator.php | 8 ++++---- src/View/Cell.php | 1 - .../MissingCellTemplateException.php | 2 +- .../Exception/MissingTemplateException.php | 2 +- src/View/Helper/FormHelper.php | 1 - src/View/Helper/HtmlHelper.php | 4 +--- src/View/Helper/PaginatorHelper.php | 2 +- src/View/Helper/UrlHelper.php | 2 +- src/View/View.php | 10 +++++----- src/View/ViewBuilder.php | 2 +- .../TestApp/Database/Schema/CompatDialect.php | 1 - 107 files changed, 100 insertions(+), 193 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c848f18749..b7c5b9a3148 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,10 +218,6 @@ jobs: if: always() run: vendor/bin/phpcs --report=checkstyle | cs2pr - - name: Run psalm - if: always() - run: tools/psalm --output-format=github - - name: Run phpstan if: always() run: tools/phpstan analyse --error-format=github diff --git a/.phive/phars.xml b/.phive/phars.xml index 3d13d7f8053..4acd75dc41b 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,4 @@ - diff --git a/src/Cache/Cache.php b/src/Cache/Cache.php index c69d9aa2419..dab7c9be2ad 100644 --- a/src/Cache/Cache.php +++ b/src/Cache/Cache.php @@ -74,7 +74,7 @@ class Cache * class names. * * @var array - * @psalm-var array + * @phpstan-var array */ protected static array $_dsnClassMap = [ 'array' => Engine\ArrayEngine::class, diff --git a/src/Cache/Engine/FileEngine.php b/src/Cache/Engine/FileEngine.php index 2e7761b82d2..e38405f60f5 100644 --- a/src/Cache/Engine/FileEngine.php +++ b/src/Cache/Engine/FileEngine.php @@ -182,7 +182,6 @@ public function get(string $key, mixed $default = null): mixed $data = ''; $this->_File->next(); while ($this->_File->valid()) { - /** @psalm-suppress PossiblyInvalidOperand */ $data .= $this->_File->current(); $this->_File->next(); } @@ -374,7 +373,6 @@ protected function _setKey(string $key, bool $createKey = false): bool if (!$createKey && !$path->isFile()) { return false; } - /** @psalm-suppress TypeDoesNotContainType */ if ( !isset($this->_File) || $this->_File->getBasename() !== $key || diff --git a/src/Collection/CollectionTrait.php b/src/Collection/CollectionTrait.php index 21510fb9d45..07b0c7e7e35 100644 --- a/src/Collection/CollectionTrait.php +++ b/src/Collection/CollectionTrait.php @@ -734,7 +734,6 @@ public function nest( } if (!$key || !isset($parents[$key])) { foreach ($values as $id) { - /** @psalm-suppress PossiblyInvalidArgument */ $parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1); $mapReduce->emit($parents[$id]); } @@ -1043,7 +1042,6 @@ public function cartesianProduct(?callable $operation = null, ?callable $filter while (!($changeIndex === 0 && $currentIndexes[0] === $collectionArraysCounts[0])) { $currentCombination = array_map(function ($value, $keys, $index) { - /** @psalm-suppress InvalidArrayOffset */ return $value[$keys[$index]]; }, $collectionArrays, $collectionArraysKeys, $currentIndexes); @@ -1053,7 +1051,6 @@ public function cartesianProduct(?callable $operation = null, ?callable $filter $currentIndexes[$lastIndex]++; - /** @psalm-suppress InvalidArrayOffset */ for ( $changeIndex = $lastIndex; $currentIndexes[$changeIndex] === $collectionArraysCounts[$changeIndex] && $changeIndex > 0; diff --git a/src/Collection/Iterator/BufferedIterator.php b/src/Collection/Iterator/BufferedIterator.php index 27513b6b153..33573974bb3 100644 --- a/src/Collection/Iterator/BufferedIterator.php +++ b/src/Collection/Iterator/BufferedIterator.php @@ -29,7 +29,6 @@ class BufferedIterator extends Collection * The in-memory cache containing results from previous iterators * * @var \SplDoublyLinkedList - * @psalm-suppress MissingTemplateParam */ protected SplDoublyLinkedList $_buffer; diff --git a/src/Collection/Iterator/TreeIterator.php b/src/Collection/Iterator/TreeIterator.php index 2365f3a6465..ad8a9fb7210 100644 --- a/src/Collection/Iterator/TreeIterator.php +++ b/src/Collection/Iterator/TreeIterator.php @@ -35,7 +35,7 @@ class TreeIterator extends RecursiveIteratorIterator implements CollectionInterf * The iteration mode * * @var int - * @psalm-var \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST + * @phpstan-var \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST */ protected int $_mode; @@ -45,8 +45,8 @@ class TreeIterator extends RecursiveIteratorIterator implements CollectionInterf * @param \RecursiveIterator $items The iterator to flatten. * @param int $mode Iterator mode. * @param int $flags Iterator flags. - * @psalm-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST $mode - * @psalm-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::CATCH_GET_CHILD $flags + * @phpstan-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST $mode + * @phpstan-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::CATCH_GET_CHILD $flags */ public function __construct( RecursiveIterator $items, diff --git a/src/Collection/Iterator/TreePrinter.php b/src/Collection/Iterator/TreePrinter.php index 76dacfbdb44..8a4c4bc2caa 100644 --- a/src/Collection/Iterator/TreePrinter.php +++ b/src/Collection/Iterator/TreePrinter.php @@ -70,7 +70,7 @@ class TreePrinter extends RecursiveIteratorIterator implements CollectionInterfa * @param string $spacer The string to use for prefixing the values according to * their depth in the tree. * @param int $mode Iterator mode. - * @psalm-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST $mode + * @phpstan-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST $mode */ public function __construct( RecursiveIterator $items, diff --git a/src/Command/Helper/TableHelper.php b/src/Command/Helper/TableHelper.php index 0e7538f7f97..0798b55e2b9 100644 --- a/src/Command/Helper/TableHelper.php +++ b/src/Command/Helper/TableHelper.php @@ -46,7 +46,6 @@ protected function _calculateWidths(array $rows): array $widths = []; foreach ($rows as $line) { foreach (array_values($line) as $k => $v) { - /** @psalm-suppress InvalidCast */ $columnLength = $this->_cellWidth((string)$v); if ($columnLength >= ($widths[$k] ?? 0)) { $widths[$k] = $columnLength; diff --git a/src/Command/I18nExtractCommand.php b/src/Command/I18nExtractCommand.php index a23fd206640..213e01e0947 100644 --- a/src/Command/I18nExtractCommand.php +++ b/src/Command/I18nExtractCommand.php @@ -140,7 +140,6 @@ public static function getDescription(): string */ protected function _getPaths(ConsoleIo $io): void { - /** @psalm-suppress UndefinedConstant */ $defaultPaths = array_merge( [APP], array_values(App::path('templates')), @@ -538,7 +537,6 @@ protected function _parse(ConsoleIo $io, string $functionName, array $map): void protected function _buildFiles(Arguments $args): void { $paths = $this->_paths; - /** @psalm-suppress UndefinedConstant */ $paths[] = realpath(APP) . DIRECTORY_SEPARATOR; usort($paths, function (string $a, string $b) { @@ -868,7 +866,6 @@ protected function _searchFiles(): void */ protected function _isExtractingApp(): bool { - /** @psalm-suppress UndefinedConstant */ return $this->_paths === [APP]; } diff --git a/src/Command/PluginAssetsCopyCommand.php b/src/Command/PluginAssetsCopyCommand.php index c3f4738afd6..2221e374e17 100644 --- a/src/Command/PluginAssetsCopyCommand.php +++ b/src/Command/PluginAssetsCopyCommand.php @@ -22,8 +22,6 @@ /** * Command for copying plugin assets to app's webroot. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginAssetsCopyCommand extends Command { diff --git a/src/Command/PluginAssetsRemoveCommand.php b/src/Command/PluginAssetsRemoveCommand.php index 3fa77addc42..b0d613b2e27 100644 --- a/src/Command/PluginAssetsRemoveCommand.php +++ b/src/Command/PluginAssetsRemoveCommand.php @@ -22,8 +22,6 @@ /** * Command for removing plugin assets from app's webroot. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginAssetsRemoveCommand extends Command { diff --git a/src/Command/PluginAssetsSymlinkCommand.php b/src/Command/PluginAssetsSymlinkCommand.php index 254d9c8f205..ba49750f574 100644 --- a/src/Command/PluginAssetsSymlinkCommand.php +++ b/src/Command/PluginAssetsSymlinkCommand.php @@ -22,8 +22,6 @@ /** * Command for symlinking / copying plugin assets to app's webroot. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginAssetsSymlinkCommand extends Command { diff --git a/src/Command/PluginLoadCommand.php b/src/Command/PluginLoadCommand.php index 7220e9e1222..469b79fd419 100644 --- a/src/Command/PluginLoadCommand.php +++ b/src/Command/PluginLoadCommand.php @@ -27,8 +27,6 @@ /** * Command for loading plugins. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginLoadCommand extends Command { diff --git a/src/Console/BaseCommand.php b/src/Console/BaseCommand.php index 28ccb4d41bb..646db2984f4 100644 --- a/src/Console/BaseCommand.php +++ b/src/Console/BaseCommand.php @@ -117,7 +117,6 @@ public function getRootName(): string public static function defaultName(): string { $pos = strrpos(static::class, '\\'); - /** @psalm-suppress PossiblyFalseOperand */ $name = substr(static::class, $pos + 1, -7); return Inflector::underscore($name); diff --git a/src/Console/Command/HelpCommand.php b/src/Console/Command/HelpCommand.php index 07df45ba534..ab7c69d030a 100644 --- a/src/Console/Command/HelpCommand.php +++ b/src/Console/Command/HelpCommand.php @@ -182,7 +182,7 @@ protected function outputPaths(ConsoleIo $io): void /** * @param array $names Names * @return string - * @psalm-param non-empty-array $names + * @phpstan-param non-empty-array $names */ protected function getShortestName(array $names): string { diff --git a/src/Console/CommandCollection.php b/src/Console/CommandCollection.php index 2af54a2cba4..d656f29e803 100644 --- a/src/Console/CommandCollection.php +++ b/src/Console/CommandCollection.php @@ -146,7 +146,7 @@ public function get(string $name): CommandInterface|string * Implementation of IteratorAggregate. * * @return \Traversable - * @psalm-return \Traversable> + * @phpstan-return \Traversable> */ public function getIterator(): Traversable { diff --git a/src/Console/ConsoleInput.php b/src/Console/ConsoleInput.php index bf4c4016998..a1df57367fd 100644 --- a/src/Console/ConsoleInput.php +++ b/src/Console/ConsoleInput.php @@ -65,7 +65,6 @@ public function __construct(string $handle = 'php://stdin') */ public function __destruct() { - /** @psalm-suppress RedundantCondition */ if (isset($this->_input) && is_resource($this->_input)) { fclose($this->_input); } diff --git a/src/Console/ConsoleOutput.php b/src/Console/ConsoleOutput.php index 249bb2a7b98..09485833aed 100644 --- a/src/Console/ConsoleOutput.php +++ b/src/Console/ConsoleOutput.php @@ -382,7 +382,6 @@ public function setOutputAs(int $type): void */ public function __destruct() { - /** @psalm-suppress RedundantCondition */ if (isset($this->_output) && is_resource($this->_output)) { fclose($this->_output); } diff --git a/src/Console/TestSuite/ConsoleIntegrationTestTrait.php b/src/Console/TestSuite/ConsoleIntegrationTestTrait.php index e8b2378b359..3f911451923 100644 --- a/src/Console/TestSuite/ConsoleIntegrationTestTrait.php +++ b/src/Console/TestSuite/ConsoleIntegrationTestTrait.php @@ -116,7 +116,6 @@ public function exec(string $command, array $input = []): void * Cleans state to get ready for the next test * * @return void - * @psalm-suppress PossiblyNullPropertyAssignmentValue */ #[After] public function cleanupConsoleTrait(): void diff --git a/src/Controller/Component/FormProtectionComponent.php b/src/Controller/Component/FormProtectionComponent.php index 32288590a1a..3895c4abbbe 100644 --- a/src/Controller/Component/FormProtectionComponent.php +++ b/src/Controller/Component/FormProtectionComponent.php @@ -33,7 +33,7 @@ * - Existing fields have not been removed from the form. * - Values of hidden inputs have not been changed. * - * @psalm-property array{validate:bool, unlockedFields:array, unlockedActions:array, validationFailureCallback:?\Closure} $_config + * @phpstan-property array{validate:bool, unlockedFields:array, unlockedActions:array, validationFailureCallback:?\Closure} $_config */ class FormProtectionComponent extends Component { diff --git a/src/Controller/ComponentRegistry.php b/src/Controller/ComponentRegistry.php index 365cf86019a..e6e4ac9091c 100644 --- a/src/Controller/ComponentRegistry.php +++ b/src/Controller/ComponentRegistry.php @@ -181,7 +181,6 @@ protected function _create(object|string $class, string $alias, array $config): * Get container instance. * * @return \Cake\Core\ContainerInterface - * @psalm-suppress OverriddenMethodAccess */ protected function getContainer(): ContainerInterface { diff --git a/src/Controller/Controller.php b/src/Controller/Controller.php index 8acc2b52dfc..904ddc509f0 100644 --- a/src/Controller/Controller.php +++ b/src/Controller/Controller.php @@ -176,7 +176,7 @@ class Controller implements EventListenerInterface, EventDispatcherInterface * Middlewares list. * * @var array - * @psalm-var array + * @phpstan-var array */ protected array $middlewares = []; @@ -529,7 +529,7 @@ public function invokeAction(Closure $action, array $args): void * - `except`: (array|string) Run the middleware for all actions except the specified ones. * @return void * @since 4.3.0 - * @psalm-param array{only?: array|string, except?: array|string} $options + * @phpstan-param array{only?: array|string, except?: array|string} $options */ public function middleware(MiddlewareInterface|Closure|string $middleware, array $options = []): void { diff --git a/src/Core/ObjectRegistry.php b/src/Core/ObjectRegistry.php index 6cd472edd68..36296af5bf6 100644 --- a/src/Core/ObjectRegistry.php +++ b/src/Core/ObjectRegistry.php @@ -48,7 +48,7 @@ abstract class ObjectRegistry implements Countable, IteratorAggregate * Map of loaded objects. * * @var array - * @psalm-var array + * @phpstan-var array */ protected array $_loaded = []; @@ -75,7 +75,7 @@ abstract class ObjectRegistry implements Countable, IteratorAggregate * @param string $name The name/class of the object to load. * @param array $config Additional settings to use when loading the object. * @return object - * @psalm-return TObject + * @phpstan-return TObject * @throws \Exception If the class cannot be found. */ public function load(string $name, array $config = []): object @@ -174,7 +174,7 @@ protected function _checkDuplicate(string $name, array $config): void * * @param string $class The class to resolve. * @return class-string|null The resolved name or null for failure. - * @psalm-return class-string|null + * @phpstan-return class-string|null */ abstract protected function _resolveClassName(string $class): ?string; @@ -198,8 +198,8 @@ abstract protected function _throwMissingClassError(string $class, ?string $plug * @param string $alias The alias of the object. * @param array $config The Configuration settings for construction * @return object - * @psalm-param TObject|class-string $class - * @psalm-return TObject + * @phpstan-param TObject|class-string $class + * @phpstan-return TObject */ abstract protected function _create(object|string $class, string $alias, array $config): object; @@ -230,7 +230,7 @@ public function has(string $name): bool * @param string $name Name of object. * @return object Object instance. * @throws \Cake\Core\Exception\CakeException If not loaded or found. - * @psalm-return TObject + * @phpstan-return TObject */ public function get(string $name): object { @@ -246,7 +246,7 @@ public function get(string $name): object * * @param string $name Name of property to read * @return object|null - * @psalm-return TObject|null + * @phpstan-return TObject|null */ public function __get(string $name): ?object { @@ -269,7 +269,7 @@ public function __isset(string $name): bool * * @param string $name Name of a property to set. * @param object $object Object to set. - * @psalm-param TObject $object + * @phpstan-param TObject $object * @return void */ public function __set(string $name, object $object): void @@ -340,7 +340,7 @@ public function reset() * @param string $name The name of the object to set in the registry. * @param object $object instance to store in the registry * @return $this - * @psalm-param TObject $object + * @phpstan-param TObject $object */ public function set(string $name, object $object) { @@ -383,7 +383,7 @@ public function unload(string $name) * Returns an array iterator. * * @return \Traversable - * @psalm-return \Traversable + * @phpstan-return \Traversable */ public function getIterator(): Traversable { diff --git a/src/Core/PluginCollection.php b/src/Core/PluginCollection.php index 16ded90551b..e97024ad042 100644 --- a/src/Core/PluginCollection.php +++ b/src/Core/PluginCollection.php @@ -251,7 +251,7 @@ public function get(string $name): PluginInterface * @return \Cake\Core\PluginInterface * @throws \Cake\Core\Exception\MissingPluginException When plugin instance could not be created. * @throws \InvalidArgumentException When class name cannot be found. - * @psalm-param class-string<\Cake\Core\PluginInterface>|string $name + * @phpstan-param class-string<\Cake\Core\PluginInterface>|string $name */ public function create(string $name, array $config = []): PluginInterface { diff --git a/src/Core/StaticConfigTrait.php b/src/Core/StaticConfigTrait.php index c4e06c421a9..12843c9816a 100644 --- a/src/Core/StaticConfigTrait.php +++ b/src/Core/StaticConfigTrait.php @@ -314,7 +314,7 @@ public static function parseDsn(string $dsn): array * * @param array $map Additions/edits to the class map to apply. * @return void - * @psalm-param array $map + * @phpstan-param array $map */ public static function setDsnClassMap(array $map): void { diff --git a/src/Core/TestSuite/ContainerStubTrait.php b/src/Core/TestSuite/ContainerStubTrait.php index 635764eb0b6..0892a1b8f69 100644 --- a/src/Core/TestSuite/ContainerStubTrait.php +++ b/src/Core/TestSuite/ContainerStubTrait.php @@ -39,7 +39,7 @@ trait ContainerStubTrait /** * The customized application class name. * - * @psalm-var class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface>|null + * @phpstan-var class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface>|null * @var string|null */ protected ?string $_appClass = null; @@ -64,7 +64,7 @@ trait ContainerStubTrait * @param string $class The application class name. * @param array|null $constructorArgs The constructor arguments for your application class. * @return void - * @psalm-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class + * @phpstan-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class */ public function configApplication(string $class, ?array $constructorArgs): void { diff --git a/src/Core/functions.php b/src/Core/functions.php index 7b30e90986f..abc6febe45b 100644 --- a/src/Core/functions.php +++ b/src/Core/functions.php @@ -143,7 +143,7 @@ function h(mixed $text, bool $double = true, ?string $charset = null): mixed * @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null. * @return array Array with 2 indexes. 0 => plugin name, 1 => class name. * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pluginSplit - * @psalm-return array{string|null, string} + * @phpstan-return array{string|null, string} */ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array { @@ -153,7 +153,7 @@ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = nu $parts[0] .= '.'; } - /** @psalm-var array{string, string} */ + /** @phpstan-var array{string, string} */ return $parts; } diff --git a/src/Core/functions_global.php b/src/Core/functions_global.php index d39b30dc556..022593cbdae 100644 --- a/src/Core/functions_global.php +++ b/src/Core/functions_global.php @@ -84,7 +84,7 @@ function h(mixed $text, bool $double = true, ?string $charset = null): mixed * @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null. * @return array Array with 2 indexes. 0 => plugin name, 1 => class name. * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pluginSplit - * @psalm-return array{string|null, string} + * @phpstan-return array{string|null, string} */ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array { diff --git a/src/Database/Connection.php b/src/Database/Connection.php index 50d1f0e7833..c39e20d5538 100644 --- a/src/Database/Connection.php +++ b/src/Database/Connection.php @@ -132,7 +132,7 @@ public function __construct(array $config) * * @param array $config Connection config * @return array - * @psalm-return array{read: \Cake\Database\Driver, write: \Cake\Database\Driver} + * @phpstan-return array{read: \Cake\Database\Driver, write: \Cake\Database\Driver} */ protected function createDrivers(array $config): array { @@ -792,11 +792,9 @@ public function __debugInfo(): array $config = $replace + $this->_config; if (isset($config['read'])) { - /** @psalm-suppress PossiblyInvalidArgument */ $config['read'] = array_intersect_key($secrets, $config['read']) + $config['read']; } if (isset($config['write'])) { - /** @psalm-suppress PossiblyInvalidArgument */ $config['write'] = array_intersect_key($secrets, $config['write']) + $config['write']; } diff --git a/src/Database/Driver.php b/src/Database/Driver.php index 4787339e8f1..f7b44e2613d 100644 --- a/src/Database/Driver.php +++ b/src/Database/Driver.php @@ -668,8 +668,8 @@ protected function _updateQueryTranslator(UpdateQuery $query): UpdateQuery * @throws \Cake\Database\Exception\DatabaseException In case the processed query contains any joins, as removing * aliases from the conditions can break references to the joined tables. * @template T of \Cake\Database\Query\UpdateQuery|\Cake\Database\Query\DeleteQuery - * @psalm-param T $query - * @psalm-return T + * @phpstan-param T $query + * @phpstan-return T */ protected function _removeAliasesFromConditions(UpdateQuery|DeleteQuery $query): UpdateQuery|DeleteQuery { @@ -799,7 +799,6 @@ public function schemaValue(mixed $value): string if (is_float($value)) { return str_replace(',', '.', (string)$value); } - /** @psalm-suppress InvalidArgument */ if ( ( is_int($value) || diff --git a/src/Database/Expression/CaseExpressionTrait.php b/src/Database/Expression/CaseExpressionTrait.php index 44473439c50..a2c2425bfae 100644 --- a/src/Database/Expression/CaseExpressionTrait.php +++ b/src/Database/Expression/CaseExpressionTrait.php @@ -41,7 +41,6 @@ protected function inferType(mixed $value): ?string { $type = null; - /** @psalm-suppress RedundantCondition */ if (is_string($value)) { $type = 'string'; } elseif (is_int($value)) { diff --git a/src/Database/Expression/FunctionExpression.php b/src/Database/Expression/FunctionExpression.php index 353a217ce04..27d1f3e93b3 100644 --- a/src/Database/Expression/FunctionExpression.php +++ b/src/Database/Expression/FunctionExpression.php @@ -106,7 +106,6 @@ public function getName(): string * @param bool $prepend Whether to prepend or append to the list of arguments * @see \Cake\Database\Expression\FunctionExpression::__construct() for more details. * @return $this - * @psalm-suppress MoreSpecificImplementedParamType */ public function add(ExpressionInterface|array|string $conditions, array $types = [], bool $prepend = false) { diff --git a/src/Database/Expression/QueryExpression.php b/src/Database/Expression/QueryExpression.php index 1c2eaae3a65..7a12df8c45e 100644 --- a/src/Database/Expression/QueryExpression.php +++ b/src/Database/Expression/QueryExpression.php @@ -715,7 +715,6 @@ protected function _parseCondition(string $condition, mixed $value): ExpressionI $typeMultiple = true; } - /** @psalm-suppress RedundantCondition */ if ($typeMultiple) { $value = $value instanceof ExpressionInterface ? $value : (array)$value; } diff --git a/src/Database/Expression/TupleComparison.php b/src/Database/Expression/TupleComparison.php index 20e8dc2057e..39bbc0671a1 100644 --- a/src/Database/Expression/TupleComparison.php +++ b/src/Database/Expression/TupleComparison.php @@ -31,7 +31,6 @@ class TupleComparison extends ComparisonExpression * The type to be used for casting the value to a database representation * * @var array - * @psalm-suppress NonInvariantDocblockPropertyType */ protected array $types; diff --git a/src/Database/Expression/WhenThenExpression.php b/src/Database/Expression/WhenThenExpression.php index a87343fae53..4cfc3f1363c 100644 --- a/src/Database/Expression/WhenThenExpression.php +++ b/src/Database/Expression/WhenThenExpression.php @@ -182,7 +182,6 @@ public function when(object|array|string|float|int|bool $when, array|string|null */ public function then(mixed $result, ?string $type = null) { - /** @psalm-suppress DocblockTypeContradiction */ if ( $result !== null && !is_scalar($result) && diff --git a/src/Database/Expression/WindowInterface.php b/src/Database/Expression/WindowInterface.php index f0f060bf53e..f96441e71b4 100644 --- a/src/Database/Expression/WindowInterface.php +++ b/src/Database/Expression/WindowInterface.php @@ -140,9 +140,9 @@ public function groups(?int $start, ?int $end = 0); * @param string $endDirection Frame end direction * @return $this * @throws \InvalidArgumentException WHen offsets are negative. - * @psalm-param self::RANGE|self::ROWS|self::GROUPS $type - * @psalm-param self::PRECEDING|self::FOLLOWING $startDirection - * @psalm-param self::PRECEDING|self::FOLLOWING $endDirection + * @phpstan-param self::RANGE|self::ROWS|self::GROUPS $type + * @phpstan-param self::PRECEDING|self::FOLLOWING $startDirection + * @phpstan-param self::PRECEDING|self::FOLLOWING $endDirection */ public function frame( string $type, diff --git a/src/Database/Query.php b/src/Database/Query.php index 618480ba492..e091db0d9a5 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -1802,7 +1802,6 @@ public function __clone() if (is_array($piece)) { foreach ($piece as $j => $value) { if ($value instanceof ExpressionInterface) { - /** @psalm-suppress PossiblyUndefinedMethod */ $this->_parts[$name][$i][$j] = clone $value; } } diff --git a/src/Database/Schema/SqliteSchemaDialect.php b/src/Database/Schema/SqliteSchemaDialect.php index 9ae13f120cc..5c77ea8a379 100644 --- a/src/Database/Schema/SqliteSchemaDialect.php +++ b/src/Database/Schema/SqliteSchemaDialect.php @@ -225,7 +225,6 @@ public function convertColumnDescription(TableSchema $schema, array $row): void // SQLite does not support autoincrement on composite keys. if ($row['pk'] && !empty($primary)) { $existingColumn = $primary['columns'][0]; - /** @psalm-suppress PossiblyNullOperand */ $schema->addColumn($existingColumn, ['autoIncrement' => null] + $schema->getColumn($existingColumn)); } diff --git a/src/Database/Statement/Statement.php b/src/Database/Statement/Statement.php index c58265c3796..21912c78875 100644 --- a/src/Database/Statement/Statement.php +++ b/src/Database/Statement/Statement.php @@ -75,10 +75,8 @@ public function bind(array $params, array $types): void foreach ($params as $index => $value) { $type = $types[$index] ?? null; if ($anonymousParams) { - /** @psalm-suppress InvalidOperand */ $index += $offset; } - /** @psalm-suppress PossiblyInvalidArgument */ $this->bindValue($index, $value, $type); } } @@ -104,7 +102,7 @@ public function bindValue(string|int $column, mixed $value, string|int|null $typ * @param mixed $value The value to cast. * @param \Cake\Database\TypeInterface|string|int $type The type name or type instance to use. * @return array List containing converted value and internal type. - * @psalm-return array{0:mixed, 1:int} + * @phpstan-return array{0:mixed, 1:int} */ protected function cast(mixed $value, TypeInterface|string|int $type = 'string'): array { diff --git a/src/Database/TypeFactory.php b/src/Database/TypeFactory.php index 881f17a4da9..ae373cc15da 100644 --- a/src/Database/TypeFactory.php +++ b/src/Database/TypeFactory.php @@ -29,7 +29,7 @@ class TypeFactory * representing the class that will do actual type conversions. * * @var array - * @psalm-var array> + * @phpstan-var array> */ protected static array $_types = [ 'tinyinteger' => Type\IntegerType::class, @@ -118,7 +118,7 @@ public static function set(string $name, TypeInterface $instance): void * @param string $type Name of type to map. * @param string $className The classname to register. * @return void - * @psalm-param class-string<\Cake\Database\TypeInterface> $className + * @phpstan-param class-string<\Cake\Database\TypeInterface> $className */ public static function map(string $type, string $className): void { @@ -131,7 +131,7 @@ public static function map(string $type, string $className): void * * @param array $map List of types to be mapped. * @return void - * @psalm-param array> $map + * @phpstan-param array> $map */ public static function setMap(array $map): void { diff --git a/src/Datasource/ConnectionManager.php b/src/Datasource/ConnectionManager.php index 1810c78aa59..820d02fc2f3 100644 --- a/src/Datasource/ConnectionManager.php +++ b/src/Datasource/ConnectionManager.php @@ -52,7 +52,7 @@ class ConnectionManager * An array mapping url schemes to fully qualified driver class names * * @var array - * @psalm-var array + * @phpstan-var array */ protected static array $_dsnClassMap = [ 'mysql' => Mysql::class, diff --git a/src/Datasource/EntityTrait.php b/src/Datasource/EntityTrait.php index 2b72ec616d2..4cab7fb5919 100644 --- a/src/Datasource/EntityTrait.php +++ b/src/Datasource/EntityTrait.php @@ -302,7 +302,6 @@ public function patch(array $values, array $options = []) } foreach ($values as $name => $value) { - /** @psalm-suppress RedundantCastGivenDocblockType */ $name = (string)$name; if ($name === '') { throw new InvalidArgumentException('Cannot set an empty field'); diff --git a/src/Datasource/ModelAwareTrait.php b/src/Datasource/ModelAwareTrait.php index 0f10ce5b548..53033715635 100644 --- a/src/Datasource/ModelAwareTrait.php +++ b/src/Datasource/ModelAwareTrait.php @@ -104,7 +104,6 @@ public function fetchModel(?string $modelClass = null, ?string $modelType = null [, $alias] = pluginSplit($modelClass, true); } else { $options['className'] = $modelClass; - /** @psalm-suppress PossiblyFalseOperand */ $alias = substr( $modelClass, strrpos($modelClass, '\\') + 1, diff --git a/src/Datasource/RulesAwareTrait.php b/src/Datasource/RulesAwareTrait.php index d8235fa943d..8f74ebdeeab 100644 --- a/src/Datasource/RulesAwareTrait.php +++ b/src/Datasource/RulesAwareTrait.php @@ -101,7 +101,6 @@ public function rulesChecker(): RulesChecker /** @var class-string<\Cake\Datasource\RulesChecker> $class */ $class = defined('static::RULES_CLASS') ? static::RULES_CLASS : RulesChecker::class; /** - * @psalm-suppress ArgumentTypeCoercion * @phpstan-ignore-next-line */ $this->_rulesChecker = $this->buildRules(new $class(['repository' => $this])); diff --git a/src/Error/Debugger.php b/src/Error/Debugger.php index 3d06c65c3fd..68d0568c579 100644 --- a/src/Error/Debugger.php +++ b/src/Error/Debugger.php @@ -431,7 +431,6 @@ public static function formatTrace(Throwable|array $backtrace, array $options = } /** - * @psalm-suppress InvalidArgument * @phpstan-ignore-next-line */ return implode("\n", $back); diff --git a/src/Error/functions.php b/src/Error/functions.php index b214db82b2c..56bb966ecde 100644 --- a/src/Error/functions.php +++ b/src/Error/functions.php @@ -99,7 +99,6 @@ function dd(mixed $var, ?bool $showHtml = null): void } $trace = Debugger::trace(['start' => 0, 'depth' => 2, 'format' => 'array']); - /** @psalm-suppress PossiblyInvalidArrayOffset */ $location = [ 'line' => $trace[0]['line'], 'file' => $trace[0]['file'], diff --git a/src/Error/functions_global.php b/src/Error/functions_global.php index c3091d062a1..aecc1068aa9 100644 --- a/src/Error/functions_global.php +++ b/src/Error/functions_global.php @@ -102,7 +102,6 @@ function dd(mixed $var, ?bool $showHtml = null): void } $trace = Debugger::trace(['start' => 0, 'depth' => 2, 'format' => 'array']); - /** @psalm-suppress PossiblyInvalidArrayOffset */ $location = [ 'line' => $trace[0]['line'], 'file' => $trace[0]['file'], diff --git a/src/Event/Event.php b/src/Event/Event.php index 15a0fdc9a64..85a1144970a 100644 --- a/src/Event/Event.php +++ b/src/Event/Event.php @@ -37,7 +37,7 @@ class Event implements EventInterface * The object this event applies to (usually the same object that generates the event) * * @var object|null - * @psalm-var TSubject|null + * @phpstan-var TSubject|null */ protected ?object $_subject = null; @@ -79,7 +79,7 @@ class Event implements EventInterface * (usually the object that is generating the event). * @param array $data any value you wish to be transported * with this event to it can be read by listeners. - * @psalm-param TSubject|null $subject + * @phpstan-param TSubject|null $subject */ public function __construct(string $name, ?object $subject = null, array $data = []) { @@ -105,7 +105,7 @@ public function getName(): string * * @return object * @throws \Cake\Core\Exception\CakeException - * @psalm-return TSubject + * @phpstan-return TSubject */ public function getSubject(): object { diff --git a/src/Event/EventInterface.php b/src/Event/EventInterface.php index 676700a5062..97673d78c67 100644 --- a/src/Event/EventInterface.php +++ b/src/Event/EventInterface.php @@ -36,7 +36,7 @@ public function getName(): string; * Returns the subject of this event. * * @return object - * @psalm-return TSubject + * @phpstan-return TSubject */ public function getSubject(): object; diff --git a/src/Form/Form.php b/src/Form/Form.php index 7551ddcf304..ff492c19336 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -73,7 +73,7 @@ class Form implements EventListenerInterface, EventDispatcherInterface, Validato * Schema class. * * @var string - * @psalm-var class-string<\Cake\Form\Schema> + * @phpstan-var class-string<\Cake\Form\Schema> */ protected string $_schemaClass = Schema::class; diff --git a/src/Form/FormProtector.php b/src/Form/FormProtector.php index 716e901c3e0..1425ac203df 100644 --- a/src/Form/FormProtector.php +++ b/src/Form/FormProtector.php @@ -271,7 +271,7 @@ protected function extractToken(mixed $formData): ?string * * @param array $formData Form data. * @return array - * @psalm-return array{fields: array, unlockedFields: array} + * @phpstan-return array{fields: array, unlockedFields: array} */ protected function extractHashParts(array $formData): array { @@ -381,7 +381,7 @@ protected function sortedUnlockedFields(array $formData): array * @param string $url Form URL. * @param string $sessionId Session ID. * @return array The token data. - * @psalm-return array{fields: string, unlocked: string, debug: string} + * @phpstan-return array{fields: string, unlocked: string, debug: string} */ public function buildTokenData(string $url = '', string $sessionId = ''): array { diff --git a/src/Http/Client.php b/src/Http/Client.php index 990d04e1155..2fc653448f4 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -693,7 +693,7 @@ protected function _createRequest(string $method, string $url, mixed $data, arra * @param string $type short type alias or full mimetype. * @return array Headers to set on the request. * @throws \Cake\Core\Exception\CakeException When an unknown type alias is used. - * @psalm-return array + * @phpstan-return array */ protected function _typeHeaders(string $type): array { diff --git a/src/Http/Client/Adapter/Stream.php b/src/Http/Client/Adapter/Stream.php index 00010e36330..79139c829bd 100644 --- a/src/Http/Client/Adapter/Stream.php +++ b/src/Http/Client/Adapter/Stream.php @@ -268,7 +268,6 @@ protected function _send(RequestInterface $request): array } $meta = stream_get_meta_data($this->_stream); - /** @psalm-suppress InvalidPropertyAssignmentValue */ fclose($this->_stream); if ($timedOut) { diff --git a/src/Http/ControllerFactoryInterface.php b/src/Http/ControllerFactoryInterface.php index 3db7c473373..d7e34fb0bd7 100644 --- a/src/Http/ControllerFactoryInterface.php +++ b/src/Http/ControllerFactoryInterface.php @@ -32,7 +32,7 @@ interface ControllerFactoryInterface * @param \Psr\Http\Message\ServerRequestInterface $request The request to build a controller for. * @return mixed * @throws \Cake\Http\Exception\MissingControllerException - * @psalm-return TController + * @phpstan-return TController */ public function create(ServerRequestInterface $request): mixed; @@ -41,7 +41,7 @@ public function create(ServerRequestInterface $request): mixed; * * @param mixed $controller The controller to invoke. * @return \Psr\Http\Message\ResponseInterface The response - * @psalm-param TController $controller + * @phpstan-param TController $controller */ public function invoke(mixed $controller): ResponseInterface; } diff --git a/src/Http/Cookie/Cookie.php b/src/Http/Cookie/Cookie.php index 89c39e0c5a9..0afba1384a2 100644 --- a/src/Http/Cookie/Cookie.php +++ b/src/Http/Cookie/Cookie.php @@ -247,7 +247,6 @@ protected static function dateTimeInstance(DateTimeInterface|string|int|null $ex if ($expires instanceof DateTimeInterface) { /** - * @psalm-suppress UndefinedInterfaceMethod * @phpstan-ignore-next-line */ return $expires->setTimezone(new DateTimeZone('GMT')); diff --git a/src/Http/MiddlewareQueue.php b/src/Http/MiddlewareQueue.php index 779ed00e0c5..e565fed18d6 100644 --- a/src/Http/MiddlewareQueue.php +++ b/src/Http/MiddlewareQueue.php @@ -179,7 +179,6 @@ public function insertBefore(string $class, MiddlewareInterface|Closure|string $ $found = false; $i = 0; foreach ($this->queue as $i => $object) { - /** @psalm-suppress ArgumentTypeCoercion */ if ( ( is_string($object) @@ -213,7 +212,6 @@ public function insertAfter(string $class, MiddlewareInterface|Closure|string $m $found = false; $i = 0; foreach ($this->queue as $i => $object) { - /** @psalm-suppress ArgumentTypeCoercion */ if ( ( is_string($object) diff --git a/src/Http/Response.php b/src/Http/Response.php index 96e9c1f065f..e4cd16f62d8 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -834,7 +834,6 @@ protected function _getUTCDate(DateTimeInterface|string|int|null $time = null): } /** - * @psalm-suppress UndefinedInterfaceMethod * @phpstan-ignore-next-line */ return $result->setTimezone(new DateTimeZone('UTC')); diff --git a/src/Http/Session.php b/src/Http/Session.php index fff5a0a6dc1..9aa46c52720 100644 --- a/src/Http/Session.php +++ b/src/Http/Session.php @@ -490,7 +490,6 @@ public function consume(string $name): mixed } $value = $this->read($name); if ($value !== null) { - /** @psalm-suppress InvalidScalarArgument */ $this->_overwrite($_SESSION, Hash::remove($_SESSION, $name)); } @@ -564,7 +563,6 @@ public function id(?string $id = null): string public function delete(string $name): void { if ($this->check($name)) { - /** @psalm-suppress InvalidScalarArgument */ $this->_overwrite($_SESSION, Hash::remove($_SESSION, $name)); } } diff --git a/src/Http/UriFactory.php b/src/Http/UriFactory.php index 263384e13e4..64d011e2938 100644 --- a/src/Http/UriFactory.php +++ b/src/Http/UriFactory.php @@ -45,7 +45,7 @@ public function createUri(string $uri = ''): UriInterface * @param array|null $server Array of server data to build the Uri from. * $_SERVER will be used if $server parameter is null. * @return array - * @psalm-return array{uri: \Psr\Http\Message\UriInterface, base: string, webroot: string} + * @phpstan-return array{uri: \Psr\Http\Message\UriInterface, base: string, webroot: string} */ public static function marshalUriAndBaseFromSapi(?array $server = null): array { @@ -118,7 +118,7 @@ protected static function updatePath(string $base, UriInterface $uri): UriInterf * @param \Psr\Http\Message\UriInterface $uri The Uri instance. * @param array $server The SERVER data to use. * @return array An array containing the base and webroot paths. - * @psalm-return array{base: string, webroot: string} + * @phpstan-return array{base: string, webroot: string} */ protected static function getBase(UriInterface $uri, array $server): array { diff --git a/src/I18n/Date.php b/src/I18n/Date.php index 1ac6deafab1..856c2435a0e 100644 --- a/src/I18n/Date.php +++ b/src/I18n/Date.php @@ -29,7 +29,7 @@ * * Adds handy methods and locale-aware formatting helpers. * - * @psalm-immutable + * @phpstan-immutable */ class Date extends ChronosDate implements JsonSerializable, Stringable { diff --git a/src/I18n/DateTime.php b/src/I18n/DateTime.php index b1af90c8662..b21e02d4b07 100644 --- a/src/I18n/DateTime.php +++ b/src/I18n/DateTime.php @@ -29,7 +29,7 @@ * Extends the built-in DateTime class to provide handy methods and locale-aware * formatting helpers. * - * @psalm-immutable + * @phpstan-immutable */ class DateTime extends Chronos implements JsonSerializable, Stringable { diff --git a/src/I18n/Parser/PoFileParser.php b/src/I18n/Parser/PoFileParser.php index a2a2a5ec4a0..9f9f33bd168 100644 --- a/src/I18n/Parser/PoFileParser.php +++ b/src/I18n/Parser/PoFileParser.php @@ -112,9 +112,6 @@ public function parse(string $resource): array case 2: assert(isset($stage[0])); assert(isset($stage[1])); - /** - * @psalm-suppress InvalidArrayOffset - */ $item[$stage[0]][$stage[1]] .= substr($line, 1, -1); break; diff --git a/src/I18n/RelativeTimeFormatter.php b/src/I18n/RelativeTimeFormatter.php index b3a2bb5927e..391cda743d3 100644 --- a/src/I18n/RelativeTimeFormatter.php +++ b/src/I18n/RelativeTimeFormatter.php @@ -415,7 +415,7 @@ public function dateAgoInWords(DateTime|Date $date, array $options = []): string * @param array $options The options provided by the user. * @param string $class The class name to use for defaults. * @return array Options with defaults applied. - * @psalm-param class-string<\Cake\I18n\Date>|class-string<\Cake\I18n\DateTime> $class + * @phpstan-param class-string<\Cake\I18n\Date>|class-string<\Cake\I18n\DateTime> $class */ protected function _options(array $options, string $class): array { diff --git a/src/I18n/Time.php b/src/I18n/Time.php index 5f97556bc12..aabed779022 100644 --- a/src/I18n/Time.php +++ b/src/I18n/Time.php @@ -28,7 +28,7 @@ * * Adds handy methods and locale-aware formatting helpers. * - * @psalm-immutable + * @phpstan-immutable */ class Time extends ChronosTime implements JsonSerializable, Stringable { diff --git a/src/Log/Engine/BaseLog.php b/src/Log/Engine/BaseLog.php index 13eb95043d3..19594c88f00 100644 --- a/src/Log/Engine/BaseLog.php +++ b/src/Log/Engine/BaseLog.php @@ -191,7 +191,6 @@ protected function interpolate(Stringable|string $message, array $context = []): $replacements['{' . $key . '}'] = sprintf('[unhandled value of type %s]', get_debug_type($value)); } - /** @psalm-suppress InvalidArgument */ return str_replace(array_keys($replacements), $replacements, $message); } } diff --git a/src/Log/Log.php b/src/Log/Log.php index 708c4d61591..67ddd46654c 100644 --- a/src/Log/Log.php +++ b/src/Log/Log.php @@ -116,7 +116,7 @@ class Log * An array mapping url schemes to fully qualified Log engine class names * * @var array - * @psalm-var array + * @phpstan-var array */ protected static array $_dsnClassMap = [ 'console' => Engine\ConsoleLog::class, @@ -351,7 +351,6 @@ public static function write(string|int $level, Stringable|string $message, arra } if (!in_array($level, static::$_levels, true)) { - /** @psalm-suppress PossiblyFalseArgument */ throw new InvalidArgumentException(sprintf('Invalid log level `%s`', $level)); } diff --git a/src/Mailer/AbstractTransport.php b/src/Mailer/AbstractTransport.php index 48c8a0b17a3..517e82143c4 100644 --- a/src/Mailer/AbstractTransport.php +++ b/src/Mailer/AbstractTransport.php @@ -38,7 +38,7 @@ abstract class AbstractTransport * * @param \Cake\Mailer\Message $message Email message. * @return array - * @psalm-return array{headers: string, message: string} + * @phpstan-return array{headers: string, message: string} */ abstract public function send(Message $message): array; diff --git a/src/Mailer/Mailer.php b/src/Mailer/Mailer.php index 56b5eb675eb..b227a793e05 100644 --- a/src/Mailer/Mailer.php +++ b/src/Mailer/Mailer.php @@ -153,7 +153,7 @@ class Mailer implements EventListenerInterface * Message class name. * * @var string - * @psalm-var class-string<\Cake\Mailer\Message> + * @phpstan-var class-string<\Cake\Mailer\Message> */ protected string $messageClass = Message::class; @@ -187,7 +187,7 @@ class Mailer implements EventListenerInterface * Mailer driver class map. * * @var array - * @psalm-var array + * @phpstan-var array */ protected static array $_dsnClassMap = []; @@ -317,7 +317,7 @@ public function setViewVars(array|string $key, mixed $value = null) * @return array * @throws \Cake\Mailer\Exception\MissingActionException * @throws \BadMethodCallException - * @psalm-return array{headers: string, message: string} + * @phpstan-return array{headers: string, message: string} */ public function send(?string $action = null, array $args = [], array $headers = []): array { @@ -373,7 +373,7 @@ public function render(string $content = '') * * @param string $content Content. * @return array - * @psalm-return array{headers: string, message: string} + * @phpstan-return array{headers: string, message: string} */ public function deliver(string $content = ''): array { @@ -553,7 +553,7 @@ public function reset() * * @param array $contents The content with 'headers' and 'message' keys. * @return void - * @psalm-param array{headers: string, message: string} $contents + * @phpstan-param array{headers: string, message: string} $contents */ protected function logDelivery(array $contents): void { diff --git a/src/Mailer/Renderer.php b/src/Mailer/Renderer.php index 27df78b54b2..dbb7e41dafa 100644 --- a/src/Mailer/Renderer.php +++ b/src/Mailer/Renderer.php @@ -51,8 +51,8 @@ public function __construct() * @param string $content The content. * @param array $types Content types to render. Valid array values are {@link Message::MESSAGE_HTML}, {@link Message::MESSAGE_TEXT}. * @return array The rendered content with "html" and/or "text" keys. - * @psalm-param array<\Cake\Mailer\Message::MESSAGE_HTML|\Cake\Mailer\Message::MESSAGE_TEXT> $types - * @psalm-return array{html?: string, text?: string} + * @phpstan-param array<\Cake\Mailer\Message::MESSAGE_HTML|\Cake\Mailer\Message::MESSAGE_TEXT> $types + * @phpstan-return array{html?: string, text?: string} */ public function render(string $content, array $types = []): array { diff --git a/src/Mailer/TransportFactory.php b/src/Mailer/TransportFactory.php index d2ef121999b..8bec1c39df0 100644 --- a/src/Mailer/TransportFactory.php +++ b/src/Mailer/TransportFactory.php @@ -37,7 +37,7 @@ class TransportFactory * An array mapping url schemes to fully qualified Transport class names * * @var array - * @psalm-var array + * @phpstan-var array */ protected static array $_dsnClassMap = [ 'debug' => Transport\DebugTransport::class, diff --git a/src/Network/Socket.php b/src/Network/Socket.php index 8bf79c6f612..d29a03270bb 100644 --- a/src/Network/Socket.php +++ b/src/Network/Socket.php @@ -144,7 +144,6 @@ public function connect(): bool } /** - * @psalm-suppress InvalidArgument * @phpstan-ignore-next-line */ set_error_handler($this->_connectionErrorHandler(...)); @@ -432,7 +431,6 @@ public function disconnect(): bool return true; } - /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->connected = !fclose($this->connection); if (!$this->connected) { diff --git a/src/ORM/Association/BelongsToMany.php b/src/ORM/Association/BelongsToMany.php index bd84c20d009..5dde8bd3a16 100644 --- a/src/ORM/Association/BelongsToMany.php +++ b/src/ORM/Association/BelongsToMany.php @@ -1266,7 +1266,6 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $property = $this->getProperty(); if ($inserts !== []) { - /** @psalm-suppress RedundantConditionGivenDocblockType */ $inserted = array_combine( array_keys($inserts), (array)$sourceEntity->get($property), diff --git a/src/ORM/Association/HasMany.php b/src/ORM/Association/HasMany.php index 7803f52f27b..2e53236e99b 100644 --- a/src/ORM/Association/HasMany.php +++ b/src/ORM/Association/HasMany.php @@ -390,7 +390,6 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, arr $conditions = [ 'OR' => (new Collection($targetEntities)) ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { - /** @psalm-suppress InvalidArgument,UnusedPsalmSuppress */ /** @var array $targetPrimaryKey */ return $entity->extract($targetPrimaryKey); }) diff --git a/src/ORM/AssociationCollection.php b/src/ORM/AssociationCollection.php index b91295a5593..0818746ba01 100644 --- a/src/ORM/AssociationCollection.php +++ b/src/ORM/AssociationCollection.php @@ -73,8 +73,8 @@ public function __construct(?LocatorInterface $tableLocator = null) * @return \Cake\ORM\Association The association object being added. * @throws \Cake\Core\Exception\CakeException If the alias is already added. * @template T of \Cake\ORM\Association - * @psalm-param T $association - * @psalm-return T + * @phpstan-param T $association + * @phpstan-return T */ public function add(string $alias, Association $association): Association { @@ -96,8 +96,8 @@ public function add(string $alias, Association $association): Association * @return \Cake\ORM\Association * @throws \InvalidArgumentException * @template T of \Cake\ORM\Association - * @psalm-param class-string $className - * @psalm-return T + * @phpstan-param class-string $className + * @phpstan-return T */ public function load(string $className, string $associated, array $options = []): Association { diff --git a/src/ORM/Behavior/TranslateBehavior.php b/src/ORM/Behavior/TranslateBehavior.php index bf32e2d625d..621119899ae 100644 --- a/src/ORM/Behavior/TranslateBehavior.php +++ b/src/ORM/Behavior/TranslateBehavior.php @@ -74,7 +74,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * Default strategy class name. * * @var string - * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @phpstan-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ protected static string $defaultStrategyClass = ShadowTableStrategy::class; @@ -139,7 +139,7 @@ public function initialize(array $config): void * @param string $class Class name. * @return void * @since 4.0.0 - * @psalm-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class + * @phpstan-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class */ public static function setDefaultStrategyClass(string $class): void { @@ -151,7 +151,7 @@ public static function setDefaultStrategyClass(string $class): void * * @return string * @since 4.0.0 - * @psalm-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @phpstan-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ public static function getDefaultStrategyClass(): string { diff --git a/src/ORM/Behavior/TreeBehavior.php b/src/ORM/Behavior/TreeBehavior.php index 48504f69842..c489af00007 100644 --- a/src/ORM/Behavior/TreeBehavior.php +++ b/src/ORM/Behavior/TreeBehavior.php @@ -918,8 +918,8 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark * @param \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery $query the Query to modify * @return \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery * @template T of \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery - * @psalm-param T $query - * @psalm-return T + * @phpstan-param T $query + * @phpstan-return T */ protected function _scope(SelectQuery|UpdateQuery|DeleteQuery $query): SelectQuery|UpdateQuery|DeleteQuery { diff --git a/src/ORM/BehaviorRegistry.php b/src/ORM/BehaviorRegistry.php index cca7bbcf24f..3984c0bfcc9 100644 --- a/src/ORM/BehaviorRegistry.php +++ b/src/ORM/BehaviorRegistry.php @@ -91,7 +91,7 @@ public function setTable(Table $table): void * * @param string $class Partial classname to resolve. * @return string|null Either the correct classname or null. - * @psalm-return class-string|null + * @phpstan-return class-string|null */ public static function className(string $class): ?string { diff --git a/src/ORM/EagerLoader.php b/src/ORM/EagerLoader.php index 636dbc6a316..18e93b9c6d9 100644 --- a/src/ORM/EagerLoader.php +++ b/src/ORM/EagerLoader.php @@ -375,7 +375,6 @@ protected function _reformatContain(array $associations, array $original): array } if (!is_array($options)) { - /** @psalm-suppress InvalidArrayOffset */ $options = [$options => []]; } diff --git a/src/ORM/Locator/LocatorInterface.php b/src/ORM/Locator/LocatorInterface.php index 81ad5a9caef..15d0ba2c484 100644 --- a/src/ORM/Locator/LocatorInterface.php +++ b/src/ORM/Locator/LocatorInterface.php @@ -62,7 +62,6 @@ public function get(string $alias, array $options = []): Table; * @param string $alias The alias to set. * @param \Cake\ORM\Table $repository The table to set. * @return \Cake\ORM\Table - * @psalm-suppress MoreSpecificImplementedParamType */ public function set(string $alias, RepositoryInterface $repository): Table; } diff --git a/src/ORM/Locator/TableLocator.php b/src/ORM/Locator/TableLocator.php index 14d6c9e7ed3..0f9a7f65893 100644 --- a/src/ORM/Locator/TableLocator.php +++ b/src/ORM/Locator/TableLocator.php @@ -51,7 +51,6 @@ class TableLocator extends AbstractLocator implements LocatorInterface * Instances that belong to the registry. * * @var array - * @psalm-suppress NonInvariantDocblockPropertyType */ protected array $instances = []; @@ -67,7 +66,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * Fallback class to use * * @var string - * @psalm-var class-string<\Cake\ORM\Table> + * @phpstan-var class-string<\Cake\ORM\Table> */ protected string $fallbackClassName = Table::class; @@ -126,7 +125,7 @@ public function allowFallbackClass(bool $allow) * * @param string $className Fallback class name * @return $this - * @psalm-param class-string<\Cake\ORM\Table> $className + * @phpstan-param class-string<\Cake\ORM\Table> $className */ public function setFallbackClassName(string $className) { @@ -325,7 +324,6 @@ protected function _create(array $options): Table * @param string $alias The alias to set. * @param \Cake\ORM\Table $repository The Table to set. * @return \Cake\ORM\Table - * @psalm-suppress MoreSpecificImplementedParamType */ public function set(string $alias, RepositoryInterface $repository): Table { diff --git a/src/ORM/Query/SelectQuery.php b/src/ORM/Query/SelectQuery.php index e276b2ed71a..26efc49bb7d 100644 --- a/src/ORM/Query/SelectQuery.php +++ b/src/ORM/Query/SelectQuery.php @@ -1673,13 +1673,11 @@ protected function _addDefaultSelectTypes(): void * @param string $finder The finder method to use. * @param mixed ...$args Arguments that match up to finder-specific parameters * @return static Returns a modified query. - * @psalm-suppress MoreSpecificReturnType */ public function find(string $finder, mixed ...$args): static { $table = $this->getRepository(); - /** @psalm-suppress LessSpecificReturnStatement */ return $table->callFinder($finder, $this, ...$args); } diff --git a/src/ORM/Table.php b/src/ORM/Table.php index dacc731f95c..36833d48c2b 100644 --- a/src/ORM/Table.php +++ b/src/ORM/Table.php @@ -263,7 +263,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * The name of the class that represent a single row for this table * * @var string|null - * @psalm-var class-string<\Cake\Datasource\EntityInterface>|null + * @phpstan-var class-string<\Cake\Datasource\EntityInterface>|null */ protected ?string $_entityClass = null; @@ -848,7 +848,6 @@ public function behaviors(): BehaviorRegistry * @template TName of key-of * @phpstan-param TName $name The behavior alias to get from the registry. * @phpstan-return TBehaviors[TName] - * @psalm-return \Cake\ORM\Behavior * @throws \InvalidArgumentException If the behavior does not exist. */ public function getBehavior(string $name): Behavior diff --git a/src/Routing/Asset.php b/src/Routing/Asset.php index 17c900efdb3..a76dcbe5dee 100644 --- a/src/Routing/Asset.php +++ b/src/Routing/Asset.php @@ -356,7 +356,7 @@ protected static function requestWebroot(): string * * @param string $name The name you want to plugin split. * @return array Array with 2 indexes. 0 => plugin name, 1 => filename. - * @psalm-return array{string|null, string} + * @phpstan-return array{string|null, string} */ protected static function pluginSplit(string $name): array { diff --git a/src/Routing/Route/Route.php b/src/Routing/Route/Route.php index c9d5cbaf6c5..82bec910682 100644 --- a/src/Routing/Route/Route.php +++ b/src/Routing/Route/Route.php @@ -501,7 +501,6 @@ public function parse(string $url, string $method): ?array } if (isset($route['_args_'])) { - /** @psalm-suppress PossiblyInvalidArgument */ $pass = $this->_parseArgs($route['_args_'], $route); $route['pass'] = array_merge($route['pass'], $pass); unset($route['_args_']); @@ -525,7 +524,6 @@ public function parse(string $url, string $method): ?array if (isset($this->options['pass'])) { $j = count($this->options['pass']); while ($j--) { - /** @psalm-suppress PossiblyInvalidArgument */ if (isset($route[$this->options['pass'][$j]])) { array_unshift($route['pass'], $route[$this->options['pass'][$j]]); } diff --git a/src/TestSuite/Constraint/Response/StatusCodeBase.php b/src/TestSuite/Constraint/Response/StatusCodeBase.php index 5fe0a75650f..2342368f3ae 100644 --- a/src/TestSuite/Constraint/Response/StatusCodeBase.php +++ b/src/TestSuite/Constraint/Response/StatusCodeBase.php @@ -32,7 +32,6 @@ abstract class StatusCodeBase extends ResponseBase * * @param array|int $other Array of min/max status codes, or a single code * @return bool - * @psalm-suppress MoreSpecificImplementedParamType * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool @@ -68,7 +67,6 @@ protected function statusCodeBetween(int $min, int $max): bool */ protected function failureDescription(mixed $other): string { - /** @psalm-suppress InternalMethod */ return $this->toString(); } } diff --git a/src/TestSuite/Constraint/Session/FlashParamEquals.php b/src/TestSuite/Constraint/Session/FlashParamEquals.php index 92159b40ff5..2e89dfcff9b 100644 --- a/src/TestSuite/Constraint/Session/FlashParamEquals.php +++ b/src/TestSuite/Constraint/Session/FlashParamEquals.php @@ -80,10 +80,8 @@ public function matches(mixed $other): bool // Server::run calls Session::close at the end of the request. // Which means, that we cannot use Session object here to access the session data. // Call to Session::read will start new session (and will erase the data). - /** @psalm-suppress InvalidScalarArgument */ $messages = (array)Hash::get($_SESSION, 'Flash.' . $this->key); if ($this->at) { - /** @psalm-suppress InvalidScalarArgument */ $messages = [Hash::get($_SESSION, 'Flash.' . $this->key . '.' . $this->at)]; } diff --git a/src/TestSuite/Constraint/Session/SessionEquals.php b/src/TestSuite/Constraint/Session/SessionEquals.php index 0c289403a00..eb4ddbf364d 100644 --- a/src/TestSuite/Constraint/Session/SessionEquals.php +++ b/src/TestSuite/Constraint/Session/SessionEquals.php @@ -51,7 +51,6 @@ public function matches(mixed $other): bool // Server::run calls Session::close at the end of the request. // Which means, that we cannot use Session object here to access the session data. // Call to Session::read will start new session (and will erase the data). - /** @psalm-suppress InvalidScalarArgument */ return Hash::get($_SESSION, $this->path) === $other; } diff --git a/src/TestSuite/Constraint/Session/SessionHasKey.php b/src/TestSuite/Constraint/Session/SessionHasKey.php index f085d201363..7b81d049f47 100644 --- a/src/TestSuite/Constraint/Session/SessionHasKey.php +++ b/src/TestSuite/Constraint/Session/SessionHasKey.php @@ -51,7 +51,6 @@ public function matches(mixed $other): bool // Server::run calls Session::close at the end of the request. // Which means, that we cannot use Session object here to access the session data. // Call to Session::read will start new session (and will erase the data). - /** @psalm-suppress InvalidScalarArgument */ return Hash::check($_SESSION, $this->path); } diff --git a/src/TestSuite/Fixture/TestFixture.php b/src/TestSuite/Fixture/TestFixture.php index 55945b8d039..c74033b9f39 100644 --- a/src/TestSuite/Fixture/TestFixture.php +++ b/src/TestSuite/Fixture/TestFixture.php @@ -60,7 +60,6 @@ class TestFixture implements FixtureInterface * The schema for this fixture. * * @var \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface - * @psalm-suppress PropertyNotSetInConstructor */ protected TableSchemaInterface&SqlGeneratorInterface $_schema; diff --git a/src/TestSuite/IntegrationTestTrait.php b/src/TestSuite/IntegrationTestTrait.php index 078e12271df..65028fc0cd4 100644 --- a/src/TestSuite/IntegrationTestTrait.php +++ b/src/TestSuite/IntegrationTestTrait.php @@ -201,7 +201,6 @@ trait IntegrationTestTrait * Clears the state used for requests. * * @return void - * @psalm-suppress PossiblyNullPropertyAssignmentValue */ #[After] public function cleanup(): void @@ -1511,7 +1510,6 @@ protected function extractExceptionMessage(Exception $exception): string */ protected function getSession(): TestSession { - /** @psalm-suppress InvalidScalarArgument */ return new TestSession($_SESSION); } diff --git a/src/TestSuite/TestCase.php b/src/TestSuite/TestCase.php index d71489c5162..9bb282c120b 100644 --- a/src/TestSuite/TestCase.php +++ b/src/TestSuite/TestCase.php @@ -318,9 +318,6 @@ public function loadPlugins(array $plugins = []): BaseApplication { $this->appPluginsToLoad = $plugins; - /** - * @psalm-suppress MissingTemplateParam - */ $app = new class ('') extends BaseApplication { /** diff --git a/src/Utility/Filesystem.php b/src/Utility/Filesystem.php index 8d8f6cb7e2e..fa51ad3d610 100644 --- a/src/Utility/Filesystem.php +++ b/src/Utility/Filesystem.php @@ -58,7 +58,6 @@ public function find(string $path, Closure|string|null $filter = null, ?int $fla $flags ??= FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS; - /** @psalm-suppress ArgumentTypeCoercion */ $directory = new FilesystemIterator($path, $flags); if ($filter === null) { @@ -83,10 +82,8 @@ public function findRecursive(string $path, Closure|string|null $filter = null, $flags ??= FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS; - /** @psalm-suppress ArgumentTypeCoercion */ $directory = new RecursiveDirectoryIterator($path, $flags); - /** @psalm-suppress InvalidArgument */ $dirFilter = new RecursiveCallbackFilterIterator( $directory, function (SplFileInfo $current) { diff --git a/src/Utility/Hash.php b/src/Utility/Hash.php index cb07e66474b..45c3b796539 100644 --- a/src/Utility/Hash.php +++ b/src/Utility/Hash.php @@ -115,7 +115,7 @@ public static function get(ArrayAccess|array $data, array|string|int|null $path, * @return \ArrayAccess|array An array of the extracted values. Returns an empty array * if there are no matches. * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::extract - * @psalm-return ($path is non-empty-string ? array : \ArrayAccess|array) + * @phpstan-return ($path is non-empty-string ? array : \ArrayAccess|array) */ public static function extract(ArrayAccess|array $data, string $path): ArrayAccess|array { @@ -294,7 +294,7 @@ protected static function _matches(ArrayAccess|array $data, string $selector): b * @param string $path The path to insert at. * @param mixed $values The values to insert. * @return \ArrayAccess|array The data with $values inserted. - * @psalm-return (T is array ? array : \ArrayAccess) + * @phpstan-return (T is array ? array : \ArrayAccess) * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::insert */ public static function insert(ArrayAccess|array $data, string $path, mixed $values = null): ArrayAccess|array @@ -398,7 +398,7 @@ protected static function _simpleOp( * @param T $data The data to operate on * @param string $path A path expression to use to remove. * @return \ArrayAccess|array The modified array. - * @psalm-return (T is array ? array : \ArrayAccess) + * @phpstan-return (T is array ? array : \ArrayAccess) * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::remove */ public static function remove(ArrayAccess|array $data, string $path): ArrayAccess|array @@ -555,7 +555,7 @@ public static function combine( * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::format * @see sprintf() * @see \Cake\Utility\Hash::extract() - * @psalm-return ($paths is non-empty-array ? array : null) + * @phpstan-return ($paths is non-empty-array ? array : null) */ public static function format(array $data, array $paths, string $format): ?array { @@ -1216,7 +1216,7 @@ public static function normalize(array $data, bool $assoc = true, mixed $default * @see \Cake\Utility\Hash::extract() * @throws \InvalidArgumentException When providing invalid data. * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::nest - * @psalm-param array{idPath?: string, parentPath?: string, children?: string, root?: string|null} $options + * @phpstan-param array{idPath?: string, parentPath?: string, children?: string, root?: string|null} $options */ public static function nest(array $data, array $options = []): array { diff --git a/src/Utility/Xml.php b/src/Utility/Xml.php index f44c98461fb..f3e0962f13d 100644 --- a/src/Utility/Xml.php +++ b/src/Utility/Xml.php @@ -390,7 +390,7 @@ protected static function _fromArray( * * @param array $data Array with information to create children * @return void - * @psalm-param {dom: \DOMDocument, node: \DOMDocument|\DOMElement, key: string, format: string, ?value: mixed} $data + * @phpstan-param array{dom: \DOMDocument, node: \DOMNode, key: string, format: string, value?: mixed} $data */ protected static function _createChild(array $data): void { @@ -401,9 +401,7 @@ protected static function _createChild(array $data): void $key = $data['key']; $format = $data['format']; $value = $data['value']; - /** @var \DOMDocument $dom */ $dom = $data['dom']; - /** @var \DOMNode $node */ $node = $data['node']; $childNS = null; $childValue = null; diff --git a/src/Validation/RulesProvider.php b/src/Validation/RulesProvider.php index 72dc8e454b8..077b7cc77f0 100644 --- a/src/Validation/RulesProvider.php +++ b/src/Validation/RulesProvider.php @@ -48,7 +48,7 @@ class RulesProvider * * @param object|string $class the default class to proxy * @throws \ReflectionException - * @psalm-param object|class-string $class + * @phpstan-param object|class-string $class */ public function __construct(object|string $class = Validation::class) { diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index d2204942d18..7d5930ac479 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -140,7 +140,7 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable * used for validation * * @var array - * @psalm-var array + * @phpstan-var array */ protected array $_providers = []; @@ -148,7 +148,7 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable * An associative array of objects or classes used as a default provider list * * @var array - * @psalm-var array + * @phpstan-var array */ protected static array $_defaultProviders = []; @@ -306,7 +306,7 @@ public function hasField(string $name): bool * * @param string $name The name under which the provider should be set. * @param object|string $object Provider object or class name. - * @psalm-param object|class-string $object + * @phpstan-param object|class-string $object * @return $this */ public function setProvider(string $name, object|string $object) @@ -343,7 +343,7 @@ public static function getDefaultProvider(string $name): object|string|null * * @param string $name The name under which the provider should be set. * @param object|string $object Provider object or class name. - * @psalm-param object|class-string $object + * @phpstan-param object|class-string $object * @return void */ public static function addDefaultProvider(string $name, object|string $object): void diff --git a/src/View/Cell.php b/src/View/Cell.php index 82abe4299d0..a01897bcc51 100644 --- a/src/View/Cell.php +++ b/src/View/Cell.php @@ -190,7 +190,6 @@ public function render(?string $template = null): string $className = static::class; $namePrefix = '\View\Cell\\'; - /** @psalm-suppress PossiblyFalseOperand */ $name = substr($className, strpos($className, $namePrefix) + strlen($namePrefix)); $name = substr($name, 0, -4); if (!$builder->getTemplatePath()) { diff --git a/src/View/Exception/MissingCellTemplateException.php b/src/View/Exception/MissingCellTemplateException.php index 311ec196e41..ece31e84204 100644 --- a/src/View/Exception/MissingCellTemplateException.php +++ b/src/View/Exception/MissingCellTemplateException.php @@ -56,7 +56,7 @@ public function __construct( * Get the passed in attributes * * @return array - * @psalm-return array{name: string, file: string, paths: array} + * @phpstan-return array{name: string, file: string, paths: array} */ public function getAttributes(): array { diff --git a/src/View/Exception/MissingTemplateException.php b/src/View/Exception/MissingTemplateException.php index 1e58061f66e..7b7fd8dc2b1 100644 --- a/src/View/Exception/MissingTemplateException.php +++ b/src/View/Exception/MissingTemplateException.php @@ -87,7 +87,7 @@ public function formatMessage(): string * Get the passed in attributes * * @return array - * @psalm-return array{file: string, paths: array} + * @phpstan-return array{file: string, paths: array} */ public function getAttributes(): array { diff --git a/src/View/Helper/FormHelper.php b/src/View/Helper/FormHelper.php index 7bc4e1b3208..1e0e0fd0c74 100644 --- a/src/View/Helper/FormHelper.php +++ b/src/View/Helper/FormHelper.php @@ -1407,7 +1407,6 @@ protected function enumOptions(string $enumClass): array $values = []; foreach ($enumClass::cases() as $enumClass) { /** - * @psalm-suppress UndefinedInterfaceMethod * @phpstan-ignore-next-line */ $values[$enumClass->value] = $hasLabel ? $enumClass->label() diff --git a/src/View/Helper/HtmlHelper.php b/src/View/Helper/HtmlHelper.php index 21a96215ab4..b094a7547c1 100644 --- a/src/View/Helper/HtmlHelper.php +++ b/src/View/Helper/HtmlHelper.php @@ -283,7 +283,6 @@ public function link(array|string $title, array|string|null $url = null, array $ if ($escapeTitle === true) { $title = h($title); } elseif (is_string($escapeTitle)) { - /** @psalm-suppress PossiblyInvalidArgument */ $title = htmlentities($title, ENT_QUOTES, $escapeTitle); } @@ -536,7 +535,7 @@ public function script(array|string $url, array $options = []): ?string * @param array $options Same options as `UrlHelper::script()`. * @return string * @since 5.2.0 - * @psalm-param array{imports?: array, scopes?: array>>, integrity?: array} $map + * @phpstan-param array{imports?: array, scopes?: array>>, integrity?: array} $map */ public function importmap(array $map, array $options = []): string { @@ -1099,7 +1098,6 @@ public function media(array|string|null $path, array $options = []): string if (!$path && !empty($options['src'])) { $path = $options['src']; } - /** @psalm-suppress PossiblyNullArgument */ $options['src'] = $this->Url->assetUrl($path, $options); } diff --git a/src/View/Helper/PaginatorHelper.php b/src/View/Helper/PaginatorHelper.php index 96294c063b8..d078c662aed 100644 --- a/src/View/Helper/PaginatorHelper.php +++ b/src/View/Helper/PaginatorHelper.php @@ -725,7 +725,7 @@ public function numbers(array $options = []): string * @param array $params Params from the numbers() method. * @param array $options Options from the numbers() method. * @return array An array with the start and end numbers. - * @psalm-return array{0: int, 1: int} + * @phpstan-return array{0: int, 1: int} */ protected function _getNumbersStartAndEnd(array $params, array $options): array { diff --git a/src/View/Helper/UrlHelper.php b/src/View/Helper/UrlHelper.php index e5b83dfe418..f48b29e3784 100644 --- a/src/View/Helper/UrlHelper.php +++ b/src/View/Helper/UrlHelper.php @@ -41,7 +41,7 @@ class UrlHelper extends Helper * Asset URL engine class name * * @var string - * @psalm-var class-string<\Cake\Routing\Asset> + * @phpstan-var class-string<\Cake\Routing\Asset> */ protected string $_assetUrlClassName; diff --git a/src/View/View.php b/src/View/View.php index e2d81accb6a..69d093d6d00 100644 --- a/src/View/View.php +++ b/src/View/View.php @@ -278,7 +278,7 @@ class View implements EventDispatcherInterface * ViewBlock class. * * @var string - * @psalm-var class-string<\Cake\View\ViewBlock> + * @phpstan-var class-string<\Cake\View\ViewBlock> */ protected string $_viewBlockClass = ViewBlock::class; @@ -652,7 +652,7 @@ public function setLayout(string $name) * @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 + * @phpstan-param array{cache?:array|true, callbacks?:bool, plugin?:string|false, ignoreMissing?:bool} $options */ public function element(string $name, array $data = [], array $options = []): string { @@ -1422,7 +1422,7 @@ protected function _checkFilePath(string $file, string $path): string * @param string $name The name you want to plugin split. * @param bool $fallback If true uses the plugin set in the current Request when parsed plugin is not loaded * @return array Array with 2 indexes. 0 => plugin name, 1 => filename. - * @psalm-return array{string|null, string} + * @phpstan-return array{string|null, string} */ public function pluginSplit(string $name, bool $fallback = true): array { @@ -1625,12 +1625,12 @@ protected function _paths(?string $plugin = null, bool $cached = true): array * @param array $data Data * @param array $options Element options * @return array Element Cache configuration. - * @psalm-return array{key:string, config:string} + * @phpstan-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 */ + /** @phpstan-var array{key:string, config:string} $cache */ $cache = $options['cache']; $cache['key'] = 'element_' . $cache['key']; diff --git a/src/View/ViewBuilder.php b/src/View/ViewBuilder.php index 2b9ad02652e..357103a2b13 100644 --- a/src/View/ViewBuilder.php +++ b/src/View/ViewBuilder.php @@ -98,7 +98,7 @@ class ViewBuilder implements JsonSerializable * or a fully namespaced classname. * * @var string|null - * @psalm-var class-string<\Cake\View\View>|string|null + * @phpstan-var class-string<\Cake\View\View>|string|null */ protected ?string $_className = null; diff --git a/tests/test_app/TestApp/Database/Schema/CompatDialect.php b/tests/test_app/TestApp/Database/Schema/CompatDialect.php index 555c11cc0a3..6c7a5a7130a 100644 --- a/tests/test_app/TestApp/Database/Schema/CompatDialect.php +++ b/tests/test_app/TestApp/Database/Schema/CompatDialect.php @@ -220,7 +220,6 @@ public function convertColumnDescription(TableSchema $schema, array $row): void // SQLite does not support autoincrement on composite keys. if ($row['pk'] && !empty($primary)) { $existingColumn = $primary['columns'][0]; - /** @psalm-suppress PossiblyNullOperand */ $schema->addColumn($existingColumn, ['autoIncrement' => null] + $schema->getColumn($existingColumn)); } From 1be60a9352e118094ccb5ab518b1bae11d2b5fd5 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 16 Apr 2025 12:19:32 +0200 Subject: [PATCH 12/17] Remove silencing. --- src/Command/CounterCacheCommand.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Command/CounterCacheCommand.php b/src/Command/CounterCacheCommand.php index edc6798fcde..ec387c063df 100644 --- a/src/Command/CounterCacheCommand.php +++ b/src/Command/CounterCacheCommand.php @@ -72,8 +72,7 @@ public function execute(Arguments $args, ConsoleIo $io): int $methodArgs['page'] = (int)$args->getOption('page'); } - /** @phpstan-ignore-next-line */ - $table->updateCounterCache(...$methodArgs); + $table->getBehavior('CounterCache')->updateCounterCache(...$methodArgs); $io->success('Counter cache updated successfully.'); From 85743f9d06e3d4903fda7f77312207f8d4a853b2 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 16 Apr 2025 13:34:38 +0200 Subject: [PATCH 13/17] Fix up annotation --- src/Command/CounterCacheCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Command/CounterCacheCommand.php b/src/Command/CounterCacheCommand.php index ec387c063df..6f266334413 100644 --- a/src/Command/CounterCacheCommand.php +++ b/src/Command/CounterCacheCommand.php @@ -72,6 +72,7 @@ public function execute(Arguments $args, ConsoleIo $io): int $methodArgs['page'] = (int)$args->getOption('page'); } + /** @var \Cake\ORM\Table $table */ $table->getBehavior('CounterCache')->updateCounterCache(...$methodArgs); $io->success('Counter cache updated successfully.'); From f8b2a1caf625fd7b80f7dd6c5511980d50e497a6 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 16 Apr 2025 13:37:36 +0200 Subject: [PATCH 14/17] Remove one phpstan silencing. --- src/ORM/Query/SelectQuery.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ORM/Query/SelectQuery.php b/src/ORM/Query/SelectQuery.php index 26efc49bb7d..2f27ddc368e 100644 --- a/src/ORM/Query/SelectQuery.php +++ b/src/ORM/Query/SelectQuery.php @@ -384,8 +384,7 @@ public function all(): ResultSetInterface } $this->_results = $results; - /** @phpstan-ignore-next-line */ - return $this->_results; + return $results; } /** From 3e329eb3d09648b57c1f32d2e3703cfe4f663f5d Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Thu, 17 Apr 2025 19:35:32 +0200 Subject: [PATCH 15/17] 5.2: Throw deprecation on invalid short option collision. (#18336) * Throw deprecation on invalid short option collision. * Fix up test. --- src/Console/ConsoleOptionParser.php | 5 +++++ .../Console/ConsoleOptionParserTest.php | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Console/ConsoleOptionParser.php b/src/Console/ConsoleOptionParser.php index 812d6a7a09e..b88088264d2 100644 --- a/src/Console/ConsoleOptionParser.php +++ b/src/Console/ConsoleOptionParser.php @@ -20,6 +20,7 @@ use Cake\Console\Exception\MissingOptionException; use Cake\Utility\Inflector; use LogicException; +use function Cake\Core\deprecationWarning; /** * Handles parsing the ARGV in the command line and provides support @@ -396,6 +397,10 @@ public function addOption(ConsoleInputOption|string $name, array $options = []) $this->_options[$name] = $option; asort($this->_options); if ($option->short()) { + if (isset($this->_shortOptions[$option->short()])) { + deprecationWarning('5.2.0', 'You cannot redefine short options. This will throw an error in 5.3.0+.'); + } + $this->_shortOptions[$option->short()] = $name; asort($this->_shortOptions); } diff --git a/tests/TestCase/Console/ConsoleOptionParserTest.php b/tests/TestCase/Console/ConsoleOptionParserTest.php index ac059ce0cc1..21ed90cc543 100644 --- a/tests/TestCase/Console/ConsoleOptionParserTest.php +++ b/tests/TestCase/Console/ConsoleOptionParserTest.php @@ -25,6 +25,7 @@ use Cake\Console\TestSuite\StubConsoleOutput; use Cake\TestSuite\TestCase; use LogicException; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; /** * ConsoleOptionParserTest @@ -183,6 +184,24 @@ public function testAddOptionShort(): void $this->assertEquals(['test' => 'value', 'help' => false], $result[0], 'Short parameter did not parse out'); } + /** + * test adding an option and using the short value for parsing throws deprecation if conflicting. + */ + #[WithoutErrorHandler] + public function testAddOptionShortConflict(): void + { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', [ + 'short' => 't', + ]); + + $this->deprecated(function () use ($parser) { + $parser->addOption('other', [ + 'short' => 't', + ]); + }); + } + /** * test adding an option and using the short value for parsing. */ From 2c08c770145d8e408ad85f7320e87f67988e8745 Mon Sep 17 00:00:00 2001 From: ADmad Date: Fri, 18 Apr 2025 09:17:06 +0530 Subject: [PATCH 16/17] Ensure a field's setter is not run if the value is unchanged. (#18348) Fixes #18346 --- src/Datasource/EntityTrait.php | 22 +++++++++++++--------- src/ORM/Entity.php | 1 + tests/TestCase/ORM/EntityTest.php | 6 +++--- tests/TestCase/ORM/MarshallerTest.php | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/Datasource/EntityTrait.php b/src/Datasource/EntityTrait.php index 4cab7fb5919..b7ad02cebc0 100644 --- a/src/Datasource/EntityTrait.php +++ b/src/Datasource/EntityTrait.php @@ -311,6 +311,12 @@ public function patch(array $values, array $options = []) continue; } + if ($options['asOriginal'] || $this->isModified($name, $value)) { + $this->setDirty($name, true); + } else { + continue; + } + if ($options['setter']) { $setter = static::_accessor($name, 'set'); if ($setter) { @@ -327,14 +333,6 @@ public function patch(array $values, array $options = []) $this->_original[$name] = $this->_fields[$name]; } - // Don't dirty scalar values and objects that didn't - // change. Arrays will always be marked as dirty because - // the original/updated list could contain references to the - // same objects, even though those objects may have changed internally. - if ($this->isModified($name, $value)) { - $this->setDirty($name, true); - } - $this->_fields[$name] = $value; } @@ -344,6 +342,12 @@ public function patch(array $values, array $options = []) /** * Check if the provided value is same as existing value for a field. * + * This check is used to determine if a field should be set as dirty or not. + * It will return `false` for scalar values and objects which haven't changed. + * For arrays `true` will be returned always because the original/updated list + * could contain references to the same objects, even though those objects + * may have changed internally. + * * @param string $field The field to check. * @return bool */ @@ -873,7 +877,7 @@ public function extractOriginalChanged(array $fields): array */ public function isOriginalField(string $name): bool { - return in_array($name, $this->_originalFields); + return in_array($name, $this->_originalFields, true); } /** diff --git a/src/ORM/Entity.php b/src/ORM/Entity.php index c924e4d8f8b..48c55fac11d 100644 --- a/src/ORM/Entity.php +++ b/src/ORM/Entity.php @@ -76,6 +76,7 @@ public function __construct(array $properties = [], array $options = []) } $this->patch($properties, [ + 'asOriginal' => true, 'setter' => $options['useSetters'], 'guard' => $options['guard'], ]); diff --git a/tests/TestCase/ORM/EntityTest.php b/tests/TestCase/ORM/EntityTest.php index ecde874a522..9db90a7b83e 100644 --- a/tests/TestCase/ORM/EntityTest.php +++ b/tests/TestCase/ORM/EntityTest.php @@ -273,9 +273,9 @@ public function testConstructor(): void ->with( ...self::withConsecutive( [ - ['a' => 'b', 'c' => 'd'], ['setter' => true, 'guard' => false], + ['a' => 'b', 'c' => 'd'], ['setter' => true, 'guard' => false, 'asOriginal' => true], ], - [['foo' => 'bar'], ['setter' => false, 'guard' => false]], + [['foo' => 'bar'], ['setter' => false, 'guard' => false, 'asOriginal' => true]], ), ); @@ -295,7 +295,7 @@ public function testConstructorWithGuard(): void ->getMock(); $entity->expects($this->once()) ->method('patch') - ->with(['foo' => 'bar'], ['setter' => true, 'guard' => true]); + ->with(['foo' => 'bar'], ['setter' => true, 'guard' => true, 'asOriginal' => true]); $entity->__construct(['foo' => 'bar'], ['guard' => true]); } diff --git a/tests/TestCase/ORM/MarshallerTest.php b/tests/TestCase/ORM/MarshallerTest.php index 7b55c9e4296..37c76255469 100644 --- a/tests/TestCase/ORM/MarshallerTest.php +++ b/tests/TestCase/ORM/MarshallerTest.php @@ -1579,6 +1579,25 @@ public function testMergeDirty(): void $this->assertFalse($entity->isDirty('title')); $this->assertFalse($entity->isDirty('author_id')); $this->assertTrue($entity->isDirty('crazy')); + + // https://github.com/cakephp/cakephp/issues/18346 + $entity = new class ([ + 'title' => 'Foo', + 'author_id' => 1, + ], ['useSetters' => false]) extends Entity { + protected function _setTitle(string $name): string + { + return 'The ' . $name; + } + }; + $entity->clean(); + + $this->assertSame('Foo', $entity->title); + $marshall->merge($entity, ['title' => 'Foo', 'author_id' => 2]); + $this->assertSame('Foo', $entity->title, 'Setter should not be called as the value is unchanged'); + $this->assertFalse($entity->isDirty('title')); + $this->assertTrue($entity->isDirty('author_id')); + $this->assertSame(2, $entity->author_id); } /** From 64789870f0b2b7b92de9ca85871c2da7193345b6 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 17 Apr 2025 23:47:27 -0400 Subject: [PATCH 17/17] Update version number to 5.2.2 --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 1dc53dd96d8..bae3d5eff81 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -16,4 +16,4 @@ // @license https://opensource.org/licenses/mit-license.php MIT License // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -5.2.1 +5.2.2