vendor/willdurand/hateoas/src/HateoasBuilder.php line 200

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Hateoas;
  4. use Doctrine\Common\Annotations\AnnotationReader;
  5. use Doctrine\Common\Annotations\FileCacheReader;
  6. use Hateoas\Configuration\Metadata\ConfigurationExtensionInterface;
  7. use Hateoas\Configuration\Metadata\Driver\AnnotationDriver;
  8. use Hateoas\Configuration\Metadata\Driver\ExtensionDriver;
  9. use Hateoas\Configuration\Metadata\Driver\XmlDriver;
  10. use Hateoas\Configuration\Metadata\Driver\YamlDriver;
  11. use Hateoas\Configuration\Provider\ChainProvider;
  12. use Hateoas\Configuration\Provider\ExpressionEvaluatorProvider;
  13. use Hateoas\Configuration\Provider\FunctionProvider;
  14. use Hateoas\Configuration\Provider\StaticMethodProvider;
  15. use Hateoas\Expression\LinkExpressionFunction;
  16. use Hateoas\Factory\EmbeddedsFactory;
  17. use Hateoas\Factory\LinkFactory;
  18. use Hateoas\Factory\LinksFactory;
  19. use Hateoas\Helper\LinkHelper;
  20. use Hateoas\Serializer\AddRelationsListener;
  21. use Hateoas\Serializer\ExclusionManager;
  22. use Hateoas\Serializer\JsonHalSerializer;
  23. use Hateoas\Serializer\Metadata\InlineDeferrer;
  24. use Hateoas\Serializer\SerializerInterface;
  25. use Hateoas\Serializer\XmlSerializer;
  26. use Hateoas\UrlGenerator\UrlGeneratorInterface;
  27. use Hateoas\UrlGenerator\UrlGeneratorRegistry;
  28. use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
  29. use JMS\Serializer\EventDispatcher\Events;
  30. use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
  31. use JMS\Serializer\Expression\ExpressionEvaluator;
  32. use JMS\Serializer\SerializerBuilder;
  33. use JMS\Serializer\Type\Parser;
  34. use Metadata\Cache\FileCache;
  35. use Metadata\Driver\DriverChain;
  36. use Metadata\Driver\FileLocator;
  37. use Metadata\MetadataFactory;
  38. use Metadata\MetadataFactoryInterface;
  39. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  40. class HateoasBuilder
  41. {
  42.     /**
  43.      * @var SerializerBuilder
  44.      */
  45.     private $serializerBuilder;
  46.     /**
  47.      * @var ExpressionLanguage
  48.      */
  49.     private $expressionLanguage;
  50.     /**
  51.      * @var ExpressionEvaluator
  52.      */
  53.     private $expressionEvaluator;
  54.     /**
  55.      * @var array
  56.      */
  57.     private $contextVariables = [];
  58.     /**
  59.      * @var SerializerInterface
  60.      */
  61.     private $xmlSerializer;
  62.     /**
  63.      * @var SerializerInterface
  64.      */
  65.     private $jsonSerializer;
  66.     /**
  67.      * @var UrlGeneratorRegistry
  68.      */
  69.     private $urlGeneratorRegistry;
  70.     /**
  71.      * @var ConfigurationExtensionInterface[]
  72.      */
  73.     private $configurationExtensions = [];
  74.     /**
  75.      * @var ChainProvider
  76.      */
  77.     private $chainProvider;
  78.     /**
  79.      * @var string[]
  80.      */
  81.     private $metadataDirs = [];
  82.     /**
  83.      * @var bool
  84.      */
  85.     private $debug false;
  86.     /**
  87.      * @var string
  88.      */
  89.     private $cacheDir;
  90.     /**
  91.      * @var AnnotationReader
  92.      */
  93.     private $annotationReader;
  94.     /**
  95.      * @var bool
  96.      */
  97.     private $includeInterfaceMetadata false;
  98.     public static function create(?SerializerBuilder $serializerBuilder null): HateoasBuilder
  99.     {
  100.         return new static($serializerBuilder);
  101.     }
  102.     public static function buildHateoas(): Hateoas
  103.     {
  104.         $builder = static::create();
  105.         return $builder->build();
  106.     }
  107.     public function __construct(?SerializerBuilder $serializerBuilder null)
  108.     {
  109.         $this->serializerBuilder    $serializerBuilder ?: SerializerBuilder::create();
  110.         $this->urlGeneratorRegistry = new UrlGeneratorRegistry();
  111.         $this->chainProvider        = new ChainProvider([
  112.             new FunctionProvider(),
  113.             new StaticMethodProvider(),
  114.         ]);
  115.     }
  116.     /**
  117.      * Build a configured Hateoas instance.
  118.      */
  119.     public function build(): Hateoas
  120.     {
  121.         $metadataFactory     $this->buildMetadataFactory();
  122.         $linkFactory         = new LinkFactory($this->urlGeneratorRegistry);
  123.         $this->contextVariables['link_helper'] = $linkHelper = new LinkHelper($linkFactory$metadataFactory);
  124.         $expressionEvaluator =  $this->getExpressionEvaluator();
  125.         foreach ($this->contextVariables as $name => $value) {
  126.             $expressionEvaluator->setContextVariable($name$value);
  127.         }
  128.         $this->chainProvider->addProvider(new ExpressionEvaluatorProvider($expressionEvaluator));
  129.         $linkFactory->setExpressionEvaluator($expressionEvaluator);
  130.         $exclusionManager    = new ExclusionManager(new ExpressionLanguageExclusionStrategy($expressionEvaluator));
  131.         $linksFactory        = new LinksFactory($metadataFactory$linkFactory$exclusionManager);
  132.         $embeddedsFactory    = new EmbeddedsFactory($metadataFactory$expressionEvaluator$exclusionManager);
  133.         if (null === $this->xmlSerializer) {
  134.             $this->setDefaultXmlSerializer();
  135.         }
  136.         if (null === $this->jsonSerializer) {
  137.             $this->setDefaultJsonSerializer();
  138.         }
  139.         $eventListeners = [
  140.             'xml' => new AddRelationsListener(
  141.                 $this->xmlSerializer,
  142.                 $linksFactory,
  143.                 $embeddedsFactory,
  144.                 new InlineDeferrer(),
  145.                 new InlineDeferrer()
  146.             ),
  147.             'json' => new AddRelationsListener(
  148.                 $this->jsonSerializer,
  149.                 $linksFactory,
  150.                 $embeddedsFactory,
  151.                 new InlineDeferrer(),
  152.                 new InlineDeferrer()
  153.             ),
  154.         ];
  155.         $this->serializerBuilder
  156.             ->addDefaultListeners()
  157.             ->configureListeners(static function (EventDispatcherInterface $dispatcher) use ($eventListeners): void {
  158.                 foreach ($eventListeners as $format => $listener) {
  159.                     $dispatcher->addListener(Events::POST_SERIALIZE, [$listener'onPostSerialize'], null$format);
  160.                 }
  161.             });
  162.         $this->serializerBuilder->addMetadataDirs($this->metadataDirs);
  163.         $this->serializerBuilder->setExpressionEvaluator($this->expressionEvaluator);
  164.         $jmsSerializer $this->serializerBuilder->build();
  165.         return new Hateoas($jmsSerializer$linkHelper);
  166.     }
  167.     public function setXmlSerializer(SerializerInterface $xmlSerializer): HateoasBuilder
  168.     {
  169.         $this->xmlSerializer $xmlSerializer;
  170.         return $this;
  171.     }
  172.     /**
  173.      * Set the default XML serializer (`XmlSerializer`).
  174.      */
  175.     public function setDefaultXmlSerializer(): HateoasBuilder
  176.     {
  177.         return $this->setXmlSerializer(new XmlSerializer());
  178.     }
  179.     public function setJsonSerializer(SerializerInterface $jsonSerializer): HateoasBuilder
  180.     {
  181.         $this->jsonSerializer $jsonSerializer;
  182.         return $this;
  183.     }
  184.     /**
  185.      * Set the default JSON serializer (`JsonHalSerializer`).
  186.      */
  187.     public function setDefaultJsonSerializer(): HateoasBuilder
  188.     {
  189.         return $this->setJsonSerializer(new JsonHalSerializer());
  190.     }
  191.     /**
  192.      * Add a new URL generator. If you pass `null` as name, it will be the
  193.      * default URL generator.
  194.      */
  195.     public function setUrlGenerator(?string $nameUrlGeneratorInterface $urlGenerator): HateoasBuilder
  196.     {
  197.         $this->urlGeneratorRegistry->set($name$urlGenerator);
  198.         return $this;
  199.     }
  200.     /**
  201.      * Add a new expression context variable.
  202.      *
  203.      * @param mixed  $value
  204.      */
  205.     public function setExpressionContextVariable(string $name$value): HateoasBuilder
  206.     {
  207.         $this->contextVariables[$name] = $value;
  208.         return $this;
  209.     }
  210.     public function setExpressionLanguage(ExpressionLanguage $expressionLanguage): HateoasBuilder
  211.     {
  212.         $this->expressionLanguage $expressionLanguage;
  213.         return $this;
  214.     }
  215.     public function addConfigurationExtension(ConfigurationExtensionInterface $configurationExtension): HateoasBuilder
  216.     {
  217.         $this->configurationExtensions[] = $configurationExtension;
  218.         return $this;
  219.     }
  220.     public function setDebug(bool $debug): HateoasBuilder
  221.     {
  222.         $this->debug = (bool) $debug;
  223.         return $this;
  224.     }
  225.     public function setCacheDir(string $dir): HateoasBuilder
  226.     {
  227.         if (!is_dir($dir)) {
  228.             $this->createDir($dir);
  229.         }
  230.         if (!is_writable($dir)) {
  231.             throw new \InvalidArgumentException(sprintf('The cache directory "%s" is not writable.'$dir));
  232.         }
  233.         $this->cacheDir $dir;
  234.         return $this;
  235.     }
  236.     /**
  237.      * @param bool $include Whether to include the metadata from the interfaces
  238.      */
  239.     public function includeInterfaceMetadata(bool $include): HateoasBuilder
  240.     {
  241.         $this->includeInterfaceMetadata = (bool) $include;
  242.         return $this;
  243.     }
  244.     /**
  245.      * Set a map of namespace prefixes to directories.
  246.      *
  247.      * This method overrides any previously defined directories.
  248.      *
  249.      * @param array $namespacePrefixToDirMap
  250.      */
  251.     public function setMetadataDirs(array $namespacePrefixToDirMap): HateoasBuilder
  252.     {
  253.         foreach ($namespacePrefixToDirMap as $dir) {
  254.             if (!is_dir($dir)) {
  255.                 throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.'$dir));
  256.             }
  257.         }
  258.         $this->metadataDirs $namespacePrefixToDirMap;
  259.         return $this;
  260.     }
  261.     /**
  262.      * Add a directory where the serializer will look for class metadata.
  263.      *
  264.      * The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume
  265.      * that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace.
  266.      *
  267.      * If you use an empty prefix, your metadata files would need to look like:
  268.      *
  269.      * ``my-dir/MyApplication.Entity.SomeObject.yml``
  270.      * ``my-dir/MyApplication.Entity.OtherObject.yml``
  271.      *
  272.      * If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like:
  273.      *
  274.      * ``my-dir/SomeObject.yml``
  275.      * ``my-dir/OtherObject.yml``
  276.      *
  277.      * Please keep in mind that you currently may only have one directory per namespace prefix.
  278.      *
  279.      * @param string $dir             The directory where metadata files are located.
  280.      * @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory.
  281.      */
  282.     public function addMetadataDir(string $dirstring $namespacePrefix ''): HateoasBuilder
  283.     {
  284.         if (!is_dir($dir)) {
  285.             throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.'$dir));
  286.         }
  287.         if (isset($this->metadataDirs[$namespacePrefix])) {
  288.             throw new \InvalidArgumentException(sprintf('There is already a directory configured for the namespace prefix "%s". Please use replaceMetadataDir() to override directories.'$namespacePrefix));
  289.         }
  290.         $this->metadataDirs[$namespacePrefix] = $dir;
  291.         return $this;
  292.     }
  293.     /**
  294.      * Add a map of namespace prefixes to directories.
  295.      *
  296.      * @param array $namespacePrefixToDirMap
  297.      */
  298.     public function addMetadataDirs(array $namespacePrefixToDirMap): HateoasBuilder
  299.     {
  300.         foreach ($namespacePrefixToDirMap as $prefix => $dir) {
  301.             $this->addMetadataDir($dir$prefix);
  302.         }
  303.         return $this;
  304.     }
  305.     /**
  306.      * Similar to addMetadataDir(), but overrides an existing entry.
  307.      */
  308.     public function replaceMetadataDir(string $dirstring $namespacePrefix ''): HateoasBuilder
  309.     {
  310.         if (!is_dir($dir)) {
  311.             throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.'$dir));
  312.         }
  313.         if (!isset($this->metadataDirs[$namespacePrefix])) {
  314.             throw new \InvalidArgumentException(sprintf('There is no directory configured for namespace prefix "%s". Please use addMetadataDir() for adding new directories.'$namespacePrefix));
  315.         }
  316.         $this->metadataDirs[$namespacePrefix] = $dir;
  317.         return $this;
  318.     }
  319.     private function buildMetadataFactory(): MetadataFactoryInterface
  320.     {
  321.         $annotationReader $this->annotationReader;
  322.         if (null === $annotationReader) {
  323.             $annotationReader = new AnnotationReader();
  324.             if (null !== $this->cacheDir) {
  325.                 $this->createDir($this->cacheDir '/annotations');
  326.                 $annotationReader = new FileCacheReader($annotationReader$this->cacheDir '/annotations'$this->debug);
  327.             }
  328.         }
  329.         $expressionEvaluator =  $this->getExpressionEvaluator();
  330.         $typeParser = new Parser();
  331.         if (!empty($this->metadataDirs)) {
  332.             $fileLocator    = new FileLocator($this->metadataDirs);
  333.             $metadataDriver = new DriverChain([
  334.                 new YamlDriver($fileLocator$expressionEvaluator$this->chainProvider$typeParser),
  335.                 new XmlDriver($fileLocator$expressionEvaluator$this->chainProvider$typeParser),
  336.                 new AnnotationDriver($annotationReader$expressionEvaluator$this->chainProvider$typeParser),
  337.             ]);
  338.         } else {
  339.             $metadataDriver = new AnnotationDriver($annotationReader$expressionEvaluator$this->chainProvider$typeParser);
  340.         }
  341.         $metadataDriver  = new ExtensionDriver($metadataDriver$this->configurationExtensions);
  342.         $metadataFactory = new MetadataFactory($metadataDrivernull$this->debug);
  343.         $metadataFactory->setIncludeInterfaces($this->includeInterfaceMetadata);
  344.         if (null !== $this->cacheDir) {
  345.             $this->createDir($this->cacheDir '/metadata');
  346.             $metadataFactory->setCache(new FileCache($this->cacheDir '/metadata'));
  347.         }
  348.         return $metadataFactory;
  349.     }
  350.     private function createDir(string $dir): void
  351.     {
  352.         if (is_dir($dir)) {
  353.             return;
  354.         }
  355.         if (false === @mkdir($dir0777true) && !is_dir($dir)) {
  356.             throw new \RuntimeException(sprintf('Could not create directory "%s".'$dir));
  357.         }
  358.     }
  359.     private function getExpressionLanguage(): ExpressionLanguage
  360.     {
  361.         if (null === $this->expressionLanguage) {
  362.             $this->expressionLanguage = new ExpressionLanguage();
  363.             $this->expressionLanguage->registerProvider(new LinkExpressionFunction());
  364.         }
  365.         return $this->expressionLanguage;
  366.     }
  367.     private function getExpressionEvaluator(): ExpressionEvaluator
  368.     {
  369.         if (null === $this->expressionEvaluator) {
  370.             $this->expressionEvaluator = new ExpressionEvaluator($this->getExpressionLanguage(), $this->contextVariables);
  371.         }
  372.         return $this->expressionEvaluator;
  373.     }
  374. }