1
0
Files
breadcrumb-the-shire/lib/Http/ApiResponse.php
fs 25370a1a55 add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00

159 lines
4.1 KiB
PHP

<?php
namespace MintyPHP\Http;
use MintyPHP\Service\Audit\ApiAuditService;
class ApiResponse
{
public static function success(array $data = [], int $status = 200): never
{
self::send($data, $status);
}
public static function created(array $data = []): never
{
self::success($data, 201);
}
public static function noContent(): never
{
self::send(null, 204);
}
public static function error(string $error, int $status = 400, array $extra = []): never
{
$body = array_merge(['error' => $error], $extra);
self::send($body, $status, $error);
}
public static function unauthorized(): never
{
self::error('unauthorized', 401);
}
public static function forbidden(string $detail = 'forbidden'): never
{
self::error($detail, 403);
}
public static function notFound(string $detail = 'not_found'): never
{
self::error($detail, 404);
}
public static function methodNotAllowed(): never
{
self::error('method_not_allowed', 405);
}
public static function validationError(array $errors): never
{
self::error('validation_error', 422, ['errors' => $errors]);
}
public static function tooManyRequests(int $retryAfter = 60): never
{
self::send(
['error' => 'rate_limit_exceeded'],
429,
'rate_limit_exceeded',
['Retry-After: ' . $retryAfter]
);
}
/**
* Transform a service result (['ok' => bool, ...] pattern) into an API response.
*/
public static function fromServiceResult(array $result, int $successStatus = 200): never
{
if ($result['ok'] ?? false) {
$data = $result;
unset($data['ok']);
self::success($data, $successStatus);
}
$status = (int) ($result['status'] ?? 0);
if ($status < 400) {
$status = 422;
}
$error = (string) ($result['error'] ?? 'operation_failed');
$errors = $result['errors'] ?? [];
$extra = [];
if ($errors) {
$extra['errors'] = $errors;
}
self::error($error, $status, $extra);
}
/**
* Read JSON request body into an array.
*/
public static function readJsonBody(): array
{
$raw = file_get_contents('php://input');
if ($raw === false || $raw === '') {
return [];
}
$data = json_decode($raw, true);
return is_array($data) ? $data : [];
}
/**
* Require specific HTTP method(s).
*/
public static function requireMethod(string ...$methods): void
{
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
if (!in_array($method, $methods, true)) {
self::methodNotAllowed();
}
}
/**
* Require API authentication.
*/
public static function requireAuth(): void
{
if (!ApiAuth::isAuthenticated()) {
self::unauthorized();
}
}
/**
* Require a specific permission key.
*/
public static function requirePermission(string $key): void
{
self::requireAuth();
if (!ApiAuth::hasPermission($key)) {
self::forbidden();
}
}
private static function send(?array $body, int $status, ?string $errorCode = null, array $headers = []): never
{
http_response_code($status);
foreach ($headers as $headerLine) {
header($headerLine);
}
if ($body === null) {
ApiAuditService::finish($status, $errorCode);
die();
}
$json = json_encode($body, JSON_UNESCAPED_UNICODE);
if (!is_string($json)) {
$status = 500;
http_response_code($status);
$errorCode = $errorCode ?: 'serialization_error';
$json = json_encode(['error' => 'serialization_error'], JSON_UNESCAPED_UNICODE) ?: '{"error":"serialization_error"}';
}
header('Content-Type: application/json; charset=utf-8');
ApiAuditService::finish($status, $errorCode);
die($json);
}
}