A minimal and structured way to handle internal service communication in PHP applications.
Instead of returning null, false, throwing exceptions for expected failures, or using loosely structured arrays, ServiceResponse provides a consistent
object containing success status, message, data, and errors. This standardization improves service orchestration, error handling, debugging, and API response
consistency.
The class supports response chaining, allowing higher-level services to preserve and expose root causes from lower-level operations.
- Core
ServiceResponseclass with fluent, chainable methods - Structured error aggregation
- Previous response chaining for root-cause tracing
- Helper functions for quick response creation
- Consistent structure for success and failure cases
[
'success' => true|false,
'message' => ?string,
'data' => mixed,
'errors' => array,
]return (new ServiceResponse())
->withMessage('Payment processed successfully')
->withData(['transaction_id' => $transactionId]);return (new ServiceResponse())
->withSuccess(false)
->withMessage('Payment failed')
->withError('card', 'Card declined');return (new ServiceResponse())
->withSuccess(false)
->withMessage('Validation failed')
->withErrors([
'email' => ['Email is required'],
'password' => ['Password must be at least 8 characters'],
]);When one service depends on another, you can attach the previous response to preserve context.
$paymentResponse = $paymentService->charge($order);
if ($paymentResponse->wasNotSuccessful()) {
return (new ServiceResponse())
->withSuccess(false)
->withMessage('Order failed')
->withPrevious($paymentResponse);
}
return (new ServiceResponse())
->withMessage('Order completed')
->withPrevious($paymentResponse);$errors = $response->getAllErrors();$root = $response->getRootCause();Helper functions provide a concise way to create responses without instantiating the class directly.
return service_response_success(
data: ['user_id' => $user->id],
message: 'User authenticated'
);return service_response_fail(
errors: ['auth' => ['Invalid credentials']],
message: 'Authentication failed'
);try {
$gateway->charge($card);
} catch (Throwable $e) {
return service_response_from_exception($e);
}public function authenticate(string $email, string $password): ServiceResponse
{
$user = $this->repo->findByEmail($email);
if (!$user || !$this->hasher->check($password, $user->password)) {
return service_response_fail(
['auth' => ['Invalid credentials']],
'Authentication failed'
);
}
return service_response_success(
['user_id' => $user->id],
'Authenticated successfully'
);
}public function charge(Order $order): ServiceResponse
{
if ($order->total <= 0) {
return service_response_fail(
['amount' => ['Invalid order total']],
'Payment failed'
);
}
$transactionId = $this->gateway->charge($order);
return service_response_success(
['transaction_id' => $transactionId],
'Payment processed'
);
}$auth = $authService->authenticate($email, $password);
if ($auth->wasNotSuccessful()) {
return $auth;
}
$payment = $paymentService->charge($order);
if ($payment->wasNotSuccessful()) {
return (new ServiceResponse())
->withSuccess(false)
->withMessage('Checkout failed')
->withPrevious($payment);
}
return (new ServiceResponse())
->withMessage('Checkout completed')
->withPrevious($payment);ServiceResponse implements JsonSerializable, allowing it to be safely encoded:
return json_encode($response);
// You can also do:
$response->jsonSerializeFrameworks like Laravel will automatically serialize the response:
return response()->json($response);Use the response status instead of exceptions for expected outcomes.
$response = $paymentService->charge($order);
if ($response->wasSuccessful()) {
// proceed with fulfillment
$fulfillmentService->dispatch($order);
} else {
// log and return structured failure
logger()->warning('Payment failed', $response->toArray());
return $response;
}- Errors are grouped by key for predictable handling
getAllErrors()merges errors from chained responsestoArray()produces API-ready output- Responses can be safely returned across service boundaries