Upgrade Laravel
Kód psaný pro Laravel 5.8 volá str_slug() a array_get(), které jsou dnes
Str::slug() a Arr::get(). Balíček dresscode/upgrade-laravel nese 179 takových změn
frameworku od verze 6 po současnou, od helperů přes Request::get() po sloupce migrací, které zrušila verze
11. Přes sto z nich DressCode rovnou opraví a u zbytku řekne, co napsat místo toho. A u modelů, příkazů a jobů
nabídne zápis atributy, jak to dělá Laravel 13.
Instalace
Aplikaci, která ještě běží na PHP starším než 8.4, upgradujte s DressCode nainstalovaným globálně, protože sám potřebuje PHP 8.4 (viz Kde DressCode běží):
composer global require dresscode/dresscode dresscode/upgrade-laravel phpstan/phpstan
dresscode upgrade today
upgrade today vezme data i přepisy balíčku sám, bez konfigurace, a styl kódu nechá, jak je. Uplatní je jen
tehdy, když projekt má laravel/framework, a jen pro verze, na které projekt dosáhne. Postup po jedné hlavní verzi a co dělat s tím,
co zbude, popisuje Upgrade kódu. Projekt, který už běží na PHP 8.4 až 8.6,
si balíček může přidat i jako vývojovou závislost:
composer require --dev dresscode/upgrade-laravel phpstan/phpstan, a pak spouští
vendor/bin/dresscode upgrade today.
Larastan v projektu nevadí, naopak: ať ho máte v phpstan.neon, nebo ho zaregistroval
phpstan/extension-installer, DressCode spustí aplikaci stejně jako PHPStan a volání fasády
Request::get() čte jako metodu třídy, která za ní stojí. Selže-li při tom bootstrap/app.php,
vypíše Larastan sám hlášku „Application bootstrap failed“ i s výjimkou, na které start aplikace selhal, a celý běh
ukončí. Pozor v CI: běh skončí nenulovým exit kódem, a když běží v jednom procesu, je to 1, stejně jako když
DressCode v kódu něco najde. Alias z config/app.php psaný bez jmenného prostoru, \Request::get(),
nezná žádná statická analýza, takže takové volání zůstane nepovšimnuté.
Co se opraví
Tohle je middleware, provider a controller psané pro Laravel 10, v projektu na Laravelu 13:
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; // Class `Illuminate\Foundation\Http\Middleware\VerifyCsrfToken` is replaced by `Illuminate\Foundation\Http\Middleware\PreventRequestForgery`.
class VerifyCsrfToken extends Middleware // Class `Illuminate\Foundation\Http\Middleware\VerifyCsrfToken` is replaced by `Illuminate\Foundation\Http\Middleware\PreventRequestForgery`.
{
protected $except = ['stripe/*'];
}
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Schema::useNativeSchemaOperationsIfPossible(); // Static method `Illuminate\Support\Facades\Schema::useNativeSchemaOperationsIfPossible()` is forbidden: there is no replacement; the schema is always changed natively; drop the call.
URL::forceRootUrl(config('app.url')); // Static method `Illuminate\Support\Facades\URL::forceRootUrl()` is replaced by `URL::useOrigin()`.
}
}
class ReportController
{
public function show(Request $request): array
{
$limit = Limit::perMinute(60);
$limit->decayMinutes = 5; // Property `Illuminate\Cache\RateLimiting\Limit::$decayMinutes` is replaced by `$this->decaySeconds = $value * 60`.
return [$request->get('day'), Schema::getAllTables()]; // Method `Illuminate\Http\Request::get()` is replaced by `Request::input()`.
// Static method `Illuminate\Support\Facades\Schema::getAllTables()` is forbidden: call `getTables()`, which returns arrays with the name and schema of each table, not rows of the database.
}
}
Po dresscode upgrade today:
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as Middleware;
class VerifyCsrfToken extends Middleware
{
protected $except = ['stripe/*'];
}
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Schema::useNativeSchemaOperationsIfPossible();
URL::useOrigin(config('app.url'));
}
}
class ReportController
{
public function show(Request $request): array
{
$limit = Limit::perMinute(60);
$limit->decaySeconds = 5 * 60;
return [$request->input('day'), Schema::getAllTables()];
}
}
Minuty se převedly na sekundy, které Laravel 11 do vlastnosti ukládá. Schema::getAllTables() zůstalo:
getTables() vrací pole se jménem a schématem každé tabulky, ne řádky databáze, a co s nimi váš kód
dělá, musíte posoudit vy.
Příklady toho, co balíček opravuje, podle druhu změny:
- helpery Laravelu 5.8:
str_slug(),str_limit(),array_get(),studly_case()a dalších 35 na metodyStraArrse stejnými argumenty, i s importem, - přejmenované třídy:
VerifyCsrfTokenaValidateCsrfTokennaPreventRequestForgery, traitHasVersion7UuidsnaHasUuids, fasádaInputnaRequest,ResourcenaJsonResource, testovacíTestResponseaAssertdo jmenného prostoruIlluminate\Testing; přepíše se import,extends,::classiwithoutMiddleware(), - přejmenované členy:
Request::get()nainput()i přes fasádu,dispatchNow()jobu nadispatchSync(),forceRootUrl()nauseOrigin(), metody kolekcí jakocontainsOneItem()nahasSole(), vlastnostQueueBusy::$connectionna$connectionNamea další, - volání jiného tvaru:
$limit->decayMinutesna sekundy,$event->exceptionOccurredna$event->exception !== null,assertDeleted()testů naassertModelMissing(), sloupce migracídouble('x', 8, 2)aunsignedDecimal(), jak je píše Laravel 11, a další.
Úplný výčet je v datech balíčku, v souboru upgrading/framework.neon.
Helper volaný bez zpětného lomítka uvnitř jmenného prostoru, jak je to v aplikaci obvyklé, DressCode přepíše jen s
--fix-risky: stejnojmennou funkci by mohl deklarovat ten prostor. str_contains(),
array_first() a array_last() data nechávají být, protože dnes jsou to funkce PHP.
Request::get() a input() nejsou úplně totéž: get() čte napřed
atributy routy, pak query a nakonec tělo požadavku, input() atributy nečte, tělo má před query a klíč
s tečkou bere jako cestu do pole. Laravel sám u zastaralého get() radí input() a DressCode ho
píše také; hodnotu, kterou nastavuje middleware jako atribut routy, čtěte přes
$request->attributes->get().
Změny v signaturách data nepotřebují: metodu, kterou přidal kontrakt, i návratový typ, který musí deklarovat potomek, DressCode ohlásí a zapíše podle deklarací nainstalované verze. Dopsání návratového typu je riziková oprava (risky fix), protože tělo metody může vracet něco jiného.
Co zůstane na vás
Kde náhrada není, vrací něco jiného nebo se liší podle databáze, DressCode místo ohlásí a řekne, co napsat. Třeba Doctrine DBAL, se kterým Laravel 11 skončil, nebo prostorové sloupce migrací:
Method `Illuminate\Database\Connection::getDoctrineSchemaManager()` is forbidden: read the schema by the schema builder, such as `Schema::getTables()`, `getColumns()` or `getIndexes()`.
Method `Illuminate\Database\Schema\Blueprint::point()` is forbidden: call `geometry()` or `geography()` with the subtype, such as `geography($column, 'point')`; the column type it creates depends on the database.
Stejně se ohlásí i další místa, například tato:
- pošta: přechod ze Swift Maileru na Symfony Mailer (
getSwiftMessage(),withSwiftMessage(),TransportManager) a ovladače Mandrill a SparkPost, které framework už nemá, - testy: factories modelů psané před Laravelem 8 i s helperem
factory()a traitMocksApplicationServices, - fronta:
retryAfter(),$retryAftera$timeoutAtjobu, místo kterých fronta čtebackoff(),$backoffa$retryUntil, - databáze: konstruktory gramatik a
Blueprint, které v Laravelu 12 vyžadují spojení,withTablePrefix(), sloupecfloat()s parametry$totala$placesa traitDetectsDeadlocks, - ostatní: helper
elixir(), validace hesla vPasswordBroker, ovladač Rackspace souborového systému a traitDetectsApplicationNamespace.
Úplný výčet i s tím, co napsat místo toho, je opět v datech balíčku.
Co framework jen označil jako zastaralé a náhradu k tomu napsal, v datech často není; s typy to DressCode ohlásí, a kde anotace náhradu řekne, i přepíše. Změny chování a konfigurace data nezachytí vůbec, ty najdete v průvodci upgradem Laravelu.
Aby se staré API nevracelo
Aby se staré API do kódu nevrátilo, třeba v průběžné integraci, patří do konfigurace typy, sada
deprecations a jméno balíčku, které pustí dovnitř jeho přepisy ze sekce laravel (viz Upgrade kódu):
typeAnalysis: phpstan
use:
- perCs # váš standard
- deprecations
- upgrade-laravel
Nový zápis modelů
Laravel 11 čte přetypování hodnot modelu z metody casts(), Laravel 12.4 pozná lokální scope podle
atributu #[Scope] místo předpony scope a Laravel 13 čte z atributů třídy to, co dřív četl
z vlastností, třeba #[Table('posts')] místo protected $table = 'posts'. Starý zápis funguje dál,
a proto je tohle nabídka, ne oprava. Dostanete ji spolu s ostatními daty balíčku, s konfigurací z předchozí sekce:
typeAnalysis: phpstan
use:
- perCs # váš standard
- deprecations
- upgrade-laravel
class Post extends Model
{
protected $table = 'blog_posts'; // Property `Illuminate\Database\Eloquent\Model::$table` is replaced by the attribute `#[Illuminate\Database\Eloquent\Attributes\Table]`.
public $timestamps = false; // Property `Illuminate\Database\Eloquent\Model::$timestamps` is replaced by the attribute `#[Illuminate\Database\Eloquent\Attributes\WithoutTimestamps]`.
protected $fillable = ['title', 'body']; // Property `Illuminate\Database\Eloquent\Model::$fillable` is replaced by the attribute `#[Illuminate\Database\Eloquent\Attributes\Fillable]`.
protected $casts = ['published_at' => 'datetime']; // Property `Illuminate\Database\Eloquent\Model::$casts` is replaced by the method `casts()`.
public function scopePublished(Builder $query): void // Method `scopePublished()` is replaced by the method `published()` with the attribute `#[Illuminate\Database\Eloquent\Attributes\Scope]`.
{
$query->whereNotNull('published_at');
}
}
Po dresscode fix --fix-risky:
#[WithoutTimestamps]
#[Table(name: 'blog_posts')]
#[Fillable(['title', 'body'])]
class Post extends Model
{
protected function casts(): array
{
return ['published_at' => 'datetime'];
}
#[Scope]
protected function published(Builder $query): void
{
$query->whereNotNull('published_at');
}
}
Vlastnosti téže třídy atributu, $table, $primaryKey a $keyType, se spojí do jednoho
#[Table]; které atributy data znají pro modely, příkazy, joby a další třídy, vyjmenovává stránka Přechod na Laravel 13. Přepis scope je
riziková oprava, a proto potřebuje --fix-risky: volání scopePublished() starým jménem v jiném
souboru DressCode nevidí. Metodu published() píše jako chráněnou, protože statické volání
Post::published() k ní jinak nedojde.
Metodu casts() píše rozhodnutí laravel.castsMethod od laravel/framework
11 a scope s atributem laravel.scopeAttribute od 12.4. Zapnou je data sama a hlídá je plugin, který pustí
dovnitř jméno balíčku v use; kdo některé nechce, vypne ho v sekci laravel hodnotou
keep.
Co balíček pokrývá
| co | verze |
|---|---|
helpery str_* a array_* |
6 |
| zastaralé a odstraněné API frameworku | 6 až 13 |
| atributy místo vlastností | 13 |
casts() místo $casts |
11 |
#[Scope] místo předpony scope |
12.4 |
Balíček začíná Laravelem 6, takže převede kód psaný pro Laravel 5.8; dál postupujte po jedné hlavní verzi, jak popisuje Upgrade kódu. Pro verze starší než 5.8 data nemá a přechod na 5.8 musíte udělat ručně.