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 c70ff296ea2..4acd75dc41b 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,5 +1,4 @@
-
-
+
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
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
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/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/CounterCacheCommand.php b/src/Command/CounterCacheCommand.php
index edc6798fcde..6f266334413 100644
--- a/src/Command/CounterCacheCommand.php
+++ b/src/Command/CounterCacheCommand.php
@@ -72,8 +72,8 @@ public function execute(Arguments $args, ConsoleIo $io): int
$methodArgs['page'] = (int)$args->getOption('page');
}
- /** @phpstan-ignore-next-line */
- $table->updateCounterCache(...$methodArgs);
+ /** @var \Cake\ORM\Table $table */
+ $table->getBehavior('CounterCache')->updateCounterCache(...$methodArgs);
$io->success('Counter cache updated successfully.');
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/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/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 28f55bbe7a3..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;
}
@@ -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/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/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/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/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/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/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..b7ad02cebc0 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');
@@ -312,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) {
@@ -328,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;
}
@@ -345,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
*/
@@ -874,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/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/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/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/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/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 82db5cffec2..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
{
@@ -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/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/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/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..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;
}
/**
@@ -1673,13 +1672,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 fc4c6012e7e..36833d48c2b 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
@@ -262,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;
@@ -844,6 +845,9 @@ 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]
* @throws \InvalidArgumentException If the behavior does not exist.
*/
public function getBehavior(string $name): Behavior
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/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/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'];
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/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/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 ab3741c18af..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;
@@ -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) {
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.
*/
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
{
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;
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));
}
/**
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,
];
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'));
}
/**
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);
}
/**
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);
- }
}
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));
}