phpsoftbox / application
Application core for the PhpSoftBox framework
Requires
- php: ^8.4
- phpsoftbox/collection: dev-master
- phpsoftbox/error-formatter: dev-master
- phpsoftbox/http-client: dev-master
- phpsoftbox/http-emitter: dev-master
- phpsoftbox/http-message: dev-master
- phpsoftbox/rate-limiter: dev-master
- phpsoftbox/router: dev-master
- phpsoftbox/view: dev-master
- psr/container: ^2.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.93
- phpsoftbox/cli-app: dev-master
- phpsoftbox/cs-fixer: ^1.1.0
- phpsoftbox/validator: dev-master
- phpunit/phpunit: ^11.2
This package is auto-updated.
Last update: 2026-03-18 21:12:27 UTC
README
Минимальное приложение для работы с PSR-15 пайплайном.
Быстрый старт
use PhpSoftBox\Application\Application; use PhpSoftBox\Application\ErrorHandler\JsonExceptionHandler; use PhpSoftBox\Application\ErrorHandler\HtmlExceptionHandler; use PhpSoftBox\Application\ErrorHandler\ContentNegotiationExceptionHandler; use PhpSoftBox\Application\Middleware\ErrorHandlerMiddleware; use PhpSoftBox\Router\Router; $router = new Router($resolver, $dispatcher, $collector); $exceptionHandler = new ContentNegotiationExceptionHandler( new JsonExceptionHandler($responseFactory, $streamFactory, includeDetails: true), new HtmlExceptionHandler($responseFactory, $streamFactory, includeDetails: true), ); $app = new Application($router, [ new ErrorHandlerMiddleware($exceptionHandler), ]); $response = $app->handle($request);
Настройка формата ошибок (Deciders)
ContentNegotiationExceptionHandler поддерживает реестр deciders для выбора формата ответа на ошибку.
Decider возвращает ExceptionFormat::JSON, ExceptionFormat::HTML или null (тогда срабатывает стандартная логика по Accept и X-Requested-With).
use PhpSoftBox\Application\ErrorHandler\ContentNegotiationExceptionHandler; use PhpSoftBox\Application\ErrorHandler\ExceptionFormat; use PhpSoftBox\Application\ErrorHandler\ExceptionFormatDeciderInterface; use PhpSoftBox\Application\ErrorHandler\ExceptionFormatDeciderRegistry; use PhpSoftBox\Application\ErrorHandler\HtmlExceptionHandler; use PhpSoftBox\Application\ErrorHandler\JsonExceptionHandler; use Psr\Http\Message\ServerRequestInterface; use Throwable; final class ApiExceptionDecider implements ExceptionFormatDeciderInterface { public function __invoke(Throwable $exception, ServerRequestInterface $request): ?ExceptionFormat { $path = ltrim($request->getUri()->getPath(), '/'); if (str_starts_with($path, 'api/')) { return ExceptionFormat::JSON; } if ($request->getHeaderLine('X-Inertia') !== '') { return ExceptionFormat::HTML; } return null; } } $deciders = new ExceptionFormatDeciderRegistry([ new ApiExceptionDecider(), ]); $exceptionHandler = new ContentNegotiationExceptionHandler( new JsonExceptionHandler($responseFactory, $streamFactory, includeDetails: true), new HtmlExceptionHandler($responseFactory, $streamFactory, includeDetails: true), $deciders, );
Репортинг ошибок в ErrorHub
use PhpSoftBox\Application\ErrorHandler\DefaultExceptionHandler; use PhpSoftBox\Application\ErrorHandler\ErrorHubExceptionReporter; use PhpSoftBox\Application\ErrorHandler\LoggerExceptionReporter; use PhpSoftBox\Application\Middleware\ErrorHandlerMiddleware; $errorhubReporter = new ErrorHubExceptionReporter( baseUrl: 'https://errorhub.getstash-dev.ru', projectKey: 'YOUR_PROJECT_KEY', token: 'YOUR_BEARER_TOKEN', // опционально username: null, // опционально (basic auth) password: null, tags: ['env:prod', 'app:backend'], ); $fallback = new ContentNegotiationExceptionHandler( new JsonExceptionHandler($responseFactory, $streamFactory, includeDetails: true), new HtmlExceptionHandler($responseFactory, $streamFactory, includeDetails: true), ); $exceptionHandler = new DefaultExceptionHandler( fallbackHandler: $fallback, responseFactory: $responseFactory, session: $session, reporters: [ new LoggerExceptionReporter($logger), $errorhubReporter, ], ); $app = new Application($router, [ new ErrorHandlerMiddleware($exceptionHandler), ]);
Если нужно добавить теги на уровне конкретного исключения:
use PhpSoftBox\Application\ErrorHandler\ErrorHubExceptionInterface; use PhpSoftBox\Application\ErrorHandler\ErrorHubExceptionTrait; final class BillingException extends \RuntimeException implements ErrorHubExceptionInterface { use ErrorHubExceptionTrait; } $e = new BillingException('Payment failed'); $e->setErrorHubTags(['module:billing']); $e->setErrorHubContext(['order_id' => 42]);
RouterFactory + RouteCache
use PhpSoftBox\Application\RouterFactory; use PhpSoftBox\Router\Cache\RouteCache; use PhpSoftBox\Router\Dispatcher; $cache = new RouteCache($cacheStorage); $factory = new RouterFactory(new Dispatcher(), $cache); $router = $factory->create(function ($routes) { $routes->get('/users', [UserController::class, 'index']); });
AppFactory
use PhpSoftBox\Application\AppFactory; $app = AppFactory::createFromContainer($container, environment: 'prod'); if (!$app->routesCached()) { require __DIR__ . '/routes.php'; }
Регистрация middleware
use PhpSoftBox\Application\Application; use PhpSoftBox\Application\Middleware\ErrorHandlerMiddleware; use PhpSoftBox\Application\Middleware\RequestSizeLimitMiddleware; use PhpSoftBox\Session\SessionMiddleware; $app = new Application($router, container: $container); $app->add(new ErrorHandlerMiddleware($exceptionHandler), priority: 100); $app->add(RequestSizeLimitMiddleware::class); $app->alias('session', SessionMiddleware::class); $app->middlewareGroup('web', ['session']);
Группы middleware
$webApp = $app->withMiddlewareGroups(['web']); $response = $webApp->handle($request);
Регистрация роутов через Application
$app->get('/users', [UserController::class, 'index']); $app->post('/users', [UserController::class, 'store']);
Методы проксируются в RouteCollector, если приложение создано с Router.
Middleware для контроллеров
$app->controllerMiddleware(UserController::class, ['auth']); $app->controllerMiddleware(UserController::class, ['admin'], only: ['store', 'update']);
Рекомендуемый путь привязки Middleware — регистрация через Router на маршруты/группы; контроллеры/экшены используйте точечно.
Группы middleware для маршрутов
use PhpSoftBox\Application\Middleware\KernelRouteMiddlewareResolver; use PhpSoftBox\Router\Dispatcher; use PhpSoftBox\Router\Router; $app->alias('auth', \PhpSoftBox\Auth\Middleware\AuthMiddleware::class); $app->middlewareGroup('api', ['auth']); $dispatcher = new Dispatcher( handlerResolver: null, middlewareResolver: new KernelRouteMiddlewareResolver($app->middlewareManager(), $container), ); $router = new Router($resolver, $dispatcher, $collector); $collector->group('/api', function ($routes) { $routes->get('/users', [UserController::class, 'index']); }, ['api']);
Ответы
В приложении доступны готовые ответы:
JsonResponseHtmlResponseXmlResponseTextResponse
Пример:
use PhpSoftBox\Application\Response\JsonResponse; return new JsonResponse(['ok' => true]);
Ошибки роутера
InvalidRouteParameterException (например, когда параметр не проходит валидацию) в прод-режиме
возвращает 404 Not Found. В debug-режиме сообщение исключения возвращается в ответе.
Авторизация приватных каналов Broadcaster
Обычно требуется эндпоинт /broadcast/auth, который выдаёт auth для приватных каналов.
Пример с PushrChannelAuth:
use PhpSoftBox\Broadcaster\Pushr\PushrChannelAuth; use PhpSoftBox\Http\Message\Response; use Psr\Http\Message\ServerRequestInterface; use function json_encode; $app->post('/broadcast/auth', function (ServerRequestInterface $request, \PhpSoftBox\Broadcaster\Channel\ChannelRegistry $channels): Response { $data = (array) ($request->getParsedBody() ?? []); $socketId = (string) ($data['socket_id'] ?? ''); $channel = (string) ($data['channel'] ?? ''); $channelData = $data['channel_data'] ?? null; $authorization = $channels->authorize($channel, $request); if (!$authorization->authorized()) { return new Response(403); } $channelData = $authorization->channelData() ?? $channelData; $auth = PushrChannelAuth::token('app-1', 'secret-1', $socketId, $channel, $channelData); return new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'auth' => $auth, 'channel_data' => $channelData, ])); });
socket_id клиент получает из события connection после подключения к WebSocket.