Installation:
composer require alterway/rest-hal-bundle:dev-master
Add to AppKernel.php:
new Alterway\Bundle\RestHalBundle\AwRestHalBundle(),
Register annotations in config.yml:
sensio_framework_extra:
router: { annotations: true }
request: { converters: true }
view: { annotations: true }
First Resource:
Create a resource class extending Alterway\Bundle\RestHalBundle\ApiResource\Resource:
namespace App\ApiResource;
use Alterway\Bundle\RestHalBundle\ApiResource\Resource;
use Symfony\Component\Routing\RouterInterface;
class PostResource extends Resource
{
public function __construct(RouterInterface $router, $postData)
{
parent::__construct($router);
$this->data = $postData;
}
protected function prepare()
{
$this->addLink('self', $this->generateUri());
$this->addEmbedded('author', new UserResource($this->router, $this->data['author']));
}
protected function generateUri()
{
return $this->router->generate('api_post_show', ['id' => $this->data['id']]);
}
}
First Controller:
use Alterway\Bundle\RestHalBundle\Response\HalResponse;
use App\ApiResource\PostResource;
class PostController
{
public function showAction($id)
{
$post = $this->getPostData($id);
return new HalResponse(new PostResource($this->get('router'), $post), 200);
}
}
Embedding Resources:
Use addEmbedded() to nest resources:
$this->addEmbedded('comments', array_map(
fn($comment) => new CommentResource($this->router, $comment),
$this->data['comments']
));
Dynamic Links:
Generate links dynamically in prepare():
$this->addLink('edit', $this->generateUri('api_post_edit', ['id' => $this->data['id']]));
$this->addLink('delete', $this->generateUri('api_post_delete', ['id' => $this->data['id']]));
Collection Resources: Extend for paginated collections:
class PostCollectionResource extends Resource
{
protected function prepare()
{
$this->addLink('self', $this->generateUri('api_posts'));
$this->addLink('next', $this->generateUri('api_posts', ['page' => $this->page + 1]));
}
}
Annotation-Driven:
/**
* @Hal(code="201")
*/
public function createAction(Request $request)
{
$post = $this->createPost($request->request->all());
return new PostResource($this->get('router'), $post);
}
Manual Response:
public function updateAction(Request $request, $id)
{
$post = $this->updatePost($id, $request->request->all());
$resource = new PostResource($this->get('router'), $post);
return new HalResponse($resource, 200, ['Location' => $resource->getLink('self')]);
}
Error Handling:
try {
$resource = new PostResource($this->get('router'), $post);
} catch (\Exception $e) {
return new HalResponse(null, 500, [], ['error' => $e->getMessage()]);
}
Doctrine Integration:
Use Doctrine\ORM\EntityManager to fetch entities:
$post = $this->getDoctrine()->getRepository('App:Post')->find($id);
Form Integration: Validate input with Symfony Forms and map to resources:
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if ($form->isValid()) {
$resource = new PostResource($this->get('router'), $post);
}
API Versioning: Use sub-resources for versioning:
$this->addLink('v2', $this->generateUri('api_v2_post_show', ['id' => $this->data['id']]));
Router Dependency:
RouterInterface into resources.$this->get('router') to the resource constructor.Circular References:
Post embeds User, User embeds Post).addEmbedded() sparingly or implement lazy-loading.Link Generation:
generateUri() separately:
$this->assertEquals('/posts/1', $resource->getLink('self'));
Annotation Conflicts:
@Hal annotations may conflict with other bundles (e.g., JMSSerializerBundle).HalResponse for complex cases.Inspect Raw HAL:
$resource = new PostResource($router, $post);
dump($resource->toArray()); // Raw HAL array
Check Links:
$resource->getLink('self'); // Returns null if link doesn’t exist
Symfony Profiler:
Enable web_profiler to inspect responses:
framework:
profiler: { only_exceptions: false }
Custom Link Types:
Extend Alterway\Bundle\RestHalBundle\Link\Link for custom link behaviors.
Resource Metadata:
Add metadata via setData():
$this->setData(['custom_field' => 'value']);
Response Modifiers:
Override HalResponse to add headers or modify output:
class CustomHalResponse extends HalResponse
{
public function __construct($resource, $status = 200, array $headers = [], array $context = [])
{
parent::__construct($resource, $status, $headers, $context);
$this->headers->set('X-API-Version', '1.0');
}
}
Deprecated dev-master:
alterway/rest-hal-bundle:dev-master only for testing. Prefer stable releases if available.Annotation Requirements:
sensio_framework_extra is properly configured. Missing annotations may cause silent failures.HAL Version:
nocarrier/hal:0.9.4. Inconsistent HAL versions may cause serialization issues.Avoid Deep Embedding:
addEmbedded() judiciously.Lazy-Loading:
For large collections, implement lazy-loading in prepare():
$this->addEmbedded('comments', function() {
return array_map(
fn($id) => new CommentResource($this->router, $this->getComment($id)),
$this->data['comment_ids']
);
});
Caching:
Cache HalResponse objects for static data:
$response = $this->get('cache')->get('api_post_' . $id, function() use ($resource) {
return new HalResponse($resource);
});
How can I help you explore Laravel packages today?