From 403167625ea70ddbd6adaec97fc5b24dff01b4ba Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 10:44:52 -0500 Subject: [PATCH 01/10] Bump PHPStan level +1 --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index d3803bde..b35bf011 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 7 + level: 8 paths: - src/ bootstrapFiles: From d3b6434b0cf682ef5ece3ad275453c7dee34cb9b Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 11:00:56 -0500 Subject: [PATCH 02/10] Make props non-nullable --- src/Command/BakeSimpleMigrationCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Command/BakeSimpleMigrationCommand.php b/src/Command/BakeSimpleMigrationCommand.php index 622b61fc..08b703e3 100644 --- a/src/Command/BakeSimpleMigrationCommand.php +++ b/src/Command/BakeSimpleMigrationCommand.php @@ -52,16 +52,16 @@ abstract class BakeSimpleMigrationCommand extends SimpleBakeCommand /** * Console IO * - * @var \Cake\Console\ConsoleIo|null + * @var \Cake\Console\ConsoleIo */ - protected ?ConsoleIo $io = null; + protected ConsoleIo $io; /** * Arguments * - * @var \Cake\Console\Arguments|null + * @var \Cake\Console\Arguments */ - protected ?Arguments $args = null; + protected Arguments $args; /** * @inheritDoc From f69e9891d84e3ea23c704a22897040531ed7712b Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 11:03:33 -0500 Subject: [PATCH 03/10] Added null guards for getColumn() and getConstraint() --- src/Command/BakeMigrationDiffCommand.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Command/BakeMigrationDiffCommand.php b/src/Command/BakeMigrationDiffCommand.php index 68bcd71b..d83f0d40 100644 --- a/src/Command/BakeMigrationDiffCommand.php +++ b/src/Command/BakeMigrationDiffCommand.php @@ -275,16 +275,24 @@ protected function getColumns(): void // changes in columns meta-data foreach ($currentColumns as $columnName) { $column = $currentSchema->getColumn($columnName); + if ($column === null) { + continue; + } $oldColumn = $this->dumpSchema[$table]->getColumn($columnName); unset( $column['collate'], $column['fixed'], - $oldColumn['collate'], - $oldColumn['fixed'], ); + if ($oldColumn !== null) { + unset( + $oldColumn['collate'], + $oldColumn['fixed'], + ); + } if ( in_array($columnName, $oldColumns, true) && + $oldColumn !== null && $column !== $oldColumn ) { $changedAttributes = array_diff_assoc($column, $oldColumn); @@ -381,9 +389,10 @@ protected function getConstraints(): void // brand new constraints $addedConstraints = array_diff($currentConstraints, $oldConstraints); foreach ($addedConstraints as $constraintName) { - $this->templateData[$table]['constraints']['add'][$constraintName] = - $currentSchema->getConstraint($constraintName); $constraint = $currentSchema->getConstraint($constraintName); + if ($constraint === null) { + continue; + } if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $this->templateData[$table]['constraints']['add'][$constraintName] = $constraint; } else { @@ -411,6 +420,9 @@ protected function getConstraints(): void $removedConstraints = array_diff($oldConstraints, $currentConstraints); foreach ($removedConstraints as $constraintName) { $constraint = $this->dumpSchema[$table]->getConstraint($constraintName); + if ($constraint === null) { + continue; + } if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $this->templateData[$table]['constraints']['remove'][$constraintName] = $constraint; } else { From 2beb98ff160cd9c40c9d74dea19d2c1a39af7629 Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 13:42:26 -0500 Subject: [PATCH 04/10] Fix nullable return types in Column and ForeignKey Column::getNull() now coalesces the nullable bool property to false. ForeignKey::getOnDelete()/getOnUpdate() guard against null from getDelete()/getUpdate() before calling mapAction(). --- src/Db/Table/Column.php | 2 +- src/Db/Table/ForeignKey.php | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Db/Table/Column.php b/src/Db/Table/Column.php index 98f28b31..a669475a 100644 --- a/src/Db/Table/Column.php +++ b/src/Db/Table/Column.php @@ -229,7 +229,7 @@ public function setNull(bool $null) */ public function getNull(): bool { - return $this->null; + return $this->null ?? false; } /** diff --git a/src/Db/Table/ForeignKey.php b/src/Db/Table/ForeignKey.php index 907e5f37..929930a9 100644 --- a/src/Db/Table/ForeignKey.php +++ b/src/Db/Table/ForeignKey.php @@ -246,7 +246,9 @@ public function setOnDelete(string $onDelete) */ public function getOnDelete(): ?string { - return $this->mapAction($this->getDelete()); + $delete = $this->getDelete(); + + return $delete !== null ? $this->mapAction($delete) : null; } /** @@ -271,6 +273,8 @@ public function setOnUpdate(string $onUpdate) */ public function getOnUpdate(): ?string { - return $this->mapAction($this->getUpdate()); + $update = $this->getUpdate(); + + return $update !== null ? $this->mapAction($update) : null; } } From 481d993197fcd01f393c7692cea16ac7a2211504 Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 13:42:47 -0500 Subject: [PATCH 05/10] Fix null safety in Db adapters and domain classes Cast preg_replace results to string, coalesce nullable getColumns() returns, cast nullable getReferencedTable()/getName() at call sites, and use null-safe operator for optional ConsoleIo. --- src/Db/Adapter/AbstractAdapter.php | 3 ++- src/Db/Adapter/MysqlAdapter.php | 6 +++--- src/Db/Adapter/PostgresAdapter.php | 18 +++++++++--------- src/Db/Adapter/SqliteAdapter.php | 14 +++++++------- src/Db/Adapter/SqlserverAdapter.php | 10 +++++----- src/Db/Adapter/TimedOutputAdapter.php | 2 +- src/Db/Plan/Plan.php | 2 +- src/Db/Table.php | 6 +++--- 8 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Db/Adapter/AbstractAdapter.php b/src/Db/Adapter/AbstractAdapter.php index 3bb4a81b..eaa4ab31 100644 --- a/src/Db/Adapter/AbstractAdapter.php +++ b/src/Db/Adapter/AbstractAdapter.php @@ -201,6 +201,7 @@ public function getConnection(): Connection $this->connection = $this->getOption('connection'); $this->connect(); } + assert($this->connection !== null); return $this->connection; } @@ -1681,7 +1682,7 @@ public function executeActions(TableMetadata $table, array $actions): void /** @var \Migrations\Db\Action\DropForeignKey $action */ $instructions->merge($this->getDropForeignKeyByColumnsInstructions( $table->getName(), - $action->getForeignKey()->getColumns(), + $action->getForeignKey()->getColumns() ?? [], )); break; diff --git a/src/Db/Adapter/MysqlAdapter.php b/src/Db/Adapter/MysqlAdapter.php index b1d8a182..0112756c 100644 --- a/src/Db/Adapter/MysqlAdapter.php +++ b/src/Db/Adapter/MysqlAdapter.php @@ -710,7 +710,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column $targetColumn = null; foreach ($columns as $column) { - if (strcasecmp($column->getName(), $columnName) === 0) { + if (strcasecmp((string)$column->getName(), $columnName) === 0) { $targetColumn = $column; break; } @@ -1184,7 +1184,7 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string $def .= ' CONSTRAINT ' . $this->quoteColumnName((string)$foreignKey->getName()); } $columnNames = []; - foreach ($foreignKey->getColumns() as $column) { + foreach ($foreignKey->getColumns() ?? [] as $column) { $columnNames[] = $this->quoteColumnName($column); } $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; @@ -1192,7 +1192,7 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string foreach ($foreignKey->getReferencedColumns() as $column) { $refColumnNames[] = $this->quoteColumnName($column); } - $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()) . ' (' . implode(',', $refColumnNames) . ')'; + $def .= ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . implode(',', $refColumnNames) . ')'; $onDelete = $foreignKey->getOnDelete(); if ($onDelete) { $def .= ' ON DELETE ' . $onDelete; diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index 5251ce4a..cb6d6b6d 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -455,9 +455,9 @@ protected function getChangeColumnInstructions( $columnSql = $dialect->columnDefinitionSql($this->mapColumnData($newColumn->toArray())); // Remove the column name from $columnSql - $columnType = preg_replace('/^"?(?:[^"]+)"?\s+/', '', $columnSql); + $columnType = (string)preg_replace('/^"?(?:[^"]+)"?\s+/', '', $columnSql); // Remove generated clause - $columnType = preg_replace('/GENERATED (?:ALWAYS|BY DEFAULT) AS IDENTITY/', '', $columnType); + $columnType = (string)preg_replace('/GENERATED (?:ALWAYS|BY DEFAULT) AS IDENTITY/', '', $columnType); $sql = sprintf( 'ALTER COLUMN %s TYPE %s', @@ -477,10 +477,10 @@ protected function getChangeColumnInstructions( ); } // NULL and DEFAULT cannot be set while changing column type - $sql = preg_replace('/ NOT NULL/', '', $sql); - $sql = preg_replace('/ DEFAULT NULL/', '', $sql); + $sql = (string)preg_replace('/ NOT NULL/', '', $sql); + $sql = (string)preg_replace('/ DEFAULT NULL/', '', $sql); // If it is set, DEFAULT is the last definition - $sql = preg_replace('/DEFAULT .*/', '', $sql); + $sql = (string)preg_replace('/DEFAULT .*/', '', $sql); if ($newColumn->getType() === 'boolean') { $sql .= sprintf( ' USING (CASE WHEN %s IS NULL THEN NULL WHEN %s::int=0 THEN FALSE ELSE TRUE END)', @@ -946,13 +946,13 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta $parts = $this->getSchemaName($tableName); $constraintName = $foreignKey->getName() ?: ( - $parts['table'] . '_' . implode('_', $foreignKey->getColumns()) . '_fkey' + $parts['table'] . '_' . implode('_', $foreignKey->getColumns() ?? []) . '_fkey' ); - $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns())); + $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns() ?? [])); $refColumnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getReferencedColumns())); $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName) . ' FOREIGN KEY (' . $columnList . ')' . - ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; + ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; if ($foreignKey->getOnDelete()) { $def .= " ON DELETE {$foreignKey->getOnDelete()}"; } @@ -1321,7 +1321,7 @@ protected function getConflictClause( } $quotedConflictColumns = array_map($this->quoteColumnName(...), $conflictColumns); $updates = []; - foreach ($updateColumns as $column) { + foreach ($updateColumns ?? [] as $column) { $quotedColumn = $this->quoteColumnName($column); $updates[] = $quotedColumn . ' = EXCLUDED.' . $quotedColumn; } diff --git a/src/Db/Adapter/SqliteAdapter.php b/src/Db/Adapter/SqliteAdapter.php index 9145e0cb..00d3024d 100644 --- a/src/Db/Adapter/SqliteAdapter.php +++ b/src/Db/Adapter/SqliteAdapter.php @@ -570,7 +570,7 @@ protected function getAddColumnInstructions(TableMetadata $table, Column $column return $state; } $finalColumnName = end($columns)->getName(); - $sql = preg_replace( + $sql = (string)preg_replace( sprintf( "/(%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+)([,)])/", $this->quoteColumnName((string)$finalColumnName), @@ -619,7 +619,7 @@ protected function getDeclaringSql(string $tableName): string $columnNamePattern = "\"$columnName\"|`$columnName`|\\[$columnName\\]|$columnName"; $columnNamePattern = "#([\(,]+\\s*)($columnNamePattern)(\\s)#iU"; - $sql = preg_replace_callback( + $sql = (string)preg_replace_callback( $columnNamePattern, function ($matches) use ($column) { return $matches[1] . $this->quoteColumnName($column['name']) . $matches[3]; @@ -631,7 +631,7 @@ function ($matches) use ($column) { $tableNamePattern = "\"$tableName\"|`$tableName`|\\[$tableName\\]|$tableName"; $tableNamePattern = "#^(CREATE TABLE)\s*($tableNamePattern)\s*(\()#Ui"; - $sql = preg_replace($tableNamePattern, "$1 `$tableName` $3", $sql, 1); + $sql = (string)preg_replace($tableNamePattern, "$1 `$tableName` $3", $sql, 1); return $sql; } @@ -1115,7 +1115,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column $newColumnName = (string)$newColumn->getName(); $instructions->addPostStep(function ($state) use ($columnName, $newColumn) { $dialect = $this->getSchemaDialect(); - $sql = preg_replace( + $sql = (string)preg_replace( sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)), sprintf('%s$1', $dialect->columnDefinitionSql($newColumn->toArray())), (string)$state['createSQL'], @@ -1149,7 +1149,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa }); $instructions->addPostStep(function ($state) use ($columnName) { - $sql = preg_replace( + $sql = (string)preg_replace( sprintf("/%s\s\w+.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName))), '', (string)$state['createSQL'], @@ -1679,7 +1679,7 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string $def .= ' CONSTRAINT ' . $this->quoteColumnName((string)$foreignKey->getName()); } $columnNames = []; - foreach ($foreignKey->getColumns() as $column) { + foreach ($foreignKey->getColumns() ?? [] as $column) { $columnNames[] = $this->quoteColumnName($column); } $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; @@ -1687,7 +1687,7 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string foreach ($foreignKey->getReferencedColumns() as $column) { $refColumnNames[] = $this->quoteColumnName($column); } - $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()) . ' (' . implode(',', $refColumnNames) . ')'; + $def .= ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . implode(',', $refColumnNames) . ')'; if ($foreignKey->getOnDelete()) { $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); } diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php index e804d590..d2ebd336 100644 --- a/src/Db/Adapter/SqlserverAdapter.php +++ b/src/Db/Adapter/SqlserverAdapter.php @@ -338,7 +338,7 @@ protected function parseDefault(?string $default): int|string|null $result = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default); - if (strtoupper($result) === 'NULL') { + if (strtoupper((string)$result) === 'NULL') { $result = null; } elseif (is_numeric($result)) { $result = (int)$result; @@ -475,7 +475,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column $dialect->columnDefinitionSql($columnData), ); $alterColumn = preg_replace('/DEFAULT NULL/', '', $alterColumn); - $instructions->addPostStep($alterColumn); + $instructions->addPostStep((string)$alterColumn); // change column comment if needed if ($newColumn->getComment()) { @@ -864,13 +864,13 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin */ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $tableName): string { - $constraintName = $foreignKey->getName() ?: $tableName . '_' . implode('_', $foreignKey->getColumns()); - $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns())); + $constraintName = $foreignKey->getName() ?: $tableName . '_' . implode('_', $foreignKey->getColumns() ?? []); + $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns() ?? [])); $refColumnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getReferencedColumns())); $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName); $def .= ' FOREIGN KEY (' . $columnList . ')'; - $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; + $def .= ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; if ($foreignKey->getOnDelete()) { $def .= " ON DELETE {$foreignKey->getOnDelete()}"; } diff --git a/src/Db/Adapter/TimedOutputAdapter.php b/src/Db/Adapter/TimedOutputAdapter.php index f5e11be6..fa5b47a1 100644 --- a/src/Db/Adapter/TimedOutputAdapter.php +++ b/src/Db/Adapter/TimedOutputAdapter.php @@ -78,7 +78,7 @@ function ($value) { return; } - $this->getIo()->verbose(' -- ' . $command); + $this->getIo()?->verbose(' -- ' . $command); } /** diff --git a/src/Db/Plan/Plan.php b/src/Db/Plan/Plan.php index dcbaa718..8969ebef 100644 --- a/src/Db/Plan/Plan.php +++ b/src/Db/Plan/Plan.php @@ -284,7 +284,7 @@ protected function remapContraintAndIndexConflicts(AlterTable $alter): AlterTabl if ($action instanceof DropForeignKey) { [$this->indexes, $dropIndexActions] = $this->forgetDropIndex( $action->getTable(), - $action->getForeignKey()->getColumns(), + $action->getForeignKey()->getColumns() ?? [], $this->indexes, ); foreach ($dropIndexActions as $dropIndexAction) { diff --git a/src/Db/Table.php b/src/Db/Table.php index aeeb0906..31aee372 100644 --- a/src/Db/Table.php +++ b/src/Db/Table.php @@ -362,7 +362,7 @@ public function addColumn(string|Column $columnName, ?string $type = null, array if ($columnName instanceof Column) { $action = new AddColumn($this->table, $columnName); } else { - $action = new AddColumn($this->table, $this->getAdapter()->getColumnForType($columnName, $type, $options)); + $action = new AddColumn($this->table, $this->getAdapter()->getColumnForType($columnName, (string)$type, $options)); } // Delegate to Adapters to check column type @@ -902,7 +902,7 @@ protected function filterPrimaryKey(array $options): void return $action->getColumn(); }); $primaryKeyColumns = $columnsCollection->filter(function (Column $columnDef, $key) use ($primaryKey) { - return isset($primaryKey[$columnDef->getName()]); + return isset($primaryKey[(string)$columnDef->getName()]); })->toArray(); if (!$primaryKeyColumns) { @@ -911,7 +911,7 @@ protected function filterPrimaryKey(array $options): void foreach ($primaryKeyColumns as $primaryKeyColumn) { if ($primaryKeyColumn->isIdentity()) { - unset($primaryKey[$primaryKeyColumn->getName()]); + unset($primaryKey[(string)$primaryKeyColumn->getName()]); } } From 08c595bfa092c09dd888bc4c09284823d3cdd5c8 Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 13:42:53 -0500 Subject: [PATCH 06/10] Fix remaining PHPStan level 8 null safety issues Guard nullable config access in BaseSeed, cast preg_replace in ColumnParser, null-check association in TableFinder, and use isset for constraint type in MigrationHelper. --- src/BaseSeed.php | 4 ++-- src/Util/ColumnParser.php | 2 +- src/Util/TableFinder.php | 5 +++-- src/View/Helper/MigrationHelper.php | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/BaseSeed.php b/src/BaseSeed.php index 146abc4e..ab7c9304 100644 --- a/src/BaseSeed.php +++ b/src/BaseSeed.php @@ -278,8 +278,8 @@ protected function runCall(string $seeder, array $options = []): void $options += [ 'connection' => $connection, - 'plugin' => $pluginName ?? $config['plugin'], - 'source' => $config['source'], + 'plugin' => $pluginName ?? ($config !== null ? $config['plugin'] : null), + 'source' => $config !== null ? $config['source'] : null, ]; $factory = new ManagerFactory([ 'connection' => $options['connection'], diff --git a/src/Util/ColumnParser.php b/src/Util/ColumnParser.php index 97b9efdc..e16d1ab8 100644 --- a/src/Util/ColumnParser.php +++ b/src/Util/ColumnParser.php @@ -213,7 +213,7 @@ public function parseForeignKeys(array $arguments): array $referencedTable = $indexType; if (!$referencedTable) { // Remove common suffixes like _id and pluralize - $referencedTable = preg_replace('/_id$/', '', $fieldName); + $referencedTable = (string)preg_replace('/_id$/', '', $fieldName); $referencedTable = Inflector::pluralize($referencedTable); } diff --git a/src/Util/TableFinder.php b/src/Util/TableFinder.php index a96b4e03..5e75c8d6 100644 --- a/src/Util/TableFinder.php +++ b/src/Util/TableFinder.php @@ -183,9 +183,10 @@ public function fetchTableName(string $className, ?string $pluginName = null): a $table = TableRegistry::getTableLocator()->get($className); foreach ($table->associations()->keys() as $key) { - if ($table->associations()->get($key)->type() === 'belongsToMany') { + $association = $table->associations()->get($key); + if ($association !== null && $association->type() === 'belongsToMany') { /** @var \Cake\ORM\Association\BelongsToMany $belongsToMany */ - $belongsToMany = $table->associations()->get($key); + $belongsToMany = $association; $tables[] = $belongsToMany->junction()->getTable(); } } diff --git a/src/View/Helper/MigrationHelper.php b/src/View/Helper/MigrationHelper.php index 3302e7e5..de7b13b8 100644 --- a/src/View/Helper/MigrationHelper.php +++ b/src/View/Helper/MigrationHelper.php @@ -692,7 +692,7 @@ public function getCreateTableData(TableSchemaInterface|string $table): array $indexes = $this->indexes($table); $foreignKeys = []; foreach ($constraints as $constraint) { - if ($constraint['type'] === 'foreign') { + if (isset($constraint['type']) && $constraint['type'] === 'foreign') { $foreignKeys[] = $constraint['columns']; } } From 485b08b969f298542c91e0044213d63c1167e454 Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 15:36:30 -0500 Subject: [PATCH 07/10] Add null guards for constraint diff and fail-fast for null column type Add missing null checks for getConstraint() in the "same name constraints" loop in BakeMigrationDiffCommand to match the guards already present in the "new" and "removed" loops. Replace assert() with an explicit InvalidArgumentException in Table::addColumn() so null type is caught in all environments, not just when assertions are enabled. Add test coverage for the null type case. --- src/Command/BakeMigrationDiffCommand.php | 11 ++++++++--- src/Db/Table.php | 5 +++-- tests/TestCase/Db/Table/TableTest.php | 9 +++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Command/BakeMigrationDiffCommand.php b/src/Command/BakeMigrationDiffCommand.php index d83f0d40..e7910f4f 100644 --- a/src/Command/BakeMigrationDiffCommand.php +++ b/src/Command/BakeMigrationDiffCommand.php @@ -404,13 +404,18 @@ protected function getConstraints(): void // if present in both, check if they are the same : if not, remove the old one and add the new one foreach ($currentConstraints as $constraintName) { $constraint = $currentSchema->getConstraint($constraintName); + if ($constraint === null) { + continue; + } + $oldConstraint = $this->dumpSchema[$table]->getConstraint($constraintName); if ( in_array($constraintName, $oldConstraints, true) && - $constraint !== $this->dumpSchema[$table]->getConstraint($constraintName) + $constraint !== $oldConstraint ) { - $this->templateData[$table]['constraints']['remove'][$constraintName] = - $this->dumpSchema[$table]->getConstraint($constraintName); + if ($oldConstraint !== null) { + $this->templateData[$table]['constraints']['remove'][$constraintName] = $oldConstraint; + } $this->templateData[$table]['constraints']['add'][$constraintName] = $constraint; } diff --git a/src/Db/Table.php b/src/Db/Table.php index 31aee372..cb026b0e 100644 --- a/src/Db/Table.php +++ b/src/Db/Table.php @@ -358,11 +358,12 @@ public function addPrimaryKey(string|array $columns) */ public function addColumn(string|Column $columnName, ?string $type = null, array $options = []) { - assert($columnName instanceof Column || $type !== null); if ($columnName instanceof Column) { $action = new AddColumn($this->table, $columnName); + } elseif ($type === null) { + throw new InvalidArgumentException('Column type must not be null when column name is a string.'); } else { - $action = new AddColumn($this->table, $this->getAdapter()->getColumnForType($columnName, (string)$type, $options)); + $action = new AddColumn($this->table, $this->getAdapter()->getColumnForType($columnName, $type, $options)); } // Delegate to Adapters to check column type diff --git a/tests/TestCase/Db/Table/TableTest.php b/tests/TestCase/Db/Table/TableTest.php index 21912fd5..1be0f352 100644 --- a/tests/TestCase/Db/Table/TableTest.php +++ b/tests/TestCase/Db/Table/TableTest.php @@ -80,6 +80,15 @@ public function testAddColumnWithColumnObject() $this->assertSame($column, $actions[0]->getColumn()); } + public function testAddColumnWithNullTypeThrows() + { + $adapter = new MysqlAdapter([]); + $table = new Table('ntable', [], $adapter); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Column type must not be null when column name is a string.'); + $table->addColumn('email', null); + } + public function testAddColumnWithNoAdapterSpecified() { try { From 4b2d38ee4d0b70ae2a4f08b3caf0cc1e42be3490 Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 15:41:42 -0500 Subject: [PATCH 08/10] Replace assert with RuntimeException in AbstractAdapter::getConnection() Assertions can be disabled in production, which would allow a null connection to slip through silently. Throw a RuntimeException instead to fail fast in all environments. --- src/Db/Adapter/AbstractAdapter.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Db/Adapter/AbstractAdapter.php b/src/Db/Adapter/AbstractAdapter.php index eaa4ab31..5b04e3e6 100644 --- a/src/Db/Adapter/AbstractAdapter.php +++ b/src/Db/Adapter/AbstractAdapter.php @@ -201,7 +201,9 @@ public function getConnection(): Connection $this->connection = $this->getOption('connection'); $this->connect(); } - assert($this->connection !== null); + if ($this->connection === null) { + throw new RuntimeException('Unable to establish database connection. Ensure a connection is configured.'); + } return $this->connection; } From c31c2abb0fe6fa9123a1ea68f58aa80978873fbc Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 16:04:28 -0500 Subject: [PATCH 09/10] Use truthiness checks for optional nullable getters Replace (string) casts with assign-and-check pattern for optional values that can legitimately be null: Index::getWhere(), ForeignKey::getName(), Index::getName(), Column::getGenerated(). This avoids silently converting null to empty string. --- src/Db/Adapter/AbstractAdapter.php | 8 ++++---- src/Db/Adapter/MysqlAdapter.php | 4 ++-- src/Db/Adapter/PostgresAdapter.php | 12 ++++++------ src/Db/Adapter/SqliteAdapter.php | 10 +++++----- src/Db/Adapter/SqlserverAdapter.php | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Db/Adapter/AbstractAdapter.php b/src/Db/Adapter/AbstractAdapter.php index 5b04e3e6..ddc46639 100644 --- a/src/Db/Adapter/AbstractAdapter.php +++ b/src/Db/Adapter/AbstractAdapter.php @@ -1688,19 +1688,19 @@ public function executeActions(TableMetadata $table, array $actions): void )); break; - case $action instanceof DropForeignKey && $action->getForeignKey()->getName(): + case $action instanceof DropForeignKey && ($fkName = $action->getForeignKey()->getName()): /** @var \Migrations\Db\Action\DropForeignKey $action */ $instructions->merge($this->getDropForeignKeyInstructions( $table->getName(), - (string)$action->getForeignKey()->getName(), + $fkName, )); break; - case $action instanceof DropIndex && $action->getIndex()->getName(): + case $action instanceof DropIndex && ($indexName = $action->getIndex()->getName()): /** @var \Migrations\Db\Action\DropIndex $action */ $instructions->merge($this->getDropIndexByNameInstructions( $table->getName(), - (string)$action->getIndex()->getName(), + $indexName, )); break; diff --git a/src/Db/Adapter/MysqlAdapter.php b/src/Db/Adapter/MysqlAdapter.php index 0112756c..9e053145 100644 --- a/src/Db/Adapter/MysqlAdapter.php +++ b/src/Db/Adapter/MysqlAdapter.php @@ -1180,8 +1180,8 @@ protected function getIndexSqlDefinition(Index $index): string protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string { $def = ''; - if ($foreignKey->getName()) { - $def .= ' CONSTRAINT ' . $this->quoteColumnName((string)$foreignKey->getName()); + if ($name = $foreignKey->getName()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($name); } $columnNames = []; foreach ($foreignKey->getColumns() ?? [] as $column) { diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index cb6d6b6d..2e5fd5d1 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -499,11 +499,11 @@ protected function getChangeColumnInstructions( 'ALTER COLUMN %s', $quotedColumnName, ); - if ($newColumn->isIdentity() && $newColumn->getGenerated() !== null) { + if ($newColumn->isIdentity() && ($generated = $newColumn->getGenerated()) !== null) { if ($column->isIdentity()) { - $sql .= sprintf(' SET GENERATED %s', (string)$newColumn->getGenerated()); + $sql .= sprintf(' SET GENERATED %s', $generated); } else { - $sql .= sprintf(' ADD GENERATED %s AS IDENTITY', (string)$newColumn->getGenerated()); + $sql .= sprintf(' ADD GENERATED %s AS IDENTITY', $generated); } } else { $sql .= ' DROP IDENTITY IF EXISTS'; @@ -917,9 +917,9 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin } else { $createIndexSentence .= '(%s)%s%s;'; } - $where = (string)$index->getWhere(); - if ($where) { - $where = ' WHERE ' . $where; + $where = ''; + if ($whereClause = $index->getWhere()) { + $where = ' WHERE ' . $whereClause; } return sprintf( diff --git a/src/Db/Adapter/SqliteAdapter.php b/src/Db/Adapter/SqliteAdapter.php index 00d3024d..a92a8aba 100644 --- a/src/Db/Adapter/SqliteAdapter.php +++ b/src/Db/Adapter/SqliteAdapter.php @@ -1214,9 +1214,9 @@ protected function getAddIndexInstructions(TableMetadata $table, Index $index): $indexColumnArray[] = sprintf('%s ASC', $this->quoteColumnName($column)); } $indexColumns = implode(',', $indexColumnArray); - $where = (string)$index->getWhere(); - if ($where) { - $where = ' WHERE ' . $where; + $where = ''; + if ($whereClause = $index->getWhere()) { + $where = ' WHERE ' . $whereClause; } $sql = sprintf( 'CREATE %s ON %s (%s)%s', @@ -1675,8 +1675,8 @@ public function getColumnTypes(): array protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string { $def = ''; - if ($foreignKey->getName()) { - $def .= ' CONSTRAINT ' . $this->quoteColumnName((string)$foreignKey->getName()); + if ($name = $foreignKey->getName()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($name); } $columnNames = []; foreach ($foreignKey->getColumns() ?? [] as $column) { diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php index d2ebd336..57dc78cb 100644 --- a/src/Db/Adapter/SqlserverAdapter.php +++ b/src/Db/Adapter/SqlserverAdapter.php @@ -839,9 +839,9 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin $include = $index->getInclude(); $includedColumns = $include ? sprintf(' INCLUDE ([%s])', implode('],[', $include)) : ''; - $where = (string)$index->getWhere(); - if ($where) { - $where = ' WHERE ' . $where; + $where = ''; + if ($whereClause = $index->getWhere()) { + $where = ' WHERE ' . $whereClause; } return sprintf( From 2201ef8154054c470150867b45f5121a11e3ff60 Mon Sep 17 00:00:00 2001 From: Jamison Bryant Date: Tue, 24 Feb 2026 16:51:46 -0500 Subject: [PATCH 10/10] Throw on null for required Column::getName() and FK::getReferencedTable() Replace (string) casts with explicit null checks and InvalidArgumentException throws for values that must be present: Column::getName() in SQL generation and ForeignKey::getReferencedTable() in FK definition builders. A column without a name or a foreign key without a referenced table are always programming errors that should fail fast rather than silently produce broken SQL. --- src/Db/Action/ChangeColumn.php | 2 +- src/Db/Adapter/AbstractAdapter.php | 12 ++++++++-- src/Db/Adapter/MysqlAdapter.php | 8 +++++-- src/Db/Adapter/PostgresAdapter.php | 21 ++++++++++++---- src/Db/Adapter/RecordingAdapter.php | 6 ++++- src/Db/Adapter/SqliteAdapter.php | 11 +++++++-- src/Db/Adapter/SqlserverAdapter.php | 37 ++++++++++++++++++++++------- src/Db/Table.php | 21 ++++++++++++---- 8 files changed, 93 insertions(+), 25 deletions(-) diff --git a/src/Db/Action/ChangeColumn.php b/src/Db/Action/ChangeColumn.php index 267e30aa..ce53e73e 100644 --- a/src/Db/Action/ChangeColumn.php +++ b/src/Db/Action/ChangeColumn.php @@ -41,7 +41,7 @@ public function __construct(TableMetadata $table, string $columnName, Column $co $this->column = $column; // if the name was omitted use the existing column name - if ($column->getName() === null || strlen((string)$column->getName()) === 0) { + if ($column->getName() === null || strlen($column->getName()) === 0) { $column->setName($columnName); } } diff --git a/src/Db/Adapter/AbstractAdapter.php b/src/Db/Adapter/AbstractAdapter.php index ddc46639..a4309e9c 100644 --- a/src/Db/Adapter/AbstractAdapter.php +++ b/src/Db/Adapter/AbstractAdapter.php @@ -1721,17 +1721,25 @@ public function executeActions(TableMetadata $table, array $actions): void case $action instanceof RemoveColumn: /** @var \Migrations\Db\Action\RemoveColumn $action */ + $columnName = $action->getColumn()->getName(); + if ($columnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } $instructions->merge($this->getDropColumnInstructions( $table->getName(), - (string)$action->getColumn()->getName(), + $columnName, )); break; case $action instanceof RenameColumn: /** @var \Migrations\Db\Action\RenameColumn $action */ + $columnName = $action->getColumn()->getName(); + if ($columnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } $instructions->merge($this->getRenameColumnInstructions( $table->getName(), - (string)$action->getColumn()->getName(), + $columnName, $action->getNewName(), )); break; diff --git a/src/Db/Adapter/MysqlAdapter.php b/src/Db/Adapter/MysqlAdapter.php index 9e053145..2d55207b 100644 --- a/src/Db/Adapter/MysqlAdapter.php +++ b/src/Db/Adapter/MysqlAdapter.php @@ -710,7 +710,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column $targetColumn = null; foreach ($columns as $column) { - if (strcasecmp((string)$column->getName(), $columnName) === 0) { + if ($column->getName() !== null && strcasecmp($column->getName(), $columnName) === 0) { $targetColumn = $column; break; } @@ -1192,7 +1192,11 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string foreach ($foreignKey->getReferencedColumns() as $column) { $refColumnNames[] = $this->quoteColumnName($column); } - $def .= ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . implode(',', $refColumnNames) . ')'; + $referencedTable = $foreignKey->getReferencedTable(); + if ($referencedTable === null) { + throw new InvalidArgumentException('Foreign key must have a referenced table.'); + } + $def .= ' REFERENCES ' . $this->quoteTableName($referencedTable) . ' (' . implode(',', $refColumnNames) . ')'; $onDelete = $foreignKey->getOnDelete(); if ($onDelete) { $def .= ' ON DELETE ' . $onDelete; diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index 2e5fd5d1..ee2ccf84 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -540,12 +540,16 @@ protected function getChangeColumnInstructions( } // rename column - if ($columnName !== $newColumn->getName()) { + $newColumnName = $newColumn->getName(); + if ($columnName !== $newColumnName) { + if ($newColumnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } $instructions->addPostStep(sprintf( 'ALTER TABLE %s RENAME COLUMN %s TO %s', $this->quoteTableName($tableName), $quotedColumnName, - $this->quoteColumnName((string)$newColumn->getName()), + $this->quoteColumnName($newColumnName), )); } @@ -867,6 +871,11 @@ public function dropDatabase($name): void */ protected function getColumnCommentSqlDefinition(Column $column, string $tableName): string { + $columnName = $column->getName(); + if ($columnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } + $comment = (string)$column->getComment(); // passing 'null' is to remove column comment $comment = strcasecmp($comment, 'NULL') !== 0 @@ -876,7 +885,7 @@ protected function getColumnCommentSqlDefinition(Column $column, string $tableNa return sprintf( 'COMMENT ON COLUMN %s.%s IS %s;', $this->quoteTableName($tableName), - $this->quoteColumnName((string)$column->getName()), + $this->quoteColumnName($columnName), $comment, ); } @@ -950,9 +959,13 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta ); $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns() ?? [])); $refColumnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getReferencedColumns())); + $referencedTable = $foreignKey->getReferencedTable(); + if ($referencedTable === null) { + throw new InvalidArgumentException('Foreign key must have a referenced table.'); + } $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName) . ' FOREIGN KEY (' . $columnList . ')' . - ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; + ' REFERENCES ' . $this->quoteTableName($referencedTable) . ' (' . $refColumnList . ')'; if ($foreignKey->getOnDelete()) { $def .= " ON DELETE {$foreignKey->getOnDelete()}"; } diff --git a/src/Db/Adapter/RecordingAdapter.php b/src/Db/Adapter/RecordingAdapter.php index aab4b41b..b44a09f6 100644 --- a/src/Db/Adapter/RecordingAdapter.php +++ b/src/Db/Adapter/RecordingAdapter.php @@ -12,6 +12,7 @@ use Migrations\Db\Action\AddForeignKey; use Migrations\Db\Action\AddIndex; use Migrations\Db\Action\CreateTable; +use InvalidArgumentException; use Migrations\Db\Action\DropForeignKey; use Migrations\Db\Action\DropIndex; use Migrations\Db\Action\DropTable; @@ -89,7 +90,10 @@ public function getInvertedCommands(): Intent case $command instanceof RenameColumn: /** @var \Migrations\Db\Action\RenameColumn $command */ $column = clone $command->getColumn(); - $name = (string)$column->getName(); + $name = $column->getName(); + if ($name === null) { + throw new InvalidArgumentException('Column name must be set.'); + } $column->setName($command->getNewName()); $inverted->addAction(new RenameColumn($command->getTable(), $column, $name)); break; diff --git a/src/Db/Adapter/SqliteAdapter.php b/src/Db/Adapter/SqliteAdapter.php index a92a8aba..76262f3a 100644 --- a/src/Db/Adapter/SqliteAdapter.php +++ b/src/Db/Adapter/SqliteAdapter.php @@ -1112,7 +1112,10 @@ protected function getChangeColumnInstructions(string $tableName, string $column { $instructions = $this->beginAlterByCopyTable($tableName); - $newColumnName = (string)$newColumn->getName(); + $newColumnName = $newColumn->getName(); + if ($newColumnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } $instructions->addPostStep(function ($state) use ($columnName, $newColumn) { $dialect = $this->getSchemaDialect(); $sql = (string)preg_replace( @@ -1687,7 +1690,11 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string foreach ($foreignKey->getReferencedColumns() as $column) { $refColumnNames[] = $this->quoteColumnName($column); } - $def .= ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . implode(',', $refColumnNames) . ')'; + $referencedTable = $foreignKey->getReferencedTable(); + if ($referencedTable === null) { + throw new InvalidArgumentException('Foreign key must have a referenced table.'); + } + $def .= ' REFERENCES ' . $this->quoteTableName($referencedTable) . ' (' . implode(',', $refColumnNames) . ')'; if ($foreignKey->getOnDelete()) { $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); } diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php index 57dc78cb..88b0f665 100644 --- a/src/Db/Adapter/SqlserverAdapter.php +++ b/src/Db/Adapter/SqlserverAdapter.php @@ -213,8 +213,16 @@ protected function getChangeCommentInstructions(TableMetadata $table, ?string $n */ protected function getColumnCommentSqlDefinition(Column $column, ?string $tableName): string { + $columnName = $column->getName(); + if ($columnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } + if ($tableName === null) { + throw new InvalidArgumentException('Table name must be set.'); + } + // passing 'null' is to remove column comment - $currentComment = $this->getColumnComment((string)$tableName, $column->getName()); + $currentComment = $this->getColumnComment($tableName, $columnName); $comment = strcasecmp((string)$column->getComment(), 'NULL') !== 0 ? $this->quoteString((string)$column->getComment()) : '\'\''; $command = $currentComment === null ? 'sp_addextendedproperty' : 'sp_updateextendedproperty'; @@ -224,8 +232,8 @@ protected function getColumnCommentSqlDefinition(Column $column, ?string $tableN $command, $comment, $this->schema, - (string)$tableName, - (string)$column->getName(), + $tableName, + $columnName, ); } @@ -420,12 +428,16 @@ protected function getChangeDefault(string $tableName, Column $newColumn): Alter return $instructions; } + $newColumnName = $newColumn->getName(); + if ($newColumnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } $instructions->addPostStep(sprintf( 'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s', $this->quoteTableName($tableName), $constraintName, $default, - $this->quoteColumnName((string)$newColumn->getName()), + $this->quoteColumnName($newColumnName), )); return $instructions; @@ -455,14 +467,19 @@ protected function getChangeColumnInstructions(string $tableName, string $column $instructions = new AlterInstructions(); $dialect = $this->getSchemaDialect(); - if ($columnName !== $newColumn->getName()) { + $newColumnName = $newColumn->getName(); + if ($newColumnName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } + + if ($columnName !== $newColumnName) { $instructions->merge( - $this->getRenameColumnInstructions($tableName, $columnName, (string)$newColumn->getName()), + $this->getRenameColumnInstructions($tableName, $columnName, $newColumnName), ); } if ($changeDefault) { - $instructions->merge($this->getDropDefaultConstraint($tableName, (string)$newColumn->getName())); + $instructions->merge($this->getDropDefaultConstraint($tableName, $newColumnName)); } // Sqlserver doesn't support defaults @@ -870,7 +887,11 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName); $def .= ' FOREIGN KEY (' . $columnList . ')'; - $def .= ' REFERENCES ' . $this->quoteTableName((string)$foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; + $referencedTable = $foreignKey->getReferencedTable(); + if ($referencedTable === null) { + throw new InvalidArgumentException('Foreign key must have a referenced table.'); + } + $def .= ' REFERENCES ' . $this->quoteTableName($referencedTable) . ' (' . $refColumnList . ')'; if ($foreignKey->getOnDelete()) { $def .= " ON DELETE {$foreignKey->getOnDelete()}"; } diff --git a/src/Db/Table.php b/src/Db/Table.php index cb026b0e..de77eb81 100644 --- a/src/Db/Table.php +++ b/src/Db/Table.php @@ -367,11 +367,16 @@ public function addColumn(string|Column $columnName, ?string $type = null, array } // Delegate to Adapters to check column type - if (!$this->getAdapter()->isValidColumnType($action->getColumn())) { + $column = $action->getColumn(); + $colName = $column->getName(); + if ($colName === null) { + throw new InvalidArgumentException('Column name must be set.'); + } + if (!$this->getAdapter()->isValidColumnType($column)) { throw new InvalidArgumentException(sprintf( 'An invalid column type "%s" was specified for column "%s".', - (string)$action->getColumn()->getType(), - (string)$action->getColumn()->getName(), + $column->getType(), + $colName, )); } @@ -903,7 +908,9 @@ protected function filterPrimaryKey(array $options): void return $action->getColumn(); }); $primaryKeyColumns = $columnsCollection->filter(function (Column $columnDef, $key) use ($primaryKey) { - return isset($primaryKey[(string)$columnDef->getName()]); + $name = $columnDef->getName(); + + return $name !== null && isset($primaryKey[$name]); })->toArray(); if (!$primaryKeyColumns) { @@ -912,7 +919,11 @@ protected function filterPrimaryKey(array $options): void foreach ($primaryKeyColumns as $primaryKeyColumn) { if ($primaryKeyColumn->isIdentity()) { - unset($primaryKey[(string)$primaryKeyColumn->getName()]); + $pkColName = $primaryKeyColumn->getName(); + if ($pkColName === null) { + continue; + } + unset($primaryKey[$pkColName]); } }