diff --git a/src/BaseSeed.php b/src/BaseSeed.php
index 28902213..146abc4e 100644
--- a/src/BaseSeed.php
+++ b/src/BaseSeed.php
@@ -238,7 +238,9 @@ public function isIdempotent(): bool
public function call(string $seeder, array $options = []): void
{
$io = $this->getIo();
- assert($io !== null, 'Requires ConsoleIo');
+ if ($io === null) {
+ throw new RuntimeException('ConsoleIo is required for calling other seeders.');
+ }
$io->out('');
$io->out(
' ====' .
@@ -285,7 +287,9 @@ protected function runCall(string $seeder, array $options = []): void
'source' => $options['source'],
]);
$io = $this->getIo();
- assert($io !== null, 'Missing ConsoleIo instance');
+ if ($io === null) {
+ throw new RuntimeException('ConsoleIo is required for calling other seeders.');
+ }
$manager = $factory->createManager($io);
$manager->seed($seeder);
}
diff --git a/src/Command/BakeMigrationDiffCommand.php b/src/Command/BakeMigrationDiffCommand.php
index cb3ad01e..68bcd71b 100644
--- a/src/Command/BakeMigrationDiffCommand.php
+++ b/src/Command/BakeMigrationDiffCommand.php
@@ -19,8 +19,15 @@
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Database\Connection;
+use Cake\Database\Schema\CachedCollection;
+use Cake\Database\Schema\CheckConstraint;
use Cake\Database\Schema\CollectionInterface;
+use Cake\Database\Schema\Column;
+use Cake\Database\Schema\Constraint;
+use Cake\Database\Schema\ForeignKey;
+use Cake\Database\Schema\Index;
use Cake\Database\Schema\TableSchema;
+use Cake\Database\Schema\UniqueKey;
use Cake\Datasource\ConnectionManager;
use Cake\Event\Event;
use Cake\Event\EventManager;
@@ -485,7 +492,7 @@ protected function checkSync(): bool
$lastVersion = $this->migratedItems[0]['version'];
$lastFile = end($this->migrationsFiles);
- return $lastFile && (bool)strpos($lastFile, (string)$lastVersion);
+ return $lastFile && str_contains($lastFile, (string)$lastVersion);
}
return false;
@@ -546,7 +553,21 @@ protected function getDumpSchema(Arguments $args): array
$this->io->abort($msg);
}
- return unserialize((string)file_get_contents($path));
+ $contents = (string)file_get_contents($path);
+
+ // Use allowed_classes to restrict deserialization to safe CakePHP schema classes
+ return unserialize($contents, [
+ 'allowed_classes' => [
+ TableSchema::class,
+ CachedCollection::class,
+ Column::class,
+ Index::class,
+ Constraint::class,
+ UniqueKey::class,
+ ForeignKey::class,
+ CheckConstraint::class,
+ ],
+ ]);
}
/**
diff --git a/src/Command/DumpCommand.php b/src/Command/DumpCommand.php
index 0e694b41..53c79026 100644
--- a/src/Command/DumpCommand.php
+++ b/src/Command/DumpCommand.php
@@ -141,7 +141,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
return self::CODE_SUCCESS;
}
- $io->error("An error occurred while writing dump file `{$filePath}`");
+ $io->err("An error occurred while writing dump file `{$filePath}`");
return self::CODE_ERROR;
}
diff --git a/src/Db/Adapter/MysqlAdapter.php b/src/Db/Adapter/MysqlAdapter.php
index eaca68bd..0873581a 100644
--- a/src/Db/Adapter/MysqlAdapter.php
+++ b/src/Db/Adapter/MysqlAdapter.php
@@ -725,7 +725,9 @@ protected function getRenameColumnInstructions(string $tableName, string $column
foreach ($rows as $row) {
if (strcasecmp($row['Field'], $columnName) === 0) {
$null = $row['Null'] === 'NO' ? 'NOT NULL' : 'NULL';
- $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
+ $comment = isset($row['Comment']) && $row['Comment'] !== ''
+ ? ' COMMENT ' . $this->getConnection()->getDriver()->schemaValue($row['Comment'])
+ : '';
// create the extra string by also filtering out the DEFAULT_GENERATED option (MySQL 8 fix)
$extras = array_filter(
diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php
index 14602abc..f1f330dc 100644
--- a/src/Db/Adapter/SqlserverAdapter.php
+++ b/src/Db/Adapter/SqlserverAdapter.php
@@ -1105,27 +1105,38 @@ private function updateSQLForIdentityInsert(string $tableName, string $sql): str
/**
* @inheritDoc
+ *
+ * Note: Check constraints are not supported for SQL Server adapter.
+ * This method returns an empty array. Use raw SQL via execute() if you need
+ * check constraints on SQL Server.
*/
protected function getCheckConstraints(string $tableName): array
{
- // TODO: Implement check constraints for SQL Server
return [];
}
/**
* @inheritDoc
+ * @throws \BadMethodCallException Check constraints are not supported for SQL Server.
*/
protected function getAddCheckConstraintInstructions(TableMetadata $table, CheckConstraint $checkConstraint): AlterInstructions
{
- throw new BadMethodCallException('Check constraints are not yet implemented for SQL Server adapter');
+ throw new BadMethodCallException(
+ 'Check constraints are not supported for the SQL Server adapter. ' .
+ 'Use $this->execute() with raw SQL to add check constraints.',
+ );
}
/**
* @inheritDoc
+ * @throws \BadMethodCallException Check constraints are not supported for SQL Server.
*/
protected function getDropCheckConstraintInstructions(string $tableName, string $constraintName): AlterInstructions
{
- throw new BadMethodCallException('Check constraints are not yet implemented for SQL Server adapter');
+ throw new BadMethodCallException(
+ 'Check constraints are not supported for the SQL Server adapter. ' .
+ 'Use $this->execute() with raw SQL to drop check constraints.',
+ );
}
/**
diff --git a/src/Db/Table.php b/src/Db/Table.php
index 3998627d..aeeb0906 100644
--- a/src/Db/Table.php
+++ b/src/Db/Table.php
@@ -955,7 +955,7 @@ public function saveData(): void
$c = array_keys($row);
foreach ($this->getData() as $row) {
$k = array_keys($row);
- if ($k != $c) {
+ if ($k !== $c) {
$bulk = false;
break;
}
diff --git a/src/Db/Table/Column.php b/src/Db/Table/Column.php
index a16585f9..98f28b31 100644
--- a/src/Db/Table/Column.php
+++ b/src/Db/Table/Column.php
@@ -789,6 +789,7 @@ protected function getValidOptions(): array
'update',
'comment',
'signed',
+ 'unsigned',
'timezone',
'properties',
'values',
diff --git a/src/Migration/Manager.php b/src/Migration/Manager.php
index 2ce00a17..862e7627 100644
--- a/src/Migration/Manager.php
+++ b/src/Migration/Manager.php
@@ -1101,18 +1101,46 @@ protected function getSeedDependenciesInstances(SeedInterface $seed): array
* Order seeds by dependencies
*
* @param \Migrations\SeedInterface[] $seeds Seeds
+ * @param array $visiting Seeds currently being visited (for cycle detection)
+ * @param array $visited Seeds that have been fully processed
* @return \Migrations\SeedInterface[]
+ * @throws \RuntimeException When a circular dependency is detected
*/
- protected function orderSeedsByDependencies(array $seeds): array
+ protected function orderSeedsByDependencies(array $seeds, array $visiting = [], array &$visited = []): array
{
$orderedSeeds = [];
foreach ($seeds as $seed) {
$name = $seed->getName();
- $orderedSeeds[$name] = $seed;
+
+ // Skip if already fully processed
+ if (isset($visited[$name])) {
+ continue;
+ }
+
+ // Check for circular dependency
+ if (isset($visiting[$name])) {
+ $cycle = array_keys($visiting);
+ $cycle[] = $name;
+ throw new RuntimeException(
+ 'Circular dependency detected in seeds: ' . implode(' -> ', $cycle),
+ );
+ }
+
+ // Mark as currently visiting
+ $visiting[$name] = true;
+
$dependencies = $this->getSeedDependenciesInstances($seed);
if ($dependencies) {
- $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
+ $orderedSeeds = array_merge(
+ $this->orderSeedsByDependencies($dependencies, $visiting, $visited),
+ $orderedSeeds,
+ );
}
+
+ // Mark as fully visited and add to result
+ $visited[$name] = true;
+ unset($visiting[$name]);
+ $orderedSeeds[$name] = $seed;
}
return $orderedSeeds;
diff --git a/src/Migrations.php b/src/Migrations.php
index bba10eee..b61bf07c 100644
--- a/src/Migrations.php
+++ b/src/Migrations.php
@@ -36,7 +36,7 @@ class Migrations
*
* @var string
*/
- protected string $command;
+ protected string $command = '';
/**
* Constructor
diff --git a/src/TestSuite/Migrator.php b/src/TestSuite/Migrator.php
index a33e9526..a0c30160 100644
--- a/src/TestSuite/Migrator.php
+++ b/src/TestSuite/Migrator.php
@@ -199,7 +199,7 @@ protected function shouldDropTables(Migrations $migrations, array $options): boo
if (!empty($messages['missing'])) {
$hasProblems = true;
$output[] = 'Applied but missing migrations:';
- $output = array_merge($output, array_map($itemize, $messages['down']));
+ $output = array_merge($output, array_map($itemize, $messages['missing']));
$output[] = '';
}
if ($output) {