PSR
Пакет PSR содержит PHP-интерфейсы для HTTP-сообщений, контейнеров, логирования, кэширования, событий, ссылок и часов. Его используют как договорённость между библиотекой и предоставляемой приложением реализацией.
Большинство файлов — контракты, а не готовые сервисы. Установка пакета не создаёт HTTP-клиент, DI-контейнер или хранилище кэша. Из готовых вспомогательных реализаций есть классы и traits логирования.
Установка
Заголовок раздела «Установка»Требуется PHP ^8.2:
composer config repositories.localzet-psr vcs https://github.com/localzet/PSRcomposer require localzet/psr:dev-mainNamespace и совместимость
Заголовок раздела «Namespace и совместимость»Все типы находятся под localzet\PSR\. Это отдельные PHP-типы, не псевдонимы Psr\:
use localzet\PSR\Log\LoggerInterface;Объект, реализующий только Psr\Log\LoggerInterface, автоматически не становится localzet\PSR\Log\LoggerInterface, даже если названия и методы совпадают. Для интеграции нужен явный adapter, реализующий ожидаемый тип.
Особенно важно различать соседний пакет localzet/psr7: его HTTP-сообщения используют Psr\Http\Message, а HTTP-контракты этого пакета — localzet\PSR\Http\Message. Передавать их друг другу как взаимозаменяемые типы нельзя.
Как выбрать контракт
Заголовок раздела «Как выбрать контракт»| Задача | Namespace | Что реализует приложение |
|---|---|---|
| Получать зависимость по ID | Container | ContainerInterface и исключения контейнера |
| Записывать диагностические сообщения | Log | LoggerInterface либо AbstractLogger::log() |
| Хранить значение по ключу с TTL | SimpleCache | CacheInterface |
| Работать с объектами кэша и отложенным сохранением | Cache | CacheItemInterface и CacheItemPoolInterface |
| Выполнять исходящий HTTP-запрос | Http\Client | ClientInterface и исключения клиента |
| Обрабатывать входящий HTTP-запрос | Http\Server | RequestHandlerInterface или MiddlewareInterface |
| Создавать HTTP-объекты | Http\Message | Соответствующий *FactoryInterface |
| Отправлять объектное событие | EventDispatcher | Диспетчер, provider и при необходимости stoppable event |
| Представлять гиперссылки | Link | Link/provider и их изменяемые варианты |
| Подменять текущее время в тесте | Clock | ClockInterface::now() |
Выбирайте ровно тот контракт, который нужен на границе компонента. Реализация не должна импортировать все группы пакета ради одного интерфейса.
Логирование без привязки к хранилищу логов
Заголовок раздела «Логирование без привязки к хранилищу логов»AbstractLogger реализует методы debug/info/notice/warning/error/critical/alert/emergency через один абстрактный log():
use localzet\PSR\Log\AbstractLogger;
$logger = new class extends AbstractLogger { public array $records = [];
public function log($level, string|Stringable $message, array $context = []): void { $this->records[] = [$level, (string) $message, $context]; }};
$logger->info('Order created', ['order_id' => 42]);LoggerTrait только передаёт level, message и context в log(). Он не интерполирует {placeholders}, не проверяет уровень и не пишет в файл. Эти решения принадлежат реализации log().
Если компоненту нужен необязательный logger, используйте NullLogger: все записи игнорируются. LoggerAwareTrait хранит logger и предоставляет setLogger(), но сам не создаёт значение по умолчанию и не выполняет запись.
Часы для воспроизводимого кода
Заголовок раздела «Часы для воспроизводимого кода»use localzet\PSR\Clock\ClockInterface;
$clock = new class implements ClockInterface { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-10-17T12:00:00+00:00'); }};Компонент, принимающий ClockInterface, может использовать реальное время в приложении и фиксированное — в тесте. Пакет не задаёт timezone или источник времени: их выбирает реализация.
HTTP middleware
Заголовок раздела «HTTP middleware»MiddlewareInterface::process() принимает localzet\PSR\Http\Message\ServerRequestInterface и следующий RequestHandlerInterface, возвращает локальный ResponseInterface. Middleware может вернуть ответ самостоятельно либо вызвать $handler->handle($request).
Пакет не собирает pipeline, не определяет порядок middleware и не предоставляет конкретный request/response. Убедитесь, что используемые HTTP-объекты реализуют именно локальные интерфейсы, иначе PHP остановит вызов из-за несовместимого типа.
Контракт и реализация
Заголовок раздела «Контракт и реализация»PHP проверяет совместимость сигнатур, но не реализует поведение комментариев интерфейса. TTL, проверка ключей, остановка события, сохранение отложенного кэша и преобразование ошибок должны быть написаны и проверены в конкретном сервисе. Наличие implements не подтверждает эти свойства.
Полный пример
Заголовок раздела «Полный пример»<?php
declare(strict_types=1);
use localzet\PSR\Clock\ClockInterface;use localzet\PSR\Log\AbstractLogger;
require __DIR__ . '/vendor/autoload.php';
$logger = new class extends AbstractLogger { public array $records = [];
public function log($level, string|Stringable $message, array $context = []): void { $this->records[] = [$level, (string) $message, $context]; }};$clock = new class implements ClockInterface { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-10-17T12:00:00+00:00'); }};
$logger->info('Order created', ['order_id' => 42]);echo json_encode([$logger->records[0], $clock->now()->format(DATE_ATOM)], JSON_THROW_ON_ERROR), PHP_EOL;Исходный код · AGPL-3.0