Kurs PHP 8.4 w praktyce – Standardy PSR

PSR (PHP Standard Recommendations) to zestaw zaleceń mających na celu ujednolicenie stylu i praktyk kodowania w ekosystemie PHP. Każdy standard określony w ramach PSR wpływa na różne aspekty tworzenia aplikacji, począwszy od organizacji kodu po bardziej złożone zagadnienia jak zarządzanie żądaniami HTTP czy wykorzystanie wzorców projektowych. PSR jest kluczowy, ponieważ zapewnia spójność i przewidywalność w różnych projektach, ułatwiając współpracę między deweloperami oraz integrację zewnętrznych bibliotek.

Każdy standard PSR jest rozwijany przez PHP-FIG (PHP Framework Interoperability Group) i jest szeroko stosowany w całym ekosystemie PHP. Zrozumienie tych standardów i ich implementacja jest kluczowa dla tworzenia nowoczesnych, skalowalnych aplikacji.

PSR-3 – Tworzenie logów w PHP (Rozdział 11)

PSR-3 definiuje interfejs do tworzenia logów w PHP, zawiera metody takie jak error, debug, info itd., ułatwiając spójne logowanie.

Zobacz oficjalną dokumentację PSR-3

Kup kurs i opanuj PSR-3 🎯
<?php

declare(strict_types=1);

namespace DJWeb\Framework\Log;

use DJWeb\Framework\Enums\Log\LogLevel;
use DJWeb\Framework\Log\Contracts\HandlerContract;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Stringable;

final class Logger implements LoggerInterface
{
    use LoggerTrait;

    /**
     * @param array $handlers
     * @param PlaceholderProcessor|null $placeholderProcessor
     */
    public function __construct(
        public private(set) array $handlers = [],
        private ?PlaceholderProcessor $placeholderProcessor = null
    )
    {
        /** @phpstan-ignore-next-line */
        $this->handlers = array_filter($this->handlers, static fn ($handler) => $handler instanceof HandlerContract);
        $this->placeholderProcessor ??= new PlaceholderProcessor();
    }

    /**
     * @param LogLevel $level
     * @param Stringable|string $message
     * @param array $context
     *
     * @return void
     */
    public function log($level, Stringable|string $message, array $context = []): void
    {
        $message = is_string($message) ? $message : (string) $message;
        $processedMessage = new Message(
            level: $level,
            message: $this->placeholderProcessor?->process($message, new Context($context)) ?? '',
            context: new Context($context),
            metadata: Metadata::create()
        );
        array_walk($this->handlers, static fn (HandlerContract $handler) => $handler->handle($processedMessage));
    }
}

PSR-6 – Cache system (Rozdział 22)

PSR-6 standaryzuje mechanizm cache w PHP – definiuje Cache Pool, Cache Item i obsługę TTL.

Zobacz oficjalną dokumentację PSR-6

Naucz się cachować z PSR-6 🚀
<?php

declare(strict_types=1);

namespace DJWeb\Framework\Cache;

use Carbon\Carbon;
use DateInterval;
use Psr\Cache\CacheItemInterface;

class CacheItem implements CacheItemInterface
{
    private mixed $value = null;
    private bool $isHit = false;
    private ?Carbon $expiry = null;

    public function __construct(
        private string $key,
    ) {
    }

    public function getKey(): string
    {
        return $this->key;
    }

    public function get(): mixed
    {
        return $this->value;
    }

    public function isHit(): bool
    {
        return $this->isHit;
    }

    public function set(mixed $value): static
    {
        return (clone $this)->with(value: $value);
    }

    public function expiresAt(?\DateTimeInterface $expiration): static
    {
        return (clone $this)->with(
            expiry: $expiration ? Carbon::instance($expiration) : null
        );
    }

    public function expiresAfter(\DateInterval|int|null $time): static
    {
        $expiry = match(true) {
            $time === null => null,
            $time instanceof DateInterval => Carbon::now()->add($time),
            default => Carbon::now()->addSeconds($time),
        };

        return (clone $this)->with(expiry: $expiry);
    }

    public function withIsHit(bool $hit): static
    {
        return (clone $this)->with(isHit: $hit);
    }

    public function getExpiry(): ?Carbon
    {
        return $this->expiry;
    }

    private function with(
        mixed $value = null,
        ?Carbon $expiry = null,
        ?bool $isHit = null,
    ): static {
        $clone = clone $this;
        $clone->value = $value ?? $this->value;
        $clone->expiry = $expiry ?? $this->expiry;
        $clone->isHit = $isHit ?? $this->isHit;
        return $clone;
    }

}

PSR-7 – HTTP Request/Response (Rozdział 2)

PSR-7 wprowadza interfejsy do obsługi żądań i odpowiedzi HTTP: RequestInterface, ResponseInterface i inne.

Zobacz oficjalną dokumentację PSR-7

Opanuj requesty z PSR-7 🌐
<?php

declare(strict_types=1);

namespace DJWeb\Framework\Http\Request\Psr7;

use DJWeb\Framework\Http\HeaderManager;
use DJWeb\Framework\Http\Request\AttributesManager;
use DJWeb\Framework\Http\Request\ParsedBodyManager;
use DJWeb\Framework\Http\Request\QueryParamsManager;
use DJWeb\Framework\Http\Request\UploadedFilesManager;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

class ServerRequest extends BaseRequest implements ServerRequestInterface
{
    /**
     * @param string $method
     * @param UriInterface $uri
     * @param StreamInterface $body
     * @param HeaderManager $headerManager
     * @param array $serverParams
     * @param array $cookieParams
     * @param QueryParamsManager $queryParamsManager
     * @param UploadedFilesManager $uploadedFilesManager
     * @param ParsedBodyManager $parsedBodyManager
     * @param AttributesManager $attributesManager
     */
    public function __construct(
        string $method,
        UriInterface $uri,
        StreamInterface $body,
        HeaderManager $headerManager,
        private ?array $serverParams,
        private array $cookieParams,
        protected QueryParamsManager $queryParamsManager,
        private UploadedFilesManager $uploadedFilesManager,
        protected ParsedBodyManager $parsedBodyManager,
        private AttributesManager $attributesManager
    ) {
        parent::__construct($method, $uri, $body, $headerManager);
    }

    /**
     * @return array
     */
    public function getServerParams(): array
    {
        return $this->serverParams ?? [];
    }

    /**
     * @return array
     */
    public function getCookieParams(): array
    {
        return $this->cookieParams;
    }

    /**
     * @param array $cookies
     *
     * @return $this
     */
    public function withCookieParams(array $cookies): static
    {
        $new = clone $this;
        $new->cookieParams = $cookies;
        return $new;
    }

    /**
     *  @return array
     * /
     */
    public function getQueryParams(): array
    {
        return $this->queryParamsManager->queryParams ?? [];
    }

    /**
     * @param array $query
     *
     * @return $this
     */
    public function withQueryParams(array $query): static
    {
        $new = clone $this;
        $new->queryParamsManager = $this->queryParamsManager->withQueryParams($query);
        return $new;
    }

    /**
     *  @return array
     * /
     */
    public function getUploadedFiles(): array
    {
        return $this->uploadedFilesManager->uploadedFiles;
    }

    /**
     * @param array $uploadedFiles
     *
     * @return $this
     */
    public function withUploadedFiles(array $uploadedFiles): static
    {
        $new = clone $this;
        $new->uploadedFilesManager = $this->uploadedFilesManager->withUploadedFiles($uploadedFiles);
        return $new;
    }

    /**
     * @return array
     */
    public function getParsedBody(): array
    {
        return $this->parsedBodyManager->parsedBody ?? [];
    }

    /**
     * @param ?array $data
     *
     * @return $this
     */
    public function withParsedBody($data): static
    {
        $new = clone $this;
        $new->parsedBodyManager = $this->parsedBodyManager->withParsedBody($data);
        return $new;
    }

    /**
     * @return array
     */
    public function getAttributes(): array
    {
        return $this->attributesManager->attributes;
    }

    public function getAttribute(string $name, mixed $default = null): mixed
    {
        return $this->attributesManager->getAttribute($name, $default);
    }

    public function withAttribute(string $name, mixed $value): static
    {
        $new = clone $this;
        $new->attributesManager = $this->attributesManager->withAttribute($name, $value);
        return $new;
    }

    public function withoutAttribute(string $name): static
    {
        $new = clone $this;
        $new->attributesManager = $this->attributesManager->withoutAttribute($name);
        return $new;
    }
}

PSR-11 – Dependency Container (Rozdział 3)

PSR-11 standaryzuje kontenery zależności, umożliwiając dynamiczne dostarczanie klas i usług.

Zobacz oficjalną dokumentację PSR-11

Zarządzaj zależnościami z PSR-11 🧱
<?php

declare(strict_types=1);

namespace DJWeb\Framework\Container;

use DJWeb\Framework\Base\DotContainer;
use DJWeb\Framework\Container\Contracts\ContainerContract;
use DJWeb\Framework\Container\Contracts\ServiceProviderContract;
use DJWeb\Framework\Exceptions\Container\NotFoundError;

class Container implements ContainerContract
{
    private DotContainer $entries;
    private Autowire $autowire;

    /**
     * @var array
     */
    private array $bindings = [];

    public function __construct()
    {
        $this->entries = new DotContainer();
        $this->autowire = new Autowire($this);
    }

    public function bind(string $key, string|int|float|bool|null $value): void
    {
        $this->bindings[$key] = $value;
    }

    public function getBinding(string $key): string|int|float|bool|null
    {
        return $this->bindings[$key] ?? null;
    }

    /**
     * @param class-string $id
     *
     * @return mixed
     *
     * @throws NotFoundError
     */
    public function get(string $id): mixed
    {
        $entry = $this->entries->get($id);
        if (! $this->has($id)) {
            return $this->autowire->instantiate($id);
        }

        if ($entry instanceof Definition) {
            return $this->autowire->instantiate($entry::class);
        }
        return $entry;
    }

    public function has(string $id): bool
    {
        return $this->entries->has($id);
    }

    /**
     * Sets item in container
     *
     * @param string $key
     * @param mixed $value
     *
     * @return ContainerContract
     */
    public function set(string $key, mixed $value): ContainerContract
    {
        $this->entries->set($key, $value);
        return $this;
    }

    /**
     * Add a definition to the container.
     *
     * @param Definition $definition
     *
     * @return self
     */
    public function addDefinition(Definition $definition): ContainerContract
    {
        $this->entries[$definition->id] = $definition;
        return $this;
    }

    /**
     * Register a service provider.
     *
     * @param ServiceProviderContract $provider
     *
     * @return self
     */
    public function register(
        ServiceProviderContract $provider
    ): ContainerContract {
        $provider->register($this);
        return $this;
    }
}

PSR-12 – Styl kodu PHP (Wszystkie rozdziały)

PSR-12 definiuje szczegółowe zasady formatowania kodu: odstępy, nawiasy, struktura funkcji i klas.

Zobacz oficjalną dokumentację PSR-12

Uczyń swój kod czytelnym z PSR-12 ✍️

PSR-14 – Event Dispatcher (Rozdział 23)

PSR-14 definiuje mechanizm zdarzeń – luźne powiązania między komponentami i wzorzec Observer.

Zobacz oficjalną dokumentację PSR-14

Wykorzystaj eventy z PSR-14 ⚙️
<?php

declare(strict_types=1);

namespace DJWeb\Framework\Events;

use DJWeb\Framework\Config\Config;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;

class EventManager
{
    public EventDispatcherInterface $dispatcher{
        get => $this->_eventDispatcher ??= new EventDispatcher($this->listenerProvider);
    }
    public ListenerProviderInterface $listenerProvider{
        get => $this->_listenerProvider ??= $this->createListenerProvider();
    }
    private ?EventDispatcherInterface $_eventDispatcher = null;
    private ?ListenerProviderInterface $_listenerProvider = null;

    public function dispatch(object $event): object
    {
        return $this->dispatcher->dispatch($event);
    }

    private function createListenerProvider(): ListenerProviderInterface
    {
        $provider = new ListenerProvider();

        $eventConfig = Config::get('events.listeners');

        foreach ($eventConfig as $eventClass => $listeners) {
            foreach ((array) $listeners as $listenerClass) {
                $provider->addListener($eventClass, $listenerClass);
            }
        }

        return $provider;
    }
}

PSR-15 – Middleware (Rozdział 12)

PSR-15 definiuje interfejsy middleware dla przetwarzania żądań HTTP w warstwach.

Zobacz oficjalną dokumentację PSR-15

Zbuduj elastyczny flow z PSR-15 🧩
<?php

declare(strict_types=1);

namespace DJWeb\Framework\Http\Middleware;

use DJWeb\Framework\Exceptions\Validation\ValidationError;
use DJWeb\Framework\Http\Response;
use DJWeb\Framework\Web\Application;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ValidationErrorMiddleware implements MiddlewareInterface
{
    private const JSON_CONTENT_TYPE = 'application/json';
    public function __construct(private ?Application $app = null) {
        $this->app ??= Application::getInstance();
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            return $handler->handle($request);
        } catch (ValidationError $error) {
            return $this->isJsonRequest($request) ?
                $this->handleJsonResponse($error) : $this->handleWebResponse($request, $error);
        }
    }

    private function isJsonRequest(ServerRequestInterface $request): bool
    {
        $contentType = $request->getHeaderLine('Content-Type');
        $acceptHeader = $request->getHeaderLine('Accept');

        return str_contains($contentType, self::JSON_CONTENT_TYPE)
            || str_contains($acceptHeader, self::JSON_CONTENT_TYPE);
    }

    private function formatErrorResponse(ValidationError $error): array
    {
        return [
            'message' => $error->getMessage(),
            'errors' => $error->validationErrors,
        ];
    }

    private function handleJsonResponse(ValidationError $error): ResponseInterface
    {
        return new Response()->withJson($this->formatErrorResponse($error), status: 422);
    }

    private function handleWebResponse(ServerRequestInterface $request, ValidationError $error): ResponseInterface
    {
        $this->app->session->set(
            'errors',
            json_encode($this->formatErrorResponse($error))
        );
        return new Response()->redirect($request->getUri()->getPath());
    }

}

Zamów praktyczny kurs PHP już teraz!

Dowiedz się jak działają współczesne frameworki PHP

cover

Kup teraz 149 zł

Nasz kurs PHP w praktyce to inwestycja w Twoją karierę. Po zakupie zyskasz:

checkmark Pełny dostęp do materiałów kursu

checkmark Możliwość czytania e-booka online

checkmark Przeglądarkę kodu źródłowego z praktycznymi przykładami

checkmark Możliwość pobrania w formatach PDF/EPUB/Markdown