Skip to content

Commit aa91ccb

Browse files
committed
Add a 1.x to 2.x upgrading guide
1 parent f236fef commit aa91ccb

1 file changed

Lines changed: 130 additions & 0 deletions

File tree

docs/upgrading.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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

Comments
 (0)