Komponenty Blade w PHP - Wielokrotnego Użytku Elementy UI
Czym są komponenty w Blade?
Komponenty w Blade to mechanizm pozwalający na tworzenie wielokrotnego użytku elementów interfejsu użytkownika. Podobnie jak w Laravel, nasze komponenty składają się z dwóch części: klasy PHP definiującej logikę i widoku Blade zawierającego strukturę HTML. Dzięki komponentom kod staje się bardziej modularny, czytelny i łatwiejszy w utrzymaniu.
Dlaczego warto używać komponentów?
Kluczowe korzyści
- DRY (Don't Repeat Yourself) - eliminacja powielania kodu
- Enkapsulacja - logika i prezentacja w jednym miejscu
- Testowanie - łatwiejsze testowanie izolowanych komponentów
- Utrzymanie - zmiany w jednym miejscu wpływają na wszystkie użycia
- Kompozycja - komponenty mogą zawierać inne komponenty
Architektura komponentów
W naszym frameworkie komponenty działają analogicznie jak w Laravelu. Każdy komponent składa się z klasy PHP i widoku Blade, a komunikacja między nimi odbywa się poprzez publiczne pola klasy.
Bazowa klasa Component
<?php
namespace App\View\Components;
abstract class Component
{
protected string $view;
protected array $data = [];
abstract public function render(): string;
protected function withView(string $view): self
{
$this->view = $view;
return $this;
}
protected function with(array $data): self
{
$this->data = $data;
return $this;
}
public function __get(string $name): mixed
{
return $this->data[$name] ?? null;
}
}
Przykład 1: Komponent Alert
Komponent Alert to klasyczny przykład wielokrotnego użytku elementu UI. Pozwala wyświetlać komunikaty w różnych stylach (success, error, warning, info).
Klasa Alert (app/View/Components/Alert.php)
<?php
namespace App\View\Components;
use DJWeb\Framework\View\Component;
class Alert extends Component
{
public function __construct(
public string $type = 'info',
public string $message = '',
public bool $dismissible = false
) {}
public function render(): string
{
return $this->blade->render('components/alert.blade.php', [
'type' => $this->type,
'message' => $this->message,
'dismissible' => $this->dismissible,
'alertClass' => $this->getAlertClass(),
]);
}
private function getAlertClass(): string
{
return match($this->type) {
'success' => 'alert-success',
'error', 'danger' => 'alert-danger',
'warning' => 'alert-warning',
'info' => 'alert-info',
default => 'alert-secondary',
};
}
}
Widok Alert (resources/views/components/alert.blade.php)
<div role="alert">
{{ $message }}
@if($slot ?? false)
{!! $slot !!}
@endif
@if($dismissible)
<button type="button" data-bs-dismiss="alert" aria-label="Close"></button>
@endif
</div>
Użycie komponentu Alert
{{-- Prosty komunikat --}}
<x-alert type="success" message="Operacja zakończona sukcesem!" />
{{-- Z możliwością zamknięcia --}}
<x-alert type="warning" message="To jest ostrzeżenie" :dismissible="true" />
{{-- Ze slotem (treścią zagnieżdżoną) --}}
<x-alert type="info" :dismissible="true">
<strong>Informacja:</strong> Możesz użyć HTML w treści alertu.
</x-alert>
Przykład 2: Komponent Button
Komponent Button demonstruje użycie slotów oraz przekazywanie atrybutów HTML.
Klasa Button (app/View/Components/Button.php)
<?php
namespace App\View\Components;
use DJWeb\Framework\View\Component;
class Button extends Component
{
public function __construct(
public string $type = 'button',
public string $variant = 'primary',
public string $size = 'md',
public bool $disabled = false
) {}
public function render(): string
{
return $this->blade->render('components/button.blade.php', [
'type' => $this->type,
'variant' => $this->variant,
'size' => $this->size,
'disabled' => $this->disabled,
'buttonClass' => $this->getButtonClass(),
]);
}
private function getButtonClass(): string
{
$classes = ["btn", "btn-{$this->variant}"];
if ($this->size !== 'md') {
$classes[] = "btn-{$this->size}";
}
return implode(' ', $classes);
}
}
Widok Button (resources/views/components/button.blade.php)
<button
type="{{ $type }}"
@if($disabled) disabled @endif
>
{!! $slot !!}
</button>
Użycie komponentu Button
{{-- Przycisk podstawowy --}}
<x-button>Kliknij mnie</x-button>
{{-- Przycisk submit z wariantem success --}}
<x-button type="submit" variant="success">Zapisz</x-button>
{{-- Mały przycisk danger --}}
<x-button variant="danger" size="sm">Usuń</x-button>
{{-- Przycisk z ikoną --}}
<x-button variant="outline-primary">
<i ></i> Pobierz
</x-button>
Przykład 3: Komponent Card
Card to bardziej zaawansowany komponent wykorzystujący wiele nazwanych slotów (named slots).
Klasa Card (app/View/Components/Card.php)
<?php
namespace App\View\Components;
use DJWeb\Framework\View\Component;
class Card extends Component
{
private array $slots = [];
public function __construct(
public string $title = '',
public string $footer = ''
) {}
public function withNamedSlot(string $name, string $content): self
{
$this->slots[$name] = $content;
return $this;
}
public function getSlot(string $name): string
{
return $this->slots[$name] ?? '';
}
public function render(): string
{
return $this->blade->render('components/card.blade.php', [
'title' => $this->title,
'footer' => $this->footer,
'header' => $this->getSlot('header'),
'body' => $this->getSlot('body'),
'footerContent' => $this->getSlot('footer'),
]);
}
}
Widok Card (resources/views/components/card.blade.php)
<div >
@if($header || $title)
<div >
@if($header)
{!! $header !!}
@else
<h5 >{{ $title }}</h5>
@endif
</div>
@endif
<div >
{!! $body !!}
</div>
@if($footerContent || $footer)
<div >
@if($footerContent)
{!! $footerContent !!}
@else
{{ $footer }}
@endif
</div>
@endif
</div>
Użycie komponentu Card z nazwanymi slotami
<x-card title="Tytuł karty">
@slot('body')
<p>To jest treść karty. Możesz tutaj umieścić dowolny HTML.</p>
<ul>
<li>Element 1</li>
<li>Element 2</li>
</ul>
@endslot
@slot('footer')
<button >Akcja</button>
<button >Anuluj</button>
@endslot
</x-card>
Zagnieżdżone komponenty
Komponenty mogą zawierać inne komponenty, co pozwala budować złożone interfejsy z prostych, wielokrotnego użytku elementów.
Przykład: Dashboard z kartami i alertami
<div >
<x-alert type="info">
Witaj w panelu administracyjnym!
</x-alert>
<div >
<div >
<x-card title="Statystyki">
@slot('body')
<p>Użytkownicy: {{ $stats['users'] }}</p>
<p>Posty: {{ $stats['posts'] }}</p>
@endslot
</x-card>
</div>
<div >
<x-card title="Ostatnie akcje">
@slot('body')
@foreach($recentActions as $action)
<div >
<x-alert type="{{ $action['type'] }}" :message="$action['message']" />
</div>
@endforeach
@endslot
@slot('footer')
<x-button variant="outline-primary" size="sm">
Zobacz wszystkie
</x-button>
@endslot
</x-card>
</div>
</div>
</div>
Implementacja ComponentDirective
Dyrektywa ComponentDirective jest odpowiedzialna za kompilację składni <x-component>
do kodu PHP, który tworzy instancję klasy komponentu i wywołuje metodę render()
.
Kompilacja komponentów
<?php
class ComponentDirective extends Directive
{
public function compile(string $template): string
{
// Najpierw kompilujemy sloty
$template = $this->compileSlots($template);
// Następnie kompilujemy same komponenty
$template = $this->compileComponents($template);
return $template;
}
private function compileComponents(string $template): string
{
$pattern = '/<x-([a-z\-]+)(.*?)(?:\/>|>(.*?)<\/x-\1>)/s';
return preg_replace_callback($pattern, function($matches) {
$component = $this->componentName($matches[1]);
$attributes = $this->parseAttributes($matches[2] ?? '');
$slot = $matches[3] ?? '';
return $this->compileComponentString($component, $attributes, $slot);
}, $template);
}
private function compileComponentString(
string $component,
array $attributes,
string $slot
): string {
$props = $this->buildPropsString($attributes);
return "<?php " .
"\$__component = new {$component}({$props}); " .
"if (!empty('{$slot}')) \$__component->slot = '{$slot}'; " .
"echo \$__component->render(); " .
"?>";
}
}
Wzorce projektowe w komponentach
- Composite - komponenty mogą zawierać inne komponenty
- Template Method - bazowa klasa Component definiuje szkielet
- Strategy - różne komponenty implementują różną logikę renderowania
- Single Responsibility - każdy komponent odpowiada za jedną rzecz
- Open/Closed - łatwo dodawać nowe komponenty bez modyfikacji istniejących
Testowanie komponentów
Komponenty są łatwe do testowania dzięki izolacji logiki w klasach PHP.
<?php
class AlertComponentTest extends TestCase
{
public function testRenderSuccessAlert(): void
{
$alert = new Alert(
type: 'success',
message: 'Test message'
);
$output = $alert->render();
$this->assertStringContainsString('alert-success', $output);
$this->assertStringContainsString('Test message', $output);
}
public function testRenderDismissibleAlert(): void
{
$alert = new Alert(
type: 'warning',
message: 'Warning',
dismissible: true
);
$output = $alert->render();
$this->assertStringContainsString('alert-dismissible', $output);
$this->assertStringContainsString('btn-close', $output);
}
}
Najlepsze praktyki
- Trzymaj komponenty małe i skoncentrowane na jednej odpowiedzialności
- Używaj typowania dla właściwości komponentu (PHP 8.0+)
- Dokumentuj dostępne sloty i atrybuty
- Stosuj sensowne wartości domyślne
- Waliduj dane wejściowe w konstruktorze
- Testuj komponenty w izolacji
🎓 Zbuduj system komponentów Blade!
W kursie PHP 8.4 implementujesz pełny system komponentów Blade - od podstawowej klasy Component, przez dyrektywy obsługujące sloty i atrybuty, aż po zaawansowane komponenty zagnieżdżone. Poznasz wzorce Composite i Template Method w praktycznym zastosowaniu.