Skip to content

Raise PHPStan level to 8 with null safety fixes#1044

Merged
dereuromark merged 25 commits into5.nextfrom
fix/phpstan-level-8-v2
Mar 11, 2026
Merged

Raise PHPStan level to 8 with null safety fixes#1044
dereuromark merged 25 commits into5.nextfrom
fix/phpstan-level-8-v2

Conversation

@jamisonbryant
Copy link
Contributor

@jamisonbryant jamisonbryant commented Mar 11, 2026

Summary

  • Bumps PHPStan analysis level from 7 to 8, addressing all null safety violations across the codebase
  • Narrows return types on Column::getName() and ForeignKey::getColumns() to non-nullable, removing dead null checks downstream
  • Adds proper null guards and RuntimeException throws where null values indicate genuine error conditions (e.g., AbstractAdapter::getConnection(), column diff loops)
  • Replaces loose null checks with truthiness checks for optional nullable getters
  • Includes tests for the new type constraints and null-safety behavior

Changes

Type narrowing:

  • Column::getName() returns string (not ?string), with property default of ''
  • ForeignKey::getColumns() returns string[] (not ?string[])

Null safety patterns:

  • AbstractAdapter::getConnection() throws RuntimeException instead of using assert()
  • BakeMigrationDiffCommand guards against null columns in diff loops
  • Drop FK/index instructions cast nullable getName() to string
  • Adapters use truthiness checks for optional nullable return values

Also included (pre-existing commits on 5.next):

  • TYPE_BIT constant added to AdapterInterface
  • Fix upgrade command not matching plugins with slashes
  • Fix TEXT column variants not round-tripping correctly
  • Fix for JSON column type change
  • Additional empty array check

Test plan

  • PHPStan passes at level 8 (composer stan -- --memory-limit=512M)
  • Full test suite passes on SQLite, MySQL, and Postgres
  • No behavioral changes to migration generation or execution

swiffer and others added 21 commits March 10, 2026 22:58
1. Handle uninitialized Column::$fixed property in BakeMigrationDiffCommand

   When TableSchema::getColumn() is called on cached/serialized schema
   objects, the Column::$fixed property may not be initialized, causing
   an Error. Added safeGetColumn() helper that catches this error and
   uses reflection to initialize uninitialized properties before retry.

2. Fix CURRENT_TIMESTAMP test assertion for MySQL/MariaDB

   Different versions of MySQL and MariaDB return CURRENT_TIMESTAMP
   in different formats (CURRENT_TIMESTAMP, current_timestamp(),
   CURRENT_TIMESTAMP()). Changed the test to use a regex that accepts
   all valid formats case-insensitively.

Fixes #1033
When using migration_diff, TEXT column variants (TINYTEXT, MEDIUMTEXT,
LONGTEXT) were not being properly mapped back from the database. The
BLOB type handling already used rawType to distinguish BLOB variants,
but TEXT variants were missing equivalent handling.

This adds similar rawType-based mapping for TEXT columns in
mapColumnType() and includes round-trip tests.

Fixes #1029
Add support for the `fixed` attribute on binary columns to distinguish
between fixed-length BINARY and variable-length VARBINARY types.
This mirrors cakephp/cakephp#19207.

* Add test cases for fixed option on binary columns

Tests cover:
- Column class getter/setter for fixed option
- Column::toArray() including fixed in output
- Column::setOptions() accepting fixed option
- MigrationHelper::getColumnOption() including/excluding fixed
- MysqlAdapter creating BINARY vs VARBINARY based on fixed option

* Add column type assertions to binary fixed test
Only show 'Set legacyTables => false' step after tables have been
dropped, as the upgrade command becomes unavailable once this config
is set.

Fixes #1036
* Fix upgrade command not matching plugins with slashes

When upgrading from legacy phinxlog tables, the plugin name was being
derived by camelizing the table prefix (e.g. cake_d_c_users -> CakeDCUsers).
This didn't work for plugins with slashes in their names like CakeDC/Users
since the slash was replaced with underscore during table name generation.

This fix builds a map of loaded plugin names to their expected table
prefixes, allowing proper matching of plugins like CakeDC/Users.

Fixes #1038

* Fix test for SQL Server compatibility
* Add TYPE_BIT constant to AdapterInterface

Adds the TYPE_BIT constant mapping to TableSchemaInterface::TYPE_BIT
for consistency with the core BIT type support added in cakephp/cakephp#19223.

This allows migration users to reference AdapterInterface::TYPE_BIT
when defining BIT columns in MySQL migrations.

* Bump cakephp/database constraint to ^5.3.2 for TYPE_BIT support
Column::getNull() now coalesces the nullable bool property to
false. ForeignKey::getOnDelete()/getOnUpdate() guard against null
from getDelete()/getUpdate() before calling mapAction().
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.
Add null guards for constraint diff, fail-fast for null column type,
fix import order, and remaining null safety in BaseSeed, ColumnParser,
TableFinder, and MigrationHelper.
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.
Replace (string) casts with assign-then-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.
…le()

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.
Override getColumns() in Migrations ForeignKey to return array instead
of the parent's ?array. The $columns property is always initialized as
[] in the constructor, making null unreachable. This is a covariant
return type narrowing, same pattern used for Column::getNull().

Removes the now-unnecessary ?? [] fallbacks from all 6 call sites
across the adapter and plan classes.
Override getName() in Migrations Column to return string instead of the
parent's ?string. The $name property is typed as string (not ?string)
in the constructor, making null unreachable. This is a covariant return
type narrowing, same pattern used for ForeignKey::getColumns().

Removes now-unnecessary null checks across adapter/action files and
the dead null guard in Table::setPrimaryKey().
Use early-continue pattern to narrow nullable safeGetColumn() return
types before array operations, fixing PHPStan level 8 errors.
Remove unused InvalidArgumentException import from RecordingAdapter,
extract assignment out of if condition in PostgresAdapter, and remove
blank line before closing brace in ForeignKeyTest.
@jamisonbryant jamisonbryant self-assigned this Mar 11, 2026
@jamisonbryant jamisonbryant added this to the 5.next milestone Mar 11, 2026
@dereuromark
Copy link
Member

#1043
With this merged, the diff should now be clear again and the PR reviewable.

Generated migration snapshots can emit 'fixed' => null for binary
columns, which causes a TypeError since setFixed() only accepted
bool. Widen the parameter to ?bool to match the property and getter
types. Fixes #1046.
@dereuromark dereuromark merged commit 9cdbada into 5.next Mar 11, 2026
14 checks passed
@dereuromark dereuromark deleted the fix/phpstan-level-8-v2 branch March 11, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants