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: 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/Command/BakeMigrationDiffCommand.php b/src/Command/BakeMigrationDiffCommand.php index 68bcd71b..e7910f4f 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 { @@ -395,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; } @@ -411,6 +425,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 { 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 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 3bb4a81b..a4309e9c 100644 --- a/src/Db/Adapter/AbstractAdapter.php +++ b/src/Db/Adapter/AbstractAdapter.php @@ -201,6 +201,9 @@ public function getConnection(): Connection $this->connection = $this->getOption('connection'); $this->connect(); } + if ($this->connection === null) { + throw new RuntimeException('Unable to establish database connection. Ensure a connection is configured.'); + } return $this->connection; } @@ -1681,23 +1684,23 @@ 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; - 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; @@ -1718,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 b1d8a182..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($column->getName(), $columnName) === 0) { + if ($column->getName() !== null && strcasecmp($column->getName(), $columnName) === 0) { $targetColumn = $column; break; } @@ -1180,11 +1180,11 @@ 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) { + foreach ($foreignKey->getColumns() ?? [] as $column) { $columnNames[] = $this->quoteColumnName($column); } $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; @@ -1192,7 +1192,11 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string foreach ($foreignKey->getReferencedColumns() as $column) { $refColumnNames[] = $this->quoteColumnName($column); } - $def .= ' REFERENCES ' . $this->quoteTableName($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 5251ce4a..ee2ccf84 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)', @@ -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'; @@ -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, ); } @@ -917,9 +926,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( @@ -946,13 +955,17 @@ 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())); + $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($foreignKey->getReferencedTable()) . ' (' . $refColumnList . ')'; + ' REFERENCES ' . $this->quoteTableName($referencedTable) . ' (' . $refColumnList . ')'; if ($foreignKey->getOnDelete()) { $def .= " ON DELETE {$foreignKey->getOnDelete()}"; } @@ -1321,7 +1334,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/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 9145e0cb..76262f3a 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; } @@ -1112,10 +1112,13 @@ 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 = preg_replace( + $sql = (string)preg_replace( sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)), sprintf('%s$1', $dialect->columnDefinitionSql($newColumn->toArray())), (string)$state['createSQL'], @@ -1149,7 +1152,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'], @@ -1214,9 +1217,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,11 +1678,11 @@ 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) { + foreach ($foreignKey->getColumns() ?? [] as $column) { $columnNames[] = $this->quoteColumnName($column); } $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; @@ -1687,7 +1690,11 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string foreach ($foreignKey->getReferencedColumns() as $column) { $refColumnNames[] = $this->quoteColumnName($column); } - $def .= ' REFERENCES ' . $this->quoteTableName($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 e804d590..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, ); } @@ -338,7 +346,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; @@ -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 @@ -475,7 +492,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()) { @@ -839,9 +856,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( @@ -864,13 +881,17 @@ 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 . ')'; + $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/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..de77eb81 100644 --- a/src/Db/Table.php +++ b/src/Db/Table.php @@ -358,19 +358,25 @@ 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, $type, $options)); } // 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, )); } @@ -902,7 +908,9 @@ protected function filterPrimaryKey(array $options): void return $action->getColumn(); }); $primaryKeyColumns = $columnsCollection->filter(function (Column $columnDef, $key) use ($primaryKey) { - return isset($primaryKey[$columnDef->getName()]); + $name = $columnDef->getName(); + + return $name !== null && isset($primaryKey[$name]); })->toArray(); if (!$primaryKeyColumns) { @@ -911,7 +919,11 @@ protected function filterPrimaryKey(array $options): void foreach ($primaryKeyColumns as $primaryKeyColumn) { if ($primaryKeyColumn->isIdentity()) { - unset($primaryKey[$primaryKeyColumn->getName()]); + $pkColName = $primaryKeyColumn->getName(); + if ($pkColName === null) { + continue; + } + unset($primaryKey[$pkColName]); } } 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; } } 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']; } } 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 {