Wzorzec projektowy Dekorator w PHP 8.4
Standard Dekorator (Decorator) to wzorzec strukturalny pozwalający dynamicznie rozszerzać funkcjonalność obiektów. Zgodny ze standardami PSR-7 i PSR-17, idealny do obsługi HTTP i walidacji.
Dlaczego wzorzec Dekorator jest ważny?
- Pozwala dodawać nowe funkcjonalności bez modyfikacji istniejącego kodu,
- Realizuje zasadę Open/Closed Principle - otwarty na rozszerzenia, zamknięty na modyfikacje,
- Unika eksplozji liczby podklas w hierarchii dziedziczenia,
- Umożliwia komponowanie zachowań w czasie wykonania programu.
Kluczowe cechy wzorca Dekorator
- Ten sam interfejs - dekorator implementuje interfejs dekorowanego obiektu,
- Kompozycja - dekorator zawiera referencję do obiektu bazowego,
- Łańcuchowanie - możliwość nakładania wielu dekoratorów,
- Pojedyncza odpowiedzialność - każdy dekorator dodaje jedną funkcjonalność.
Jak działa wzorzec Dekorator?
Wzorzec projektowy Dekorator (Decorator) pozwala dynamicznie rozszerzać funkcjonalność obiektu, nie modyfikując jednocześnie jego oryginalnej struktury ani nie tworząc skomplikowanej hierarchii dziedziczenia. Dzięki temu można łatwo dodawać nowe funkcje, walidacje lub zachowania, nie naruszając zasady pojedynczej odpowiedzialności (Single Responsibility Principle). Klasy przedstawione w rozdziale 2 ebooka, implementujące standardy PSR-7 i PSR-17, doskonale obrazują, jak skutecznie używać tego wzorca w praktyce, zwłaszcza w kontekście obsługi plików przesłanych przez użytkownika. Uczestnicy kursu zdobędą umiejętności związane z zaawansowanymi technikami optymalizacji kodu oraz projektowaniem aplikacji zgodnych z najlepszymi praktykami.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Http\UploadedFile;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
abstract class UploadedFileDecorator implements UploadedFileInterface
{
public function __construct(protected UploadedFileInterface $file)
{
}
public function getStream(): StreamInterface
{
return $this->file->getStream();
}
public function moveTo(string $targetPath): void
{
$this->file->moveTo($targetPath);
}
public function getSize(): ?int
{
return $this->file->getSize();
}
public function getError(): int
{
return $this->file->getError();
}
public function getClientFilename(): ?string
{
return $this->file->getClientFilename();
}
public function getClientMediaType(): ?string
{
return $this->file->getClientMediaType();
}
}
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Http\UploadedFile;
use InvalidArgumentException;
use RuntimeException;
class PathValidatorDecorator extends UploadedFileDecorator
{
public function moveTo(string $targetPath): void
{
$this->validatePath($targetPath);
parent::moveTo($targetPath);
}
private function validatePath(string $targetPath): void
{
if (! $targetPath) {
throw new InvalidArgumentException('Invalid path provided for move operation');
}
$targetDirectory = dirname($targetPath);
if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
throw new RuntimeException(
sprintf('The target directory ``%s`` does not exist or is not writable', $targetDirectory)
);
}
}
}
W przedstawionym przykładzie klasy dekoratorów, takie jak ErrorValidatorDecorator czy PathValidatorDecorator, pozwalają na uzupełnienie bazowego obiektu klasy UploadedFile o dodatkowe warstwy odpowiedzialności, takie jak walidacja błędów przesyłania czy sprawdzanie poprawności ścieżki docelowej. Każdy dekorator realizuje pojedynczą, jasno określoną odpowiedzialność, na przykład ErrorValidatorDecorator dba o upewnienie się, że przesłany plik nie zawiera błędów po stronie serwera, natomiast PathValidatorDecorator weryfikuje poprawność katalogu docelowego przy przenoszeniu pliku.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Http\UploadedFile;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
class UploadedFileFactory
{
public static function createUploadedFile(
StreamInterface|string $streamOrFile,
int $size,
int $error,
?string $clientFilename = null,
?string $clientMediaType = null
): UploadedFileInterface {
$baseFile = new BaseUploadedFile($streamOrFile, $size, $error, $clientFilename, $clientMediaType);
$file = new ErrorValidatorDecorator($baseFile);
$file = new MoveStatusDecorator($file);
return new PathValidatorDecorator($file);
}
}
Wzorzec dekorator pozwala na elastyczne łączenie różnych funkcjonalności poprzez ich opakowywanie kolejno wokół obiektu bazowego. Przykład z klasy UploadedFileFactory pokazuje, jak w łatwy sposób można skomponować obiekt wynikowy, nakładając kolejne dekoratory: bazowy plik zostaje najpierw opakowany przez ErrorValidatorDecorator, następnie przez MoveStatusDecorator, a na koniec przez PathValidatorDecorator. Dzięki temu końcowy użytkownik otrzymuje jeden, kompleksowy obiekt, w którym poszczególne zachowania są logicznie odseparowane.
Implementacja dekoratorów jest również zgodna z wytycznymi standardów PSR-7 i PSR-17, które szczegółowo opisują sposób obsługi zapytań HTTP oraz tworzenia obiektów reprezentujących przesłane pliki. Dzięki takiemu podejściu, dekoratory umożliwiają łatwe rozszerzanie implementacji o nowe walidacje i funkcjonalności, nie naruszając przy tym integralności oryginalnych klas oraz zachowując przejrzystość kodu. Całość staje się łatwiejsza w testowaniu, utrzymaniu oraz dalszym rozwoju aplikacji zgodnej z nowoczesnymi standardami PHP.
🎓 Chcesz poznać więcej wzorców projektowych?
Ten wpis to fragment kompleksowego kursu PHP 8.4, w którym szczegółowo omawiam wszystkie wzorce projektowe wraz z praktycznymi przykładami z rzeczywistych projektów.