Komponenty Blade w PHP - Wielokrotnego Użytku Elementy UI

13.08.2025

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.

Kup pełny kurs PHP 8.4 🚀 Pobierz darmowy fragment 📥

Zobacz powiązane artykuły