Standardized HTTP Response Handling in Hyperf Applications
This article presents two robust approaches for implementing consistent request-response formatting across Hyperf-based services. Both aim to decouple response logic from business controllers while supporting debug visibility, status code flexibility, and standardized data envelopes.
Approach One: Dependency-Injected Trait
A lightweight, controller-bound solution using constructor injection via annotations. Ideal for simple APIs where response headers are largely static.
namespace App\Support;
use App\Support\HttpCode;
use App\Support\HttpMessage;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\Di\Annotation\Inject;
use Throwable;
trait StandardResponse
{
#[Inject]
protected RequestInterface $request;
#[Inject]
protected ResponseInterface $response;
public function ok(mixed $payload = null, string $message = HttpMessage::OK): \Psr\Http\Message\ResponseInterface
{
return $this->response->json([
'code' => HttpCode::SUCCESS,
'message' => $message,
'data' => $payload
]);
}
public function error(Throwable $exception, int $statusCode = 200): \Psr\Http\Message\ResponseInterface
{
$isDebug = $this->request->input('debug') === $_ENV['APP_DEBUG'] || $_ENV['APP_DEBUG'] === 'true';
$content = [
'code' => HttpCode::ERROR,
'message' => $exception->getMessage(),
'data' => $isDebug ? $exception->getTraceAsString() : null
];
return $this->response->json($content, $statusCode);
}
public function unauthorized(Throwable $exception): \Psr\Http\Message\ResponseInterface
{
$isDebug = $this->request->input('debug') === $_ENV['APP_DEBUG'] || $_ENV['APP_DEBUG'] === 'true';
$content = [
'code' => HttpCode::UNAUTHORIZED,
'message' => $exception->getMessage(),
'data' => $isDebug ? $exception->getTraceAsString() : $exception->getMessage()
];
return $this->response->json($content, 401);
}
}
Approach Two: Static Container-Driven Utility
A more flexible, framework-agnostic pattern that abstracts response construction behind static helpers. Anables full control over status codes, headers, and serialization options without requiring dependency injection in every controller.
namespace App\Support;
use App\Support\HttpCode;
use App\Support\HttpMessage;
use Hyperf\Context\ApplicationContext;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
trait StandardResponse
{
public function ok(mixed $payload = null, string $message = HttpMessage::OK, int $status = 200): \Psr\Http\Message\ResponseInterface
{
return self::buildResponse([
'code' => HttpCode::SUCCESS,
'message' => $message,
'data' => $payload
], $status);
}
public function error(Throwable $exception, int $status = 400): \Psr\Http\Message\ResponseInterface
{
$isDebug = self::getRequest()->input('debug') === $_ENV['APP_DEBUG'] || $_ENV['APP_DEBUG'] === 'true';
$content = [
'code' => HttpCode::ERROR,
'message' => $exception->getMessage(),
'data' => $isDebug ? $exception->getTraceAsString() : null
];
return self::buildResponse($content, $status);
}
public function forbidden(Throwable $exception): \Psr\Http\Message\ResponseInterface
{
return self::error($exception, 403);
}
private static function buildResponse(mixed $data, int $status = 200): \Psr\Http\Message\ResponseInterface
{
$response = self::container()->get(ResponseInterface::class);
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return $response
->withStatus($status)
->withAddedHeader('Content-Type', 'application/json; charset=utf-8')
->withBody(new SwooleStream($json));
}
private static function getRequest(): RequestInterface
{
return self::container()->get(RequestInterface::class);
}
private static function container(): \Psr\Container\ContainerInterface
{
return ApplicationContext::getContainer();
}
}
Both implementations support environment-aware debugging output and align with Hyperf’s PSR-7/PSR-15 architecture. The first is suitable for rapid prototyping or small-scale services; the second better suits large applications requiring centralized response orchestration and middleware compatibility.