PHP 8.6 vychází 19. listopadu. DressCode pro něj kód přepisuje už teď. 7 dní Přechod na novější PHP →

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 metody Str a Arr se stejnými argumenty, i s importem,
  • přejmenované třídy: VerifyCsrfToken a ValidateCsrfToken na PreventRequestForgery, trait HasVersion7Uuids na HasUuids, fasáda Input na Request, Resource na JsonResource, testovací TestResponse a Assert do jmenného prostoru Illuminate\Testing; přepíše se import, extends, ::class i withoutMiddleware(),
  • přejmenované členy: Request::get() na input() i přes fasádu, dispatchNow() jobu na dispatchSync(), forceRootUrl() na useOrigin(), metody kolekcí jako containsOneItem() na hasSole(), vlastnost QueueBusy::$connection na $connectionName a další,
  • volání jiného tvaru: $limit->decayMinutes na sekundy, $event->exceptionOccurred na $event->exception !== null, assertDeleted() testů na assertModelMissing(), sloupce migrací double('x', 8, 2) a unsignedDecimal(), 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 trait MocksApplicationServices,
  • fronta: retryAfter(), $retryAfter a $timeoutAt jobu, místo kterých fronta čte backoff(), $backoff a $retryUntil,
  • databáze: konstruktory gramatik a Blueprint, které v Laravelu 12 vyžadují spojení, withTablePrefix(), sloupec float() s parametry $total a $places a trait DetectsDeadlocks,
  • ostatní: helper elixir(), validace hesla v PasswordBroker, ovladač Rackspace souborového systému a trait DetectsApplicationNamespace.

Ú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ě.

Zobrazit na GitHubu · Nahlásit problém se stránkou na GitHubu · 7. 11. 2026