usmanzahid / service-response
A simple and effective way of communication between different services among your PHP application.
Requires
- php: ^8.1
README
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.
Includes
- 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
Response Structure
[
'success' => true|false,
'message' => ?string,
'data' => mixed,
'errors' => array,
]
Basic Usage
Success Response
return (new ServiceResponse()) ->withMessage('Payment processed successfully') ->withData(['transaction_id' => $transactionId]);
Failure Response
return (new ServiceResponse()) ->withSuccess(false) ->withMessage('Payment failed') ->withError('card', 'Card declined');
Adding Multiple Errors
return (new ServiceResponse()) ->withSuccess(false) ->withMessage('Validation failed') ->withErrors([ 'email' => ['Email is required'], 'password' => ['Password must be at least 8 characters'], ]);
Chaining Responses Between Services
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);
Accessing Aggregated Errors
$errors = $response->getAllErrors();
Getting the Root Cause
$root = $response->getRootCause();
Helper Functions
Helper functions provide a concise way to create responses without instantiating the class directly.
Success Helper
return service_response_success( data: ['user_id' => $user->id], message: 'User authenticated' );
Failure Helper
return service_response_fail( errors: ['auth' => ['Invalid credentials']], message: 'Authentication failed' );
From Exception
try { $gateway->charge($card); } catch (Throwable $e) { return service_response_from_exception($e); }
Real-World Examples
Authentication Service
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' ); }
Payment Service
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' ); }
Orchestrating Multiple Services
$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);
JSON Serialization
ServiceResponse implements JsonSerializable, allowing it to be safely encoded:
return json_encode($response); // You can also do: $response->jsonSerialize
Frameworks like Laravel will automatically serialize the response:
return response()->json($response);
Exceptionless Flow Control
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; }
Notes
- 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