Upgrade Guide (1.x to 2.x)
2.0 fixed many 1.x behaviours that returned wrong rows or were unsafe, so some code has to change. 2.1 adds async queries and changes nothing else for existing code (except SQL Server number types, below). Test your application against a copy of the database before you switch.
Checklist
- PHP 8.1 or newer.
- Primary key default is now
Id(wasid). - Timestamps are opt-in: add
use HasTimestamps;to models that haveCreatedDate/UpdatedDate. - List mass-assignable columns in
$fillable(or use$guarded). - Replace expressions in
select()/where()/orderBy()with the*Raw()methods. - Replace
User::fresh()/User::refresh()(migration helpers) withUser::recreateTable(). - Set
ENCRYPTION_KEYin.envand re-encrypt oldFormCrypt/Cryptodata. - Review
HttpClientcalls that used the$asyncparameter.
Breaking changes
| 1.x | 2.x |
|---|---|
| PHP 8.0 | PHP 8.1+ |
$primaryKey = 'id' | $primaryKey = 'Id' |
| Timestamps always on | use HasTimestamps; to enable (const CREATED_AT / UPDATED_AT to rename) |
create() / new Model($data) ignored $fillable | $fillable / $guarded enforced, primary key always guarded; forceCreate() / forceFill() skip the check |
Relation keys guessed as user_id | {Model}{PrimaryKey}, e.g. UserId (user_id when the primary key is id) |
select('CONCAT(a,b)'), where('DATE(x)', ...) | selectRaw(), whereRaw(), whereDate(); COUNT/SUM/AVG/MIN/MAX(col) and col AS alias still work |
$model->update([...]) could update the whole table | updates that row only; mass updates need a query: User::where(...)->update([...]) |
$model->delete (property) ran the method | property access only loads relations declared in your model |
User::fresh() / User::refresh() (migration) | User::recreateTable(); fresh() / refresh() now reload a model |
Query delete() hard-deleted soft-delete models | soft deletes; forceDelete() removes rows |
whereLike() passed % / _ through | escaped; whereLike($col, $pattern, false) for a raw pattern |
latest() used CreateDate | CreatedDate (or the model's created-at column) |
Query\QueryBuilder::getBindings() returned a list | named bindings [':q1_0' => value] |
autoload registered error handlers and ../Cargo/bootstrap.php | opt-in MIKO_REGISTER_ERROR_HANDLERS, no Cargo include |
FormCrypt fallback key, no MAC | ENCRYPTION_KEY required, v2: payloads; decryptLegacy() reads 1.x data |
Security::getClientIp() trusted X-Forwarded-For | only from TRUSTED_PROXIES / setTrustedProxies() |
HttpClient::get($url, $headers, $async) | get($url, $headers, $query); parallel calls with pool() or getAsync() |
HttpClient::request(..., $async, $multipart) | request($method, $url, $body, $headers, $options) with query, form, multipart, sink, timeout, retries |
HttpClient::post($url) sent [] | sends an empty body; post/put/patch default $data = null |
ORM\ConnectionPool::acquire() waited 30 s when full | throws immediately |
DbContext::ensureCreated() printed table names | returns them; Migrator::ensureDatabaseExists() is silent, seeders print on the CLI only |
Config/Database.php persistent on | persistent off by default; MySQL lc_time_names opt-in (DB_LC_TIME_NAMES); migrations table __migrations |
| SQL Server returned int columns as strings | int / float come back as int / float (2.1); bigint, decimal and money stay strings |
One shared connection
Models, Transaction, DB and the builders use the same default connection, resolved in this order:
ConnectionResolver::setDefault($connection)(or aDbContextyou created),- the last
DbConfig::...()->connect(), - the default connection of
Config/Database.php(.env).
So a Transaction::run() now really contains the model writes made inside it.
Re-encrypting old data
use Miko\Security\FormCrypt;
$plain = FormCrypt::decryptLegacy($row['Token']); // 1.x payload
if ($plain !== false) {
DB::execute('UPDATE Tokens SET Token = ? WHERE Id = ?', [FormCrypt::encrypt($plain), $row['Id']]);
}
Crypto::decryptLegacy($data, $key) does the same for Crypto values.
Running the tests on your server
The library ships its test suite. Run it against an empty database before upgrading production:
php tests/run.php # SQLite in a temp folder
MIKO_TEST_DRIVER=mysql MIKO_TEST_HOST=127.0.0.1 MIKO_TEST_PORT=3306 MIKO_TEST_DATABASE=miko_test \
MIKO_TEST_USERNAME=root MIKO_TEST_PASSWORD=secret php tests/run.php
php -d extension=soap tests/http.php # HTTP clients
The database must exist; the suite drops its miko_t_* tables at the end. The full list of changes is in CHANGELOG.md.