其他 PSR 规范概览
除了 PSR-1、PSR-4 和 PSR-12 之外,PHP-FIG 还制定了多个重要规范,覆盖日志记录、依赖注入容器、HTTP 消息、中间件等常见场景。这些规范使得不同框架和库之间的互操作性成为可能。本节将系统介绍每个重要 PSR 规范的核心内容、接口定义和实际应用。
基础概念
PSR 规范分类总览
| 类别 | PSR | 状态 | 说明 |
|---|---|---|---|
| 编码标准 | PSR-1 | 已接受 | 基础编码标准 |
| 编码标准 | PSR-12 | 已接受 | 扩展编码样式 |
| 自动加载 | PSR-4 | 已接受 | 自动加载标准 |
| 日志 | PSR-3 | 已接受 | 日志接口 |
| HTTP | PSR-7 | 已接受 | HTTP 消息接口 |
| HTTP | PSR-17 | 已接受 | HTTP 工厂接口 |
| HTTP | PSR-18 | 已接受 | HTTP 客户端接口 |
| 容器 | PSR-11 | 已接受 | 容器接口 |
| 事件 | PSR-14 | 已接受 | 事件分发器 |
| 缓存 | PSR-6 | 已接受 | 缓存接口 |
| 链接 | PSR-13 | 已接受 | 链接定义 |
| 中间件 | PSR-15 | 已接受 | HTTP 中间件 |
| 简易缓存 | PSR-16 | 已接受 | 简易缓存接口 |
PSR-3:日志接口
核心接口
php
<?php
namespace Psr\Log;
interface LoggerInterface
{
public function emergency(string|\Stringable $message, array $context = []): void;
public function alert(string|\Stringable $message, array $context = []): void;
public function critical(string|\Stringable $message, array $context = []): void;
public function error(string|\Stringable $message, array $context = []): void;
public function warning(string|\Stringable $message, array $context = []): void;
public function notice(string|\Stringable $message, array $context = []): void;
public function info(string|\Stringable $message, array $context = []): void;
public function debug(string|\Stringable $message, array $context = []): void;
public function log(mixed $level, string|\Stringable $message, array $context = []): void;
}日志级别
| 级别 | 数值 | 用途 |
|---|---|---|
| DEBUG | 100 | 详细的调试信息 |
| INFO | 200 | 有意义的事件 |
| NOTICE | 250 | 正常但值得注意的事件 |
| WARNING | 300 | 非致命异常情况 |
| ERROR | 400 | 运行时错误 |
| CRITICAL | 500 | 需要立即处理的错误 |
| ALERT | 550 | 必须立即采取行动 |
| EMERGENCY | 600 | 系统不可用 |
实际实现
php
<?php
declare(strict_types=1);
use Psr\Log\LoggerInterface;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Monolog 实现了 PSR-3
$logger = new Logger('app');
$logger->pushHandler(new StreamHandler('logs/app.log', Logger::DEBUG));
$logger->info('用户登录', ['user_id' => 123, 'ip' => '192.168.1.1']);
$logger->error('支付失败', ['order_id' => 456, 'error' => '余额不足']);
$logger->warning('API 请求超时', ['url' => '/api/users', 'timeout' => 30]);PSR-6:缓存接口
核心接口
php
<?php
namespace Psr\Cache;
interface CacheItemPoolInterface
{
public function getItem(string $key): CacheItemInterface;
public function getItems(array $keys = []): iterable;
public function hasItem(string $key): bool;
public function clear(): bool;
public function deleteItem(string $key): bool;
public function deleteItems(array $keys): bool;
public function save(CacheItemInterface $item): bool;
public function saveDeferred(CacheItemInterface $item): bool;
public function commit(): bool;
}
interface CacheItemInterface
{
public function getKey(): string;
public function get(): mixed;
public function isHit(): bool;
public function set(mixed $value): static;
public function expiresAt(?\DateTimeInterface $expiration): static;
public function expiresAfter(int|\DateInterval|null $time): static;
}使用示例
php
<?php
declare(strict_types=1);
use Psr\Cache\CacheItemPoolInterface;
class UserService
{
public function __construct(
private readonly CacheItemPoolInterface $cache
) {}
public function getUserById(int $id): array
{
$cacheKey = "user:{$id}";
$cacheItem = $this->cache->getItem($cacheKey);
if ($cacheItem->isHit()) {
return $cacheItem->get();
}
$user = $this->fetchFromDatabase($id);
$cacheItem->set($user)->expiresAfter(3600);
$this->cache->save($cacheItem);
return $user;
}
}PSR-7:HTTP 消息接口
核心接口
php
<?php
namespace Psr\Http\Message;
interface RequestInterface extends MessageInterface
{
public function getRequestTarget(): string;
public function withRequestTarget(string $requestTarget): self;
public function getMethod(): string;
public function withMethod(string $method): self;
public function getUri(): UriInterface;
public function withUri(UriInterface $uri, bool $preserveHost = false): self;
}
interface ResponseInterface extends MessageInterface
{
public function getStatusCode(): int;
public function withStatus(int $code, string $reasonPhrase = ''): self;
public function getReasonPhrase(): string;
}
interface MessageInterface
{
public function getProtocolVersion(): string;
public function withProtocolVersion(string $version): self;
public function getHeaders(): array;
public function hasHeader(string $name): bool;
public function getHeader(string $name): array;
public function getHeaderLine(string $name): string;
public function withHeader(string $name, string|string[] $value): self;
public function withAddedHeader(string $name, string|string[] $value): self;
public function withoutHeader(string $name): self;
public function getBody(): StreamInterface;
public function withBody(StreamInterface $body): self;
}使用示例
php
<?php
declare(strict_types=1);
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Nyholm\Psr7\Factory\Psr17Factory;
$psrFactory = new Psr17Factory();
// 创建请求
$request = $psrFactory->createRequest('GET', 'https://api.example.com/users')
->withHeader('Accept', 'application/json')
->withHeader('Authorization', 'Bearer token123');
// 创建响应
$response = $psrFactory->createResponse(200)
->withHeader('Content-Type', 'application/json')
->withBody($psrFactory->createStream(json_encode(['status' => 'ok'])));PSR-11:容器接口
核心接口
php
<?php
namespace Psr\Container;
interface ContainerInterface
{
public function get(string $id): object;
public function has(string $id): bool;
}
interface ContainerExceptionInterface
{
}
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}使用示例
php
<?php
declare(strict_types=1);
use Psr\Container\ContainerInterface;
class Application
{
public function __construct(
private readonly ContainerInterface $container
) {}
public function handle(string $serviceId): mixed
{
if (!$this->container->has($serviceId)) {
throw new \RuntimeException("服务 {$serviceId} 未注册");
}
return $this->container->get($serviceId);
}
}PSR-14:事件分发器
核心接口
php
<?php
namespace Psr\EventDispatcher;
interface EventDispatcherInterface
{
public function dispatch(object $event): object;
}
interface ListenerProviderInterface
{
public function getListenersForEvent(object $event): iterable;
}
interface StoppableEventInterface
{
public function isPropagationStopped(): bool;
public function stopPropagation(): void;
}PSR-15:HTTP 中间件
核心接口
php
<?php
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface;
}
interface RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface;
}中间件链示例
php
<?php
declare(strict_types=1);
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CorsMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
}
class AuthMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$token = $request->getHeaderLine('Authorization');
if (!$token || !$this->validateToken($token)) {
return new \Nyholm\Psr7\Response(401, [], 'Unauthorized');
}
return $handler->handle($request);
}
private function validateToken(string $token): bool
{
return str_starts_with($token, 'Bearer ');
}
}PSR-16:简易缓存接口
php
<?php
namespace Psr\SimpleCache;
interface CacheInterface
{
public function get(string $key, mixed $default = null): mixed;
public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool;
public function delete(string $key): bool;
public function clear(): bool;
public function getMultiple(iterable $keys, mixed $default = null): iterable;
public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool;
public function deleteMultiple(iterable $keys): bool;
public function has(string $key): bool;
}PSR-17:HTTP 工厂接口
php
<?php
namespace Psr\Http\Message;
interface RequestFactoryInterface
{
public function createRequest(string $method, $uri): RequestInterface;
}
interface ResponseFactoryInterface
{
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
}
interface StreamFactoryInterface
{
public function createStream(string $content = ''): StreamInterface;
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
public function createStreamFromResource(mixed $resource): StreamInterface;
}
interface UriFactoryInterface
{
public function createUri(string $uri = ''): UriInterface;
}
interface UploadedFileFactoryInterface
{
public function createUploadedFile(
StreamInterface $stream,
?int $size = null,
int $error = \UPLOAD_ERR_OK,
?string $clientFilename = null,
?string $clientMediaType = null
): UploadedFileInterface;
}
interface ServerRequestFactoryInterface
{
public function createServerRequest(
string $method,
$uri,
array $serverParams = []
): ServerRequestInterface;
}PSR-18:HTTP 客户端接口
php
<?php
namespace Psr\Http\Client;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
interface ClientInterface
{
public function sendRequest(RequestInterface $request): ResponseInterface;
}
interface NetworkExceptionInterface extends \RuntimeException
{
public function getRequest(): RequestInterface;
}
interface RequestExceptionInterface extends \RuntimeException
{
public function getRequest(): RequestInterface;
public function getResponse(): ?ResponseInterface;
}实战示例
场景一:使用 PSR 规范构建服务
php
<?php
declare(strict_types=1);
namespace App\Services;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseFactoryInterface;
class WeatherService
{
public function __construct(
private readonly ClientInterface $httpClient,
private readonly RequestFactoryInterface $requestFactory,
private readonly ResponseFactoryInterface $responseFactory,
private readonly CacheInterface $cache,
private readonly LoggerInterface $logger
) {}
public function getWeather(string $city): array
{
$cacheKey = "weather:{$city}";
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $cached;
}
$request = $this->requestFactory
->createRequest('GET', "https://api.weather.com/v1/current?city={$city}")
->withHeader('Accept', 'application/json');
try {
$response = $this->httpClient->sendRequest($request);
$data = json_decode((string) $response->getBody(), true);
$this->cache->set($cacheKey, $data, 1800);
$this->logger->info("Weather fetched", ['city' => $city]);
return $data;
} catch (\Throwable $e) {
$this->logger->error("Weather API error", ['city' => $city, 'error' => $e->getMessage()]);
throw $e;
}
}
}最佳实践
1. 面向接口编程
php
<?php
// ✅ 推荐:依赖 PSR 接口
use Psr\Log\LoggerInterface;
use Psr\Http\Client\ClientInterface;
class MyService
{
public function __construct(
private readonly LoggerInterface $logger,
private readonly ClientInterface $httpClient
) {}
}
// ❌ 不推荐:依赖具体实现
use Monolog\Logger;
use GuzzleHttp\Client;
class MyService
{
public function __construct(
private readonly Logger $logger,
private readonly Client $httpClient
) {}
}2. 常用 PSR 实现库
| PSR | 常用实现 |
|---|---|
| PSR-3 | Monolog |
| PSR-6 | Symfony Cache、Cache |
| PSR-7 | Nyholm/PSR-7、Guzzle PSR-7 |
| PSR-11 | PHP-DI、Symfony DI、Laravel Container |
| PSR-14 | Symfony EventDispatcher |
| PSR-15 | Laminas Stratigility、Relay |
| PSR-16 | Symfony Cache、Filecache |
| PSR-17 | Nyholm/PSR-17、Guzzle PSR-17 |
| PSR-18 | Guzzle、Symfony HTTP Client |
下一节
继续学习:Laravel 框架 — 了解 PHP 最流行的全栈框架。