|
| 1 | +# Upgrading from 1.x to 2.x |
| 2 | + |
| 3 | +Version 2.x is a ground-up rewrite. The biggest addition is a live-database schema layer that infers |
| 4 | +precise Model and Entity types, and the superglobals rules were split and renamed. This guide covers the |
| 5 | +changes and how to migrate. |
| 6 | + |
| 7 | +## Requirements |
| 8 | + |
| 9 | +| Dependency | 1.x | 2.x | |
| 10 | +| --------------- | --------- | ----------------------- | |
| 11 | +| PHP | `^8.2` | `^8.2` | |
| 12 | +| PHPStan | `^2.0` | `^2.2` | |
| 13 | +| CodeIgniter 4 | `^4.6` | `^4.7` | |
| 14 | +| PHP extensions | (none) | `sqlite3` (new) | |
| 15 | + |
| 16 | +The new `sqlite3` extension powers the Model and Entity type inference. The extension materializes your |
| 17 | +schema by running your migrations against a throwaway in-repo SQLite database, then reads the resulting |
| 18 | +columns. Without `ext-sqlite3`, the schema-derived types are unavailable (PHPStan still runs, but Model and |
| 19 | +Entity properties fall back to their framework types). |
| 20 | + |
| 21 | +## What's new |
| 22 | + |
| 23 | +### Model and Entity type inference |
| 24 | + |
| 25 | +2.x derives types from your actual database schema instead of heuristics: |
| 26 | + |
| 27 | +- `Model::find()`, `findAll()`, `first()`, and `findColumn()` return the precise row type for the model's |
| 28 | + `$returnType` (an entity, a shaped `array{...}` of columns, or a `stdClass`), honoring `asArray()`, |
| 29 | + `asObject()`, and the `select()` field list (aliases, `table.*`, and joined columns). |
| 30 | +- Entity virtual properties are typed from the entity's `$casts` and `$dates` (including custom |
| 31 | + `$castHandlers`) layered over the backing column. |
| 32 | +- `fake()` returns a single fabricated record of the model's return type. |
| 33 | + |
| 34 | +See [type-inference.md](type-inference.md) for the full list. |
| 35 | + |
| 36 | +These types are sharper than 1.x, so the upgrade may surface genuine type errors that were previously |
| 37 | +invisible. For example, a `datetime`-cast property over a non-null column is now exactly `Time` (not |
| 38 | +`Time|null`), which can reveal redundant null checks. |
| 39 | + |
| 40 | +## Breaking changes |
| 41 | + |
| 42 | +### Superglobals rules were renamed and split |
| 43 | + |
| 44 | +The two 1.x superglobals rules became four, with new identifiers and reworded messages: |
| 45 | + |
| 46 | +| 1.x | 2.x | |
| 47 | +| ---------------------------- | ------------------------------------------------------------------------- | |
| 48 | +| `SuperglobalAccessRule` | `SuperglobalsOffsetAccessRule` (`codeigniter.superglobalsOffsetAccess`) | |
| 49 | +| `SuperglobalAssignRule` | `SuperglobalsOffsetAssignRule` (`codeigniter.superglobalsOffsetAssign`) | |
| 50 | +| | `SuperglobalsOffsetUnsetRule` (`codeigniter.superglobalsOffsetUnset`) | |
| 51 | +| | `SuperglobalsGlobalAssignRule` (`codeigniter.superglobalsGlobalAssign`) | |
| 52 | + |
| 53 | +Whole-array assignment such as `$_SERVER = [...]` is now reported by `SuperglobalsGlobalAssignRule`. Because |
| 54 | +the identifiers and messages changed, any baseline entries for the old rules will no longer match and must be |
| 55 | +regenerated. |
| 56 | + |
| 57 | +Most of these errors are auto-fixable, so you can fix them in place instead of baselining them. |
| 58 | +`SuperglobalsOffsetAccessRule` and `SuperglobalsOffsetAssignRule` are fully fixable, and |
| 59 | +`SuperglobalsGlobalAssignRule` is partially fixable (single-element array assignments). Running PHPStan's |
| 60 | +fixer (`vendor/bin/phpstan analyse --fix`) rewrites the direct access into the equivalent |
| 61 | +`service('superglobals')` call, for example `$_SERVER['argv']` becomes |
| 62 | +`service('superglobals')->server('argv')`. |
| 63 | + |
| 64 | +### `model()`/`config()` with a class string is no longer discouraged |
| 65 | + |
| 66 | +The 1.x `NoClassConstFetchOnFactoriesFunctions` rule (`codeigniter.factoriesClassConstFetch`) has been |
| 67 | +removed. Passing `Foo::class` to `model()` or `config()` is no longer reported. Remove the corresponding |
| 68 | +baseline entries. |
| 69 | + |
| 70 | +## Configuration |
| 71 | + |
| 72 | +Two parameters were added for the schema layer: |
| 73 | + |
| 74 | +```yml |
| 75 | +parameters: |
| 76 | + codeigniter: |
| 77 | + # Restrict schema introspection to a single namespace. By default every registered namespace is |
| 78 | + # scanned (your app plus installed packages), so a library analyzed on its own works out of the box. |
| 79 | + schemaNamespace: Acme\Blog |
| 80 | + |
| 81 | + # Where the schema cache is written. Defaults to the `tmp` directory of the working directory. |
| 82 | + schemaCacheDirectory: %currentWorkingDirectory%/writable/cache |
| 83 | +``` |
| 84 | +
|
| 85 | +Both are optional. The defaults work for a typical application. |
| 86 | +
|
| 87 | +## Migration steps |
| 88 | +
|
| 89 | +1. Bump the constraint: |
| 90 | +
|
| 91 | + ```console |
| 92 | + composer require --dev "codeigniter/phpstan-codeigniter:^2.0" |
| 93 | + ``` |
| 94 | + |
| 95 | +2. Make sure the SQLite extension is available: |
| 96 | + |
| 97 | + ```console |
| 98 | + php -m | grep sqlite3 |
| 99 | + ``` |
| 100 | + |
| 101 | +3. Run PHPStan and expect churn against your existing baseline: entries for the renamed superglobals rules |
| 102 | + and the removed `model()`/`config()` discouragement will no longer match, and the sharper Model/Entity |
| 103 | + types may add new errors. |
| 104 | + |
| 105 | +4. Optionally, auto-rewrite the fixable superglobals access (see above) instead of baselining it: |
| 106 | + |
| 107 | + ```console |
| 108 | + vendor/bin/phpstan analyse --fix |
| 109 | + ``` |
| 110 | + |
| 111 | + The `--fix` flag is experimental. |
| 112 | + |
| 113 | +5. Review the newly reported errors before baselining. Some are genuine findings from the more precise |
| 114 | + inference, not noise. |
| 115 | + |
| 116 | +6. Regenerate the baseline once you have triaged the real issues: |
| 117 | + |
| 118 | + ```console |
| 119 | + vendor/bin/phpstan analyse --generate-baseline |
| 120 | + ``` |
| 121 | + |
| 122 | +## Known limitations |
| 123 | + |
| 124 | +- **Models that set `$table` in the constructor are not mapped.** The entity-to-table bridge reads `$table` |
| 125 | + from the model's default property value. A model that assigns `$this->table` inside its constructor is not |
| 126 | + resolved, so its entity's non-cast properties fall back to `mixed`. |
| 127 | +- **Only migrations build the schema.** Tables created outside migrations (for example, in test setup) are |
| 128 | + not introspected. |
| 129 | +- **A non-constant `select()` degrades the shape.** When the `select()` argument is not a constant string, |
| 130 | + the row type falls back to `array<string, mixed>` rather than a precise shape. |
0 commit comments