vendor/can/rest/src/Can/RestBundle/EventListener/PreferenceAppliedListener.php line 61

Open in your IDE?
  1. <?php
  2. namespace Can\RestBundle\EventListener;
  3. use Can\RestBundle\CanRestBundle;
  4. use Can\RestBundle\Preference\DeclarationCallbackInterface;
  5. use Can\RestBundle\Preference\Preference;
  6. use Can\RestBundle\Preference\DeclarationPolicy;
  7. use Can\RestBundle\Preference\PreferenceConfiguration;
  8. use Can\RestBundle\Preference\PreferenceEventDispatcher;
  9. use Can\RestBundle\Preference\PreferenceList;
  10. use Can\RestBundle\Util\Types;
  11. use Symfony\Component\HttpFoundation\Response;
  12. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  13. use DomainException;
  14. use Throwable;
  15. /**
  16.  * A listener which declares honoured (applied) client preferences as elements
  17.  * in the HTTP Preference-Applied response header.
  18.  *
  19.  * @see https://tools.ietf.org/html/rfc7240
  20.  *
  21.  * @group preference
  22.  *
  23.  * @package can/rest-bundle
  24.  * @author lechecacharro <lechecacharro@gmail.com>
  25.  */
  26. class PreferenceAppliedListener
  27. {
  28.     /**
  29.      * @var PreferenceConfiguration
  30.      */
  31.     protected $configuration;
  32.     /**
  33.      * @var PreferenceEventDispatcher
  34.      */
  35.     protected $dispatcher;
  36.     /**
  37.      * PreferenceAppliedListener constructor.
  38.      *
  39.      * @param PreferenceConfiguration   $configuration
  40.      * @param PreferenceEventDispatcher $dispatcher
  41.      */
  42.     public function __construct(PreferenceConfiguration $configurationPreferenceEventDispatcher $dispatcher)
  43.     {
  44.         $this->configuration $configuration;
  45.         $this->dispatcher $dispatcher;
  46.     }
  47.     /**
  48.      * @param ResponseEvent $event
  49.      */
  50.     public function onResponse(ResponseEvent $event): void
  51.     {
  52.         // Skip processing if the component is not enabled
  53.         if (! $this->configuration->isEnabled()) {
  54.             return;
  55.         }
  56.         $request $event->getRequest();
  57.         // Skip processing if not in the service zone
  58.         if (! $request->attributes->get(CanRestBundle::ATTR_SERVICE_ZONE)) {
  59.             return;
  60.         }
  61.         // Skip processing if no preferences to apply/applied
  62.         if (! $request->attributes->has(CanRestBundle::ATTR_PREFERENCES) ||
  63.             ! $request->attributes->has(CanRestBundle::ATTR_PREFERENCES_APPLIED)) {
  64.             return;
  65.         }
  66.         $appliedPreferenceList $request->attributes->get(CanRestBundle::ATTR_PREFERENCES_APPLIED);
  67.         if (! $appliedPreferenceList instanceof PreferenceList) {
  68.             throw new DomainException(sprintf('Expected a %s instance, but got %s'PreferenceList::class,
  69.                 Types::getClassOrType($appliedPreferenceList)
  70.             ));
  71.         }
  72.         $declaredPreferenceList $this->declareAppliedPreferences($event->getResponse(), $appliedPreferenceList);
  73.         // Allow third parties to modify the list of preferences to declare
  74.         $event $this->dispatcher->dispatchPreferencesDeclared($declaredPreferenceList);
  75.         $request->attributes->set(CanRestBundle::ATTR_PREFERENCES_DECLARED$event->getPreferenceList());
  76.     }
  77.     /**
  78.      * Declares honoured (applied) preferences as elements of the HTTP
  79.      * Preference-Applied response header, according to the configured
  80.      * declaration policy.
  81.      *
  82.      * @see PreferenceConfiguration::getPolicyApplied()
  83.      *
  84.      * @param Response       $response              the HTTP response
  85.      * @param PreferenceList $appliedPreferenceList the applied preference list
  86.      *
  87.      * @return PreferenceList the declared preferences
  88.      */
  89.     protected function declareAppliedPreferences(Response $responsePreferenceList $appliedPreferenceList): PreferenceList
  90.     {
  91.         $declarationPolicy $this->configuration->getDeclarationPolicy();
  92.         switch ($declarationPolicy) {
  93.             case DeclarationPolicy::NONE:
  94.                 $preferences = [];
  95.                 break;
  96.             case DeclarationPolicy::WHITELISTED:
  97.                 $preferences $appliedPreferenceList->filter(function (Preference $preference) {
  98.                     return $this->configuration->isDeclarationWhitelisted($preference);
  99.                 });
  100.                 break;
  101.             case DeclarationPolicy::BLACKLISTED:
  102.                 $preferences $appliedPreferenceList->filter(function (Preference $preference) {
  103.                     return ! $this->configuration->isDeclarationBlacklisted($preference);
  104.                 });
  105.                 break;
  106.             case DeclarationPolicy::NECESSARY:
  107.                 $preferences $appliedPreferenceList->filter(function (Preference $preference) {
  108.                     return $this->configuration->isDeclarationNecessary($preference);
  109.                 });
  110.                 break;
  111.             case DeclarationPolicy::CUSTOM:
  112.                 $preferences $this->getCustomPreferenceList($appliedPreferenceList);
  113.                 break;
  114.             case DeclarationPolicy::ALL:
  115.                 $preferences $appliedPreferenceList->all();
  116.                 break;
  117.             default:
  118.                 throw new DomainException(sprintf('Unsupported preference applied policy: %d'$declarationPolicy));
  119.         }
  120.         $preferenceList = new PreferenceList(...$preferences);
  121.         // Skip if no preferences are to be declared
  122.         if ($preferenceList->isEmpty()) {
  123.             return $preferenceList;
  124.         }
  125.         // Else, declare the honoured (applied) preferences
  126.         $response->headers->set('Preference-Applied'implode(', 'array_map(function (Preference $preference) {
  127.             return $preference->toString(false);
  128.         }, $preferences)));
  129.         if ($this->configuration->isVariantCacheEnabled()) {
  130.             // If a server supports the optional application of a preference
  131.             // that might result in a variance to a cache's handling of a
  132.             // response entity, a Vary header field MUST be included in the
  133.             // response listing the Prefer header field regardless of whether
  134.             // the client actually used Prefer in the request.
  135.             $response->headers->set('Vary''Prefer');
  136.         } else {
  137.             // Alternatively, the server MAY include a Vary header with the
  138.             // special value "*" (...) Note, however, that use of the
  139.             // "Vary: *" header will make it impossible for a proxy to
  140.             // cache the response.
  141.             $response->headers->set('Vary''*');
  142.         }
  143.         return $preferenceList;
  144.     }
  145.     /**
  146.      * Applies the custom declaration service to figure out which honoured
  147.      * (applied) preferences should be included as elements of the HTTP
  148.      * Preference-Applied response header.
  149.      *
  150.      * @param PreferenceList $appliedPreferenceList the applied preference list
  151.      *
  152.      * @return string[]
  153.      */
  154.     protected function getCustomPreferenceList(PreferenceList $appliedPreferenceList): array
  155.     {
  156.         $declarationService $this->configuration->getDeclarationService();
  157.         if (is_callable($declarationService)) {
  158.             try {
  159.                 return $declarationService($appliedPreferenceList);
  160.             } catch (Throwable $t) {
  161.                 throw new DomainException(sprintf(
  162.                     'Failed to obtain the list of applied preferences to '.
  163.                     'declare: %s',
  164.                     $t->getMessage()
  165.                 ), $t->getCode(), $t);
  166.             }
  167.         }
  168.         if ($declarationService instanceof DeclarationCallbackInterface) {
  169.             try {
  170.                 return $declarationService->getDeclarablePreferences($appliedPreferenceList);
  171.             } catch (Throwable $t) {
  172.                 throw new DomainException(sprintf(
  173.                     'Failed to obtain the list of applied preferences to '.
  174.                     'declare: %s',
  175.                     $t->getMessage()
  176.                 ), $t->getCode(), $t);
  177.             }
  178.         }
  179.         throw new DomainException(sprintf(
  180.             'Invalid custom declaration service: expected either a callable '.
  181.             'or a %s instance, but got %s',
  182.             DeclarationCallbackInterface::class,
  183.             Types::getClassOrType($declarationService)
  184.         ));
  185.     }
  186. }