|
@@ -0,0 +1,345 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace JMS\Serializer;
|
|
|
+
|
|
|
+use Metadata\MetadataFactory;
|
|
|
+use JMS\Serializer\Metadata\Driver\AnnotationDriver;
|
|
|
+use JMS\Serializer\Handler\HandlerRegistry;
|
|
|
+use JMS\Serializer\Construction\UnserializeObjectConstructor;
|
|
|
+use PhpCollection\Map;
|
|
|
+use JMS\Serializer\EventDispatcher\EventDispatcher;
|
|
|
+use Metadata\Driver\DriverChain;
|
|
|
+use JMS\Serializer\Metadata\Driver\YamlDriver;
|
|
|
+use JMS\Serializer\Metadata\Driver\XmlDriver;
|
|
|
+use Metadata\Driver\FileLocator;
|
|
|
+use JMS\Serializer\Handler\DateTimeHandler;
|
|
|
+use JMS\Serializer\Handler\ArrayCollectionHandler;
|
|
|
+use JMS\Serializer\Construction\ObjectConstructorInterface;
|
|
|
+use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
|
|
|
+use JMS\Serializer\Naming\CamelCaseNamingStrategy;
|
|
|
+use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
|
|
+use Doctrine\Common\Annotations\AnnotationReader;
|
|
|
+use Doctrine\Common\Annotations\FileCacheReader;
|
|
|
+use Metadata\Cache\FileCache;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Builder for serializer instances.
|
|
|
+ *
|
|
|
+ * This makes it easier for you to wire all the different classes together.
|
|
|
+ *
|
|
|
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
|
|
+ */
|
|
|
+class SerializerBuilder
|
|
|
+{
|
|
|
+ private $metadataDirs = array();
|
|
|
+ private $handlerRegistry;
|
|
|
+ private $handlersConfigured = false;
|
|
|
+ private $eventDispatcher;
|
|
|
+ private $listenersConfigured = false;
|
|
|
+ private $objectConstructor;
|
|
|
+ private $serializationVisitors;
|
|
|
+ private $deserializationVisitors;
|
|
|
+ private $visitorsAdded = false;
|
|
|
+ private $propertyNamingStrategy;
|
|
|
+ private $debug = false;
|
|
|
+ private $cacheDir;
|
|
|
+ private $annotationReader;
|
|
|
+
|
|
|
+ public static function create()
|
|
|
+ {
|
|
|
+ return new static();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function __construct()
|
|
|
+ {
|
|
|
+ $this->handlerRegistry = new HandlerRegistry();
|
|
|
+ $this->eventDispatcher = new EventDispatcher();
|
|
|
+ $this->serializationVisitors = new Map();
|
|
|
+ $this->deserializationVisitors = new Map();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setAnnotationReader(AnnotationReader $reader)
|
|
|
+ {
|
|
|
+ $this->annotationReader = $reader;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setDebug($bool)
|
|
|
+ {
|
|
|
+ $this->debug = (boolean) $bool;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setCacheDir($dir)
|
|
|
+ {
|
|
|
+ if ( ! is_dir($dir)) {
|
|
|
+ $this->createDir($dir);
|
|
|
+ }
|
|
|
+ if ( ! is_writable($dir)) {
|
|
|
+ throw new \InvalidArgumentException(sprintf('The cache directory "%s" is not writable.', $dir));
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->cacheDir = $dir;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function addDefaultHandlers()
|
|
|
+ {
|
|
|
+ $this->handlersConfigured = true;
|
|
|
+ $this->handlerRegistry->registerSubscribingHandler(new DateTimeHandler());
|
|
|
+ $this->handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler());
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function configureHandlers(\Closure $closure)
|
|
|
+ {
|
|
|
+ $this->handlersConfigured = true;
|
|
|
+ $closure($this->handlerRegistry);
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function addDefaultListeners()
|
|
|
+ {
|
|
|
+ $this->listenersConfigured = true;
|
|
|
+ $this->eventDispatcher->addSubscriber(new DoctrineProxySubscriber());
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function configureListeners(\Closure $closure)
|
|
|
+ {
|
|
|
+ $this->listenersConfigured = true;
|
|
|
+ $closure($this->eventDispatcher);
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setObjectConstructor(ObjectConstructorInterface $constructor)
|
|
|
+ {
|
|
|
+ $this->objectConstructor = $constructor;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setPropertyNamingStrategy(PropertyNamingStrategyInterface $propertyNamingStrategy)
|
|
|
+ {
|
|
|
+ $this->propertyNamingStrategy = $propertyNamingStrategy;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setSerializationVisitor($format, VisitorInterface $visitor)
|
|
|
+ {
|
|
|
+ $this->visitorsAdded = true;
|
|
|
+ $this->serializationVisitors->set($format, $visitor);
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function setDeserializationVisitor($format, VisitorInterface $visitor)
|
|
|
+ {
|
|
|
+ $this->visitorsAdded = true;
|
|
|
+ $this->deserializationVisitors->set($format, $visitor);
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function addDefaultSerializationVisitors()
|
|
|
+ {
|
|
|
+ $this->initializePropertyNamingStrategy();
|
|
|
+
|
|
|
+ $this->visitorsAdded = true;
|
|
|
+ $this->serializationVisitors->setAll(array(
|
|
|
+ 'xml' => new XmlSerializationVisitor($this->propertyNamingStrategy),
|
|
|
+ 'yml' => new YamlSerializationVisitor($this->propertyNamingStrategy),
|
|
|
+ 'json' => new JsonSerializationVisitor($this->propertyNamingStrategy),
|
|
|
+ ));
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function addDefaultDeserializationVisitors()
|
|
|
+ {
|
|
|
+ $this->initializePropertyNamingStrategy();
|
|
|
+
|
|
|
+ $this->visitorsAdded = true;
|
|
|
+ $this->deserializationVisitors->setAll(array(
|
|
|
+ 'xml' => new XmlDeserializationVisitor($this->propertyNamingStrategy),
|
|
|
+ 'json' => new JsonDeserializationVisitor($this->propertyNamingStrategy),
|
|
|
+ ));
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets a map of namespace prefixes to directories.
|
|
|
+ *
|
|
|
+ * This method overrides any previously defined directories.
|
|
|
+ *
|
|
|
+ * @param array<string,string> $namespacePrefixToDirMap
|
|
|
+ *
|
|
|
+ * @return SerializerBuilder
|
|
|
+ */
|
|
|
+ public function setMetadataDirs(array $namespacePrefixToDirMap)
|
|
|
+ {
|
|
|
+ foreach ($namespacePrefixToDirMap as $prefix => $dir) {
|
|
|
+ if ( ! is_dir($dir)) {
|
|
|
+ throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->metadataDirs = $namespacePrefixToDirMap;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adds a directory where the serializer will look for class metadata.
|
|
|
+ *
|
|
|
+ * The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume
|
|
|
+ * that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace.
|
|
|
+ *
|
|
|
+ * If you use an empty prefix, your metadata files would need to look like:
|
|
|
+ *
|
|
|
+ * ``my-dir/MyApplication.Entity.SomeObject.yml``
|
|
|
+ * ``my-dir/MyApplication.Entity.OtherObject.xml``
|
|
|
+ *
|
|
|
+ * If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like:
|
|
|
+ *
|
|
|
+ * ``my-dir/SomeObject.yml``
|
|
|
+ * ``my-dir/OtherObject.yml``
|
|
|
+ *
|
|
|
+ * Please keep in mind that you currently may only have one directory per namespace prefix.
|
|
|
+ *
|
|
|
+ * @param string $dir The directory where metadata files are located.
|
|
|
+ * @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory.
|
|
|
+ *
|
|
|
+ * @return SerializerBuilder
|
|
|
+ */
|
|
|
+ public function addMetadataDir($dir, $namespacePrefix = '')
|
|
|
+ {
|
|
|
+ if ( ! is_dir($dir)) {
|
|
|
+ throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isset($this->metadataDirs[$namespacePrefix])) {
|
|
|
+ throw new \InvalidArgumentException(sprintf('There is already a directory configured for the namespace prefix "%s". Please use replaceMetadataDir() to override directories.', $namespacePrefix));
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->metadataDirs[$namespacePrefix] = $dir;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adds a map of namespace prefixes to directories.
|
|
|
+ *
|
|
|
+ * @param array<string,string> $namespacePrefixToDirMap
|
|
|
+ *
|
|
|
+ * @return SerializerBuilder
|
|
|
+ */
|
|
|
+ public function addMetadataDirs(array $namespacePrefixToDirMap)
|
|
|
+ {
|
|
|
+ foreach ($namespacePrefixToDirMap as $prefix => $dir) {
|
|
|
+ $this->addMetadataDir($dir, $prefix);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Similar to addMetadataDir(), but overrides an existing entry.
|
|
|
+ *
|
|
|
+ * @param string $dir
|
|
|
+ * @param string $namespacePrefix
|
|
|
+ *
|
|
|
+ * @return SerializerBuilder
|
|
|
+ */
|
|
|
+ public function replaceMetadataDir($dir, $namespacePrefix = '')
|
|
|
+ {
|
|
|
+ if ( ! is_dir($dir)) {
|
|
|
+ throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $dir));
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( ! isset($this->metadataDirs[$namespacePrefix])) {
|
|
|
+ throw new \InvalidArgumentException(sprintf('There is no directory configured for namespace prefix "%s". Please use addMetadataDir() for adding new directories.', $namespacePrefix));
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->metadataDirs[$namespacePrefix] = $dir;
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function build()
|
|
|
+ {
|
|
|
+ $fileLocator = new FileLocator($this->metadataDirs);
|
|
|
+
|
|
|
+ $annotationReader = $this->annotationReader;
|
|
|
+ if (null === $annotationReader) {
|
|
|
+ $annotationReader = new AnnotationReader();
|
|
|
+
|
|
|
+ if (null !== $this->cacheDir) {
|
|
|
+ $this->createDir($this->cacheDir.'/annotations');
|
|
|
+ $annotationReader = new FileCacheReader($annotationReader, $this->cacheDir.'/annotations', $this->debug);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $metadataFactory = new MetadataFactory(new DriverChain(array(
|
|
|
+ new YamlDriver($fileLocator),
|
|
|
+ new XmlDriver($fileLocator),
|
|
|
+ new AnnotationDriver($annotationReader),
|
|
|
+ )), null, $this->debug);
|
|
|
+
|
|
|
+ if (null !== $this->cacheDir) {
|
|
|
+ $this->createDir($this->cacheDir.'/metadata');
|
|
|
+ $metadataFactory->setCache(new FileCache($this->cacheDir.'/metadata'));
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( ! $this->handlersConfigured) {
|
|
|
+ $this->addDefaultHandlers();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( ! $this->listenersConfigured) {
|
|
|
+ $this->addDefaultListeners();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( ! $this->visitorsAdded) {
|
|
|
+ $this->addDefaultSerializationVisitors();
|
|
|
+ $this->addDefaultDeserializationVisitors();
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Serializer(
|
|
|
+ $metadataFactory,
|
|
|
+ $this->handlerRegistry,
|
|
|
+ $this->objectConstructor ?: new UnserializeObjectConstructor(),
|
|
|
+ $this->serializationVisitors,
|
|
|
+ $this->deserializationVisitors,
|
|
|
+ $this->eventDispatcher
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private function initializePropertyNamingStrategy()
|
|
|
+ {
|
|
|
+ if (null !== $this->propertyNamingStrategy) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->propertyNamingStrategy = new CamelCaseNamingStrategy();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function createDir($dir)
|
|
|
+ {
|
|
|
+ if (is_dir($dir)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (false === @mkdir($dir, 0777, true)) {
|
|
|
+ throw new \RuntimeException(sprintf('Could not create directory "%s".', $dir));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|