Response
Response Classes Overview
ODY’s HTTP component includes several classes for handling responses:
Response
: The main PSR-7 compatible HTTP response class with additional convenience methodsJsonResponse
: A helper class for creating JSON responsesStream
: Represents response bodies as streamsResponseEmitter
: Handles sending responses to the clientSwooleResponseEmitter
: Specialized response emitter for Swoole environments
Basic Response Handling in Controllers
In ODY controllers, you always return a response object that implements Psr\Http\Message\ResponseInterface
. Here’s a
basic example:
/**
* Get a specific resource
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param array $params
* @return ResponseInterface
*/
public function show(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
// Process the request and get data
$data = ['id' => (int)$id, 'name' => 'Example'];
// Return a JSON response
return $this->jsonResponse($response, $data);
}
Creating Responses
Basic Responses
To create a basic response with text content:
// Using the response object passed to your controller
$response = $response->withHeader('Content-Type', 'text/plain');
$response->getBody()->write('Hello, world!');
return $response;
With the ODY Response
class, you can use convenience methods:
// Using the ODY Response fluent interface
return $response
->text() // Sets Content-Type: text/plain
->body('Hello, world!');
JSON Responses
JSON responses are common in API development. Here’s how to create them:
// Standard PSR-7 way
$response = $response->withHeader('Content-Type', 'application/json');
$response->getBody()->write(json_encode($data));
return $response;
ODY provides more convenient methods:
// Using the ODY Response class
return $response->withJson($data);
// Or using the fluent interface
return $response
->json() // Sets Content-Type: application/json
->body(json_encode($data));
HTML Responses
For returning HTML content:
// Standard PSR-7 way
$response = $response->withHeader('Content-Type', 'text/html');
$response->getBody()->write('<html><body><h1>Hello</h1></body></html>');
return $response;
ODY’s fluent interface:
// Using the ODY Response fluent interface
return $response
->html() // Sets Content-Type: text/html
->body('<html><body><h1>Hello</h1></body></html>');
Setting HTTP Status Codes
Set the HTTP status code for your response:
// Standard PSR-7 way
$response = $response->withStatus(404);
// Using ODY's fluent interface
$response = $response->status(404);
Common HTTP status codes:
200
: OK (Success)201
: Created204
: No Content400
: Bad Request401
: Unauthorized403
: Forbidden404
: Not Found422
: Unprocessable Entity500
: Internal Server Error
Response Headers
Setting Headers
Add headers to your response:
// Standard PSR-7 way
$response = $response->withHeader('Content-Type', 'application/json');
// Add a header without removing existing values with the same name
$response = $response->withAddedHeader('Cache-Control', 'no-cache');
// Remove a header
$response = $response->withoutHeader('X-Powered-By');
Using ODY’s fluent interface:
$response = $response->header('Content-Type', 'application/json');
Common Headers
Here are some commonly used headers:
// CORS headers
$response = $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Caching headers
$response = $response
->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
->withHeader('Pragma', 'no-cache')
->withHeader('Expires', '0');
// Content security
$response = $response
->withHeader('Content-Security-Policy', "default-src 'self'")
->withHeader('X-Content-Type-Options', 'nosniff')
->withHeader('X-Frame-Options', 'DENY');
Working with Response Body
The response body is represented as a PSR-7 StreamInterface
. Here are different ways to work with it:
// Write to the body
$response->getBody()->write('Hello, world!');
// Replace the entire body with a new stream
$newStream = $streamFactory->createStream('New content');
$response = $response->withBody($newStream);
The ODY Response
class provides a simpler method:
// Set the body content
$response = $response->body('Hello, world!');
Response Examples for Different Scenarios
Success Response
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$users = [
['id' => 1, 'name' => 'John Doe'],
['id' => 2, 'name' => 'Jane Smith'],
];
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(200)
->withBody(json_encode([
'status' => 'success',
'data' => $users
]));
}
Using ODY’s Response
class:
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$users = [
['id' => 1, 'name' => 'John Doe'],
['id' => 2, 'name' => 'Jane Smith'],
];
return $response->withJson([
'status' => 'success',
'data' => $users
]);
}
Created Resource Response
public function store(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$data = $request->getParsedBody();
// Process and save the data
$newId = 123; // ID of newly created resource
return $response
->withHeader('Content-Type', 'application/json')
->withHeader('Location', "/api/resources/{$newId}")
->withStatus(201)
->withBody(json_encode([
'status' => 'success',
'message' => 'Resource created',
'data' => [
'id' => $newId
]
]));
}
Using ODY’s Response
class:
public function store(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$data = $request->getParsedBody();
// Process and save the data
$newId = 123; // ID of newly created resource
return $response
->status(201)
->header('Location', "/api/resources/{$newId}")
->withJson([
'status' => 'success',
'message' => 'Resource created',
'data' => [
'id' => $newId
]
]);
}
Error Response
public function show(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
// Resource not found
if (!$id || !$this->resourceExists($id)) {
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(404)
->withBody(json_encode([
'status' => 'error',
'message' => 'Resource not found'
]));
}
// ...
}
Using ODY’s Response
class:
public function show(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
// Resource not found
if (!$id || !$this->resourceExists($id)) {
return $response
->status(404)
->withJson([
'status' => 'error',
'message' => 'Resource not found'
]);
}
// ...
}
Validation Error Response
public function update(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
$data = $request->getParsedBody();
// Validation errors
$errors = [];
if (empty($data['name'])) {
$errors['name'] = 'Name is required';
}
if (empty($data['email'])) {
$errors['email'] = 'Email is required';
}
if (!empty($errors)) {
return $response
->withHeader('Content-Type', 'application/json')
->withStatus(422)
->withBody(json_encode([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $errors
]));
}
// ...
}
Using ODY’s Response
class:
public function update(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
$data = $request->getParsedBody();
// Validation errors
$errors = [];
if (empty($data['name'])) {
$errors['name'] = 'Name is required';
}
if (empty($data['email'])) {
$errors['email'] = 'Email is required';
}
if (!empty($errors)) {
return $response
->status(422)
->withJson([
'status' => 'error',
'message' => 'Validation failed',
'errors' => $errors
]);
}
// ...
}
File Download Response
public function download(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$filePath = '/path/to/file.pdf';
$fileName = 'document.pdf';
$fileStream = new \Ody\Foundation\Http\Stream(fopen($filePath, 'r'));
return $response
->withHeader('Content-Type', 'application/pdf')
->withHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"')
->withHeader('Content-Length', (string) filesize($filePath))
->withBody($fileStream);
}
No Content Response
public function delete(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
// Delete the resource
// ...
// Return 204 No Content
return $response->withStatus(204);
}
Using ODY’s Response
class:
public function delete(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
// Delete the resource
// ...
// Return 204 No Content
return $response->status(204);
}
Response Emitters
After your controller returns a response, it needs to be sent to the client. In standard PHP environments, the response is automatically emitted. However, in Swoole environments, you need to use a response emitter.
Standard Response Emitter
use Ody\Foundation\Http\ResponseEmitter;
$emitter = new ResponseEmitter();
$emitter->emit($response);
Swoole Response Emitter
use Ody\Foundation\Http\SwooleResponseEmitter;
$emitter = new SwooleResponseEmitter();
$emitter->emit($response);
The framework typically handles this for you, but it’s useful to understand how it works.
Creating a Helper Method for JSON Responses
To standardize your JSON responses, you can create a helper method in your controllers:
/**
* Helper method to create JSON responses
*
* @param ResponseInterface $response
* @param mixed $data
* @param int $status
* @return ResponseInterface
*/
private function jsonResponse(ResponseInterface $response, $data, int $status = 200): ResponseInterface
{
// Always set JSON content type
$response = $response->withHeader('Content-Type', 'application/json');
// Set status code
$response = $response->withStatus($status);
// If using our custom Response class
if ($response instanceof \Ody\Foundation\Http\Response) {
return $response->withJson($data);
}
// For other PSR-7 implementations
$response->getBody()->write(json_encode($data));
return $response;
}
Now you can use this method consistently in your controllers:
public function show(ServerRequestInterface $request, ResponseInterface $response, array $params): ResponseInterface
{
$id = $params['id'] ?? null;
if (!$id || !$this->resourceExists($id)) {
return $this->jsonResponse($response, [
'status' => 'error',
'message' => 'Resource not found'
], 404);
}
$data = $this->fetchResource($id);
return $this->jsonResponse($response, [
'status' => 'success',
'data' => $data
]);
}
Streaming Responses
For large responses, you can stream the content to avoid memory issues:
public function stream(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$response = $response
->withHeader('Content-Type', 'text/plain')
->withHeader('Transfer-Encoding', 'chunked');
$body = $response->getBody();
// Stream content in chunks
for ($i = 0; $i < 10; $i++) {
$body->write("Chunk " . $i . "\n");
// In a real application, you might flush the output buffer here
}
return $response;
}
In Swoole environments, streaming works differently. The framework handles this for you when you return a response with a large body.
Best Practices
Immutability: Remember that PSR-7 response objects are immutable. Methods like
withHeader()
return new instances rather than modifying the existing one.JSON Responses: Use a consistent format for your JSON responses. Consider standardizing on a structure like
{ status, data, meta }
or{ status, message, data, errors }
.Status Codes: Use appropriate HTTP status codes for different scenarios. Don’t just return 200 for everything.
Error Handling: Provide clear error messages and validation errors in your responses.
Headers: Set appropriate headers for content type, caching, and security.
Resource URLs: For created resources, include a
Location
header with the URL of the new resource.Response Size: For large responses, consider streaming or pagination.
Response Encoding: Always specify the character encoding (typically UTF-8) in your content type headers.
Consistency: Create helper methods to ensure consistent response formats across your application.