nicram
nicram

Reputation: 373

Symfony FOSRestBundle add custom header to response

I use FOSRestBundle in Symfony 4 to API project. I use annotations and in controller I have for example

use FOS\RestBundle\Controller\Annotations as Rest;

/**
 * @Rest\Get("/api/user", name="index",)
 * @param UserRepository $userRepository
 * @return array
 */
public function index(UserRepository $userRepository): array
{
return ['status' => 'OK', 'data' => ['users' => $userRepository->findAll()]];
}

config/packages/fos_rest.yaml

fos_rest:
    body_listener: true
    format_listener:
        rules:
            - { path: '^/api', priorities: ['json'], fallback_format: json, prefer_extension: false }
    param_fetcher_listener: true
    view:
        view_response_listener: 'force'
        formats:
            json: true

Now I'd like to add custom header 'X-Total-Found' to my response. How to do it?

Upvotes: 0

Views: 2400

Answers (2)

Gary
Gary

Reputation: 145

I ran into the same issue. We wanted to move pagination meta information to the headers and leave the response without an envelope (data and meta properties).

My Environment

  • Symfony Version 5.2
  • PHP Version 8
  • FOS Rest Bundle

STEP 1: Create an object to hold the header info

    // src/Rest/ResponseHeaderBag.php
    namespace App\Rest;
    
    /**
     * Store header information generated in the controller. This same
     * object is used in the response subscriber.
     * @package App\Rest
     */
    class ResponseHeaderBag
    {
        protected array $data = [];
    
        /**
         * @return array
         */
        public function getData(): array
        {
            return $this->data;
        }
    
        /**
         * @param array $data
         * @return ResponseHeaderBag
         */
        public function setData(array $data): ResponseHeaderBag
        {
            $this->data = $data;
            return $this;
        }
    
        public function addData(string $key, $datum): ResponseHeaderBag
        {
            $this->data[$key] = $datum;
            return $this;
        }
    }

STEP 2: Inject the ResponseHeaderBag into the controller action

 public function searchCustomers(
    ResponseHeaderBag $responseHeaderBag
): array {
   ...
   ...
   ...
   // replace magic strings and numbers with class constants and real values.
   $responseHeaderBag->add('X-Pagination-Count', 8392); 
   ...
   ...
   ...
}

STEP 3: Register a Subscriber and listen for the Response Kernel event

    // config/services.yaml        
    App\EventListener\ResponseSubscriber:
      tags:
         - kernel.event_subscriber

Subscribers are a great way to listen for events.

    // src/EventListener/ResponseSubscriber
    namespace App\EventListener;
    
    use App\Rest\ResponseHeaderBag;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    use Symfony\Component\HttpKernel\Event\ResponseEvent;
    use Symfony\Component\HttpKernel\KernelEvents;

    class ResponseSubscriber implements EventSubscriberInterface
    {
        public function __construct(
            protected ResponseHeaderBag $responseHeaderBag
        ){
        }

        /**
         * @inheritDoc
         */
        public static function getSubscribedEvents()
        {
            return [
                KernelEvents::RESPONSE => ['addAdditionalResponseHeaders']
            ];
        }


        /**
         * Add the response headers created elsewhere in the code.
         * @param ResponseEvent $event
         */
        public function addAdditionalResponseHeaders(ResponseEvent $event): void
        {
            $response = $event->getResponse();
            foreach ($this->responseHeaderBag->getData() as $key => $datum) {
                $response->headers->set($key, $datum);
            }
        }
    }

Upvotes: 0

You are relying in FOSRestBundle ViewListener, so that gives you limited options, like not being able to pass custom headers. In order to achieve what you want, you will need to call $this->handleView() from your controller and pass it a valid View instance.

You can use the View::create() factory method or the controller $this->view() shortcut. Both take as arguments the array of your data, the status code, and a response headers array. Then, you can set up your custom header there, but you will have to do that for every call.

The other option you have, which is more maintainable, is register a on_kernel_response event listener/subscriber and somehow pass it the value of your custom header (you could store it in a request attribute for example).

Those are the two options you have. You may have a third one, but I cannot come up with it at the minute.


Upvotes: 2

Related Questions