← Zpět · 9. května 2026 · 3 min čtení

Livewire 4 a Dependency Injection: kde to funguje a kde ne

Kde v Livewire 4 selhává dependency injection a proč — pohled do zdrojáku a praktické řešení.

Dokumentace Livewire 4 u sekce lifecycle hooks říká jednu uklidňující větu: „You can use dependency injection with all hook methods.“ Je to pravda — ale jen pro lifecycle hooks. V komponentě je několik dalších míst, kde DI nefunguje a kde tě Livewire potichu nechá narazit do ArgumentCountError. Tenhle článek je o tom, kde a proč.

Krátké připomenutí: co je DI v Laravelu

Dependency Injection v Laravelu znamená, že místo manuálního vytváření závislostí (new SomeService()) jen type-hintneš parametr a service container ti instanci podstrčí sám. Funguje to ve třech podobách:

Konstruktor controlleru:

class InvoiceController extends Controller
{
    public function __construct(
        private InvoiceRepository $repository,
    ) {}
}

Method injection v controllerech a route closurech:

public function show(Request $request, InvoiceRepository $repository, int $id)
{
    return $repository->find($id);
}

Manuální resolve přes container:

$repository = app(InvoiceRepository::class);
// nebo
$repository = resolve(InvoiceRepository::class);

Pod kapotou to dělá Illuminate\Container\Container::call(), který přes reflection projde parametry metody, podívá se na type-hinty a každý si vyresolvuje.

Jak to funguje v Livewire komponentě

V Livewire komponentě nepíšeš __construct — Livewire se komponentu rekonstruuje při každém subsequent requestu a konstruktor by se ti volal pokaždé bez tvých dat. Místo toho jsou tu lifecycle hooks (mount, boot, hydrate, …) a public metody jako akce.

Když se Livewire chystá zavolat metodu na komponentě, většinou ji obalí helperem wrap(), který vnitřně použije ImplicitlyBoundMethod::call() z Laravel kontejneru — a to je přesně ta cesta, která dělá DI. Příklad ze zdrojáku Livewire (SupportLifecycleHooks::callHook):

public function callHook($name, $params = [])
{
    if (method_exists($this->component, $name)) {
        wrap($this->component)->__call($name, $params);
    }
}

A Wrapped::__call():

function __call($method, $params)
{
    if (! method_exists($this->target, $method)) return value($this->fallback);

    return ImplicitlyBoundMethod::call(app(), [$this->target, $method], $params);
}

Pokud volání metody projde tudy, DI funguje. Když ne, máš smůlu. A v Livewire 4 jsou minimálně dvě místa, kde Livewire wrap() nepoužívá a metodu zavolá rovnou jako $this->method() nebo přes invade().

Kde DI v Livewire 4 nefunguje

1. Computed properties (#[Computed])

Tohle je nejčastější past. Computed properties z dokumentace vypadají jako normální metoda na komponentě, ale Livewire je volá z BaseComputed::evaluateComputed():

protected function evaluateComputed()
{
    return invade($this->component)->{parent::getName()}();
}

Žádný wrap(), žádný kontejner. Jakmile dáš metodě parametr s type-hintem, dostaneš:

ArgumentCountError: Too few arguments to function App\Livewire\Dashboard::stats(),
0 passed and exactly 1 expected

Tohle je známá regresse z přechodu Livewire v2 → v3 (existuje k tomu discussion #6967) a v Livewire 4 se to stále nezměnilo.

2. Validační metody: rules(), messages(), validationAttributes()

Trait HandlesValidation volá tyhle tři metody:

if (method_exists($this, 'rules')) $rulesFromComponent = $this->rules();
if (method_exists($this, 'messages')) $messages = $this->messages();
if (method_exists($this, 'validationAttributes')) $validationAttributes = $this->validationAttributes();

Takže pokud bys do nich chtěl přes type-hint dostat třeba RuleFactory nebo Translator, narazíš na stejný ArgumentCountError.

3. Pár dalších míst (méně časté)

Pro úplnost — getListeners() (v SupportEvents) a queryString() (v SupportQueryString) jsou volané přes invade(), takže taky bez DI. V praxi je to ale jen málokdy problém, protože do nich obvykle nic injektovat nepotřebuješ.

Jak to vyřešit

Možnosti jsou tři, seřazené od nejlepší po nejhorší.

Best practice: inject v boot(), ulož do protected property

boot() běží na každém requestu (initial i subsequent), DI v něm funguje, a protected properties jsou ideální úložiště — Livewire je neserializuje na klienta, takže nemusíš řešit hydrataci.

<?php

namespace App\Livewire;

use App\Services\StatsService;
use Livewire\Attributes\Computed;
use Livewire\Component;

class Dashboard extends Component
{
    public string $apiKey;

    protected StatsService $stats;

    public function boot(StatsService $stats): void
    {
        $this->stats = $stats;
    }

    #[Computed]
    public function dailyRequests(): array
    {
        // $this->stats je dostupné, žádný app() helper není potřeba
        return $this->stats->forApiKey($this->apiKey);
    }

    public function render()
    {
        return view('livewire.dashboard');
    }
}

Proč ne mount()? Protože mount() se volá jen při prvním vytvoření komponenty. Při kliknutí na tlačítko (subsequent request) se komponenta zrekonstruuje, mount() se nezavolá a tvoje protected property bude neinicializovaná → must not be accessed before initialization. boot() tenhle problém nemá.

Druhá varianta: app() přímo v metodě

Jednodušší, ale méně testovatelné a porušuje to inversion of control. Hodí se na ad-hoc případy:

#[Computed]
public function dailyRequests(): array
{
    return app(StatsService::class)->forApiKey($this->apiKey);
}

Nepoužívej: __construct v Livewire komponentě

Někdy se v starších tutoriálech objevuje řešení s konstruktorem. Nedělej to. Livewire komponenty se neinstanciují přes new, ale přes service container s reflection logikou, a vlastní konstruktor ti tu logiku rozbije.

Shrnutí

Místo DI funguje? Důvod
mount(), boot(), booted() volání přes wrap()
hydrate(), dehydrate(), jejich *Foo varianty volání přes wrap()
updating(), updated(), *Foo varianty volání přes wrap()
rendering(), rendered(), render() volání přes wrap()
exception() volání přes wrap()
Akce (public metody volané z front-endu) volání přes wrap()
Event listenery (#[On], getListeners) volání přes wrap()
with(), placeholder() volání přes wrap()
#[Computed] metody invade()
rules(), messages(), validationAttributes() přímé $this->...()
getListeners(), queryString() invade()

Pokud potřebuješ službu v computed property nebo ve validation rules, injektuj ji v boot() do protected property. Funguje to, je to čisté a nemusíš si pamatovat, kde Livewire interně používá wrap() a kde ne.

Stačí psát část slova · ⌘K otevře hledání odkudkoliv

No results found