├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cache ├── CacheClearer.php └── CacheWarmer.php ├── ContextFactory └── ConfiguredContextFactory.php ├── Debug ├── DataCollector.php ├── RunsListener.php ├── TraceableEventDispatcher.php ├── TraceableFileLocator.php ├── TraceableHandlerRegistry.php └── TraceableMetadataFactory.php ├── DependencyInjection ├── Compiler │ ├── AdjustDecorationPass.php │ ├── AssignVisitorsPass.php │ ├── CustomHandlersPass.php │ ├── DoctrinePass.php │ ├── ExpressionFunctionProviderPass.php │ ├── FormErrorHandlerTranslationDomainPass.php │ ├── PerInstancePass.php │ ├── RegisterEventListenersAndSubscribersPass.php │ └── TwigExtensionPass.php ├── Configuration.php ├── DIUtils.php ├── JMSSerializerExtension.php └── ScopedContainer.php ├── ExpressionLanguage └── BasicSerializerFunctionsProvider.php ├── JMSSerializerBundle.php ├── LICENSE ├── META.md ├── README.md ├── Resources ├── config │ ├── debug.xml │ └── services.xml ├── doc │ ├── configuration.rst │ ├── cookbook.rst │ ├── cookbook │ │ └── exclusion_strategies.rst │ ├── event_system.rst │ ├── handlers.rst │ ├── index.rst │ ├── installation.rst │ ├── reference.rst │ ├── reference │ │ ├── annotations.rst │ │ ├── xml_reference.rst │ │ └── yml_reference.rst │ └── usage.rst └── views │ ├── Collector │ ├── events.html.twig │ ├── handlers.html.twig │ ├── metadata.html.twig │ ├── panel.html.twig │ ├── script │ │ └── jms.js.twig │ └── style │ │ └── jms.css.twig │ └── icon.svg ├── Serializer └── StopwatchEventSubscriber.php ├── Templating └── SerializerHelper.php ├── UPGRADING.md └── composer.json /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | Thank you for contributing! 4 | 5 | Before we can merge your Pull-Request here are some guidelines that you need to follow. 6 | These guidelines exist not to annoy you, but to keep the code base clean, unified and future proof. 7 | 8 | ## Coding Standard 9 | 10 | This project uses [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) to enforce coding standards. 11 | The coding standard rules are defined in the **.phpcs.xml.dist** file (part of this repository). 12 | The project follows a relaxed version of the Doctrine Coding standards v4. 13 | 14 | Your Pull-Request must be compliant with the said standard. 15 | To check your code you can run `vendor/bin/phpcs`. This command will give you a list of violations in your code (if any). 16 | The most common errors can be automatically fixed just by running `vendor/bin/phpcbf`. 17 | 18 | ## Dependencies 19 | 20 | We're using [`composer/composer`](https://github.com/composer/composer) to manage dependencies 21 | 22 | ## Unit-Tests 23 | 24 | Please try to add a test for your pull-request. This project uses [PHPUnit](https://phpunit.de/) as testing framework. 25 | 26 | You can run the unit-tests by calling `vendor/bin/phpunit`. 27 | 28 | New features without tests can't be merged. 29 | 30 | ## CI 31 | 32 | We automatically run your pull request through [Travis CI](https://www.travis-ci.org). 33 | If you break the tests, we cannot merge your code, 34 | so please make sure that your code is working before opening up a Pull-Request. 35 | 36 | ## Issues and Bugs 37 | 38 | To create a new issue, you can use the GitHub issue tracking system. 39 | Please try to avoid opening support-related tickets. For support related questions please use more appropriate 40 | channels as Q&A platforms (such as Stackoverflow), Forums, Local PHP user groups. 41 | 42 | If you are a Symfony user, please try to distinguish between issues related to the Bundle and issues related to this 43 | library. 44 | 45 | ## Getting merged 46 | 47 | Please allow us time to review your pull requests. 48 | We will give our best to review everything as fast as possible, but cannot always live up to our own expectations. 49 | 50 | Please, write [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), 51 | and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) before submitting your Pull Request. 52 | 53 | One may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) 54 | too. This is used to "clean" your Pull Request before merging it (we don't want 55 | commits such as "fix tests", "fix 2", "fix 3", etc.). 56 | 57 | Pull requests without tests most probably will not be merged. 58 | Documentation PRs obviously do not require tests. 59 | 60 | Thank you very much again for your contribution! 61 | -------------------------------------------------------------------------------- /Cache/CacheClearer.php: -------------------------------------------------------------------------------- 1 | */ 12 | class CacheClearer implements CacheClearerInterface 13 | { 14 | /** @var CacheInterface|ClearableCacheInterface */ 15 | private $cache; 16 | 17 | /** @param CacheInterface|ClearableCacheInterface $cache */ 18 | public function __construct(CacheInterface $cache) 19 | { 20 | $this->cache = $cache; 21 | } 22 | 23 | public function clear($cacheDir): void 24 | { 25 | if ($this->cache instanceof ClearableCacheInterface) { 26 | $this->cache->clear(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Cache/CacheWarmer.php: -------------------------------------------------------------------------------- 1 | */ 12 | class CacheWarmer implements CacheWarmerInterface 13 | { 14 | /** @var MetadataFactoryInterface */ 15 | private $metadataFactory; 16 | /** @var string[] */ 17 | private $includePaths = []; 18 | /** @var string[] */ 19 | private $excludePaths = []; 20 | 21 | public function __construct(array $includePaths, MetadataFactoryInterface $metadataFactory, array $excludePaths = []) 22 | { 23 | $this->metadataFactory = $metadataFactory; 24 | $this->includePaths = $includePaths; 25 | $this->excludePaths = $excludePaths; 26 | } 27 | 28 | /** 29 | * @param string $cacheDir 30 | * 31 | * @return string[] A list of classes or files to preload on PHP 7.4+ 32 | */ 33 | public function warmUp(string $cacheDir, ?string $buildDir = null): array 34 | { 35 | $finder = Finder::create() 36 | ->ignoreVCS(true) 37 | ->ignoreDotFiles(true) 38 | ->ignoreUnreadableDirs(true) 39 | ->in($this->includePaths) 40 | ->exclude($this->excludePaths) 41 | ->name('*.php'); 42 | 43 | foreach ($finder as $file) { 44 | $classes = self::findClasses($file->getPathname()); 45 | foreach ($classes as $class) { 46 | $this->metadataFactory->getMetadataForClass($class); 47 | } 48 | } 49 | 50 | return []; 51 | } 52 | 53 | public function isOptional(): bool 54 | { 55 | return true; 56 | } 57 | 58 | /** 59 | * Extract the classes in the given file. 60 | * 61 | * This has been copied from https://github.com/composer/composer/blob/bfed974ae969635e622c4844e5e69526d8459baf/src/Composer/Autoload/ClassMapGenerator.php#L120-L214 62 | * 63 | * @param string $path The file to check 64 | * 65 | * @return array The found classes 66 | * 67 | * @throws \RuntimeException 68 | */ 69 | private static function findClasses($path) 70 | { 71 | // Use @ here instead of Silencer to actively suppress 'unhelpful' output 72 | // @link https://github.com/composer/composer/pull/4886 73 | $contents = @php_strip_whitespace($path); 74 | if (!$contents) { 75 | if (!file_exists($path)) { 76 | $message = 'File at "%s" does not exist, check your classmap definitions'; 77 | } elseif (!is_readable($path)) { 78 | $message = 'File at "%s" is not readable, check its permissions'; 79 | } elseif ('' === trim(file_get_contents($path))) { 80 | // The input file was really empty and thus contains no classes 81 | return []; 82 | } else { 83 | $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; 84 | } 85 | 86 | $error = error_get_last(); 87 | if (isset($error['message'])) { 88 | $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; 89 | } 90 | 91 | throw new \RuntimeException(sprintf($message, $path)); 92 | } 93 | 94 | // return early if there is no chance of matching anything in this file 95 | if (!preg_match('{\b(?:class|interface|trait|enum)\s}i', $contents)) { 96 | return []; 97 | } 98 | 99 | // strip heredocs/nowdocs 100 | $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); 101 | // strip strings 102 | $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); 103 | // strip leading non-php code if needed 104 | if ('.+<\?}s', '?>'); 115 | if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface|trait|enum) \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) 122 | | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] 123 | ) 124 | }ix', $contents, $matches); 125 | 126 | $classes = []; 127 | $namespace = ''; 128 | 129 | for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { 130 | if (!empty($matches['ns'][$i])) { 131 | $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; 132 | } else { 133 | $name = $matches['name'][$i]; 134 | // skip anon classes extending/implementing 135 | if ('extends' === $name || 'implements' === $name) { 136 | continue; 137 | } 138 | 139 | if (':' === $name[0]) { 140 | // This is an XHP class, https://github.com/facebook/xhp 141 | $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); 142 | } elseif ('enum' === $matches['type'][$i]) { 143 | // In Hack, something like: 144 | // enum Foo: int { HERP = '123'; } 145 | // The regex above captures the colon, which isn't part of 146 | // the class name. 147 | $name = rtrim($name, ':'); 148 | } 149 | 150 | $classes[] = ltrim($namespace . $name, '\\'); 151 | } 152 | } 153 | 154 | return $classes; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /ContextFactory/ConfiguredContextFactory.php: -------------------------------------------------------------------------------- 1 | version = $version; 54 | } 55 | 56 | /** @param bool $serializeNulls */ 57 | public function setSerializeNulls($serializeNulls) 58 | { 59 | $this->serializeNulls = (bool) $serializeNulls; 60 | } 61 | 62 | public function enableMaxDepthChecks() 63 | { 64 | $this->enableMaxDepthChecks = true; 65 | } 66 | 67 | /** @param array $attributes */ 68 | public function setAttributes(array $attributes) 69 | { 70 | $this->attributes = $attributes; 71 | } 72 | 73 | /** @param string[] $groups */ 74 | public function setGroups(array $groups) 75 | { 76 | $this->groups = $groups; 77 | } 78 | 79 | public function createDeserializationContext(): DeserializationContext 80 | { 81 | return $this->configureContext(new DeserializationContext()); 82 | } 83 | 84 | public function createSerializationContext(): SerializationContext 85 | { 86 | return $this->configureContext(new SerializationContext()); 87 | } 88 | 89 | /** 90 | * Configures context according to configuration 91 | * 92 | * @param Context $context The context 93 | * 94 | * @return Context Given object 95 | */ 96 | private function configureContext(Context $context) 97 | { 98 | foreach ($this->attributes as $key => $value) { 99 | $context->setAttribute($key, $value); 100 | } 101 | 102 | if (!empty($this->groups)) { 103 | $context->setGroups($this->groups); 104 | } 105 | 106 | if (($context instanceof SerializationContext) && null !== $this->serializeNulls) { 107 | $context->setSerializeNull($this->serializeNulls); 108 | } 109 | 110 | if (true === $this->enableMaxDepthChecks) { 111 | $context->enableMaxDepthChecks(); 112 | } 113 | 114 | if (null !== $this->version) { 115 | $context->setVersion($this->version); 116 | } 117 | 118 | return $context; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Debug/DataCollector.php: -------------------------------------------------------------------------------- 1 | instance = $instance; 35 | $this->eventDispatcher = $eventDispatcher; 36 | $this->handler = $handler; 37 | $this->metadataFactory = $metadataFactory; 38 | $this->locator = $locator; 39 | $this->loadedDirs = $loadedDirs; 40 | $this->runsListener = $runsListener; 41 | 42 | $this->reset(); 43 | } 44 | 45 | public function collect(Request $request, Response $response, ?\Throwable $exception = null): void 46 | { 47 | } 48 | 49 | public function reset(): void 50 | { 51 | $this->data['handlers'] = []; 52 | $this->data['metadata'] = []; 53 | $this->data['listeners'] = []; 54 | $this->data['metadata_files'] = []; 55 | $this->data['loaded_dirs'] = []; 56 | $this->data['runs'] = []; 57 | $this->data['instance'] = $this->instance; 58 | } 59 | 60 | public function getInstance(): string 61 | { 62 | return $this->data['instance']; 63 | } 64 | 65 | public function getName(): string 66 | { 67 | if (($this->instance ?? $this->data['instance']) === 'default'){ 68 | return 'jms_serializer'; 69 | } 70 | 71 | return 'jms_serializer_'. ($this->instance ?? $this->data['instance']); 72 | } 73 | 74 | public function getNumListeners($type): int 75 | { 76 | return array_sum(array_map(function ($l){ 77 | return count($l); 78 | }, $this->data['listeners'][$type])); 79 | } 80 | 81 | public function getNumHandlers($type): int 82 | { 83 | return array_sum(array_map(function ($l){ 84 | return count($l); 85 | }, $this->data['handlers'][$type])); 86 | } 87 | 88 | public function getTriggeredListeners(): array 89 | { 90 | return $this->data['listeners']['called']; 91 | } 92 | 93 | public function getRuns($direction): array 94 | { 95 | return $this->data['runs'][$direction] ?? []; 96 | } 97 | 98 | public function getLoadedDirs(): array 99 | { 100 | return $this->data['loaded_dirs']; 101 | } 102 | 103 | public function getNotTriggeredListeners(): array 104 | { 105 | return $this->data['listeners']['not_called']; 106 | } 107 | 108 | public function getTriggeredHandlers(): array 109 | { 110 | return $this->data['handlers']['called']; 111 | } 112 | 113 | public function getNotTriggeredHandlers(): array 114 | { 115 | return $this->data['handlers']['not_called']; 116 | } 117 | 118 | public function getLoadedMetadata(): array 119 | { 120 | return $this->data['metadata']; 121 | } 122 | 123 | public function getMetadataFiles(): array 124 | { 125 | return $this->data['metadata_files']; 126 | } 127 | 128 | public function getTriggeredEvents() 129 | { 130 | return $this->data['triggered_events']; 131 | } 132 | 133 | public function lateCollect(): void 134 | { 135 | $this->data['listeners'] = [ 136 | 'called' => $this->eventDispatcher->getTriggeredListeners(), 137 | 'not_called' => $this->eventDispatcher->getNotTriggeredListeners(), 138 | ]; 139 | 140 | 141 | $this->data['handlers'] = [ 142 | 'called' => $this->handler->getTriggeredHandlers(), 143 | 'not_called' => $this->handler->getNotTriggeredHandlers(), 144 | ]; 145 | 146 | $this->data['metadata'] = $this->metadataFactory->getLoadedMetadata(); 147 | $this->data['metadata_files'] = $this->locator->getAttemptedFiles(); 148 | $this->data['loaded_dirs'] = $this->loadedDirs; 149 | $this->data['runs'] = $this->runsListener->getRuns(); 150 | $this->data['triggered_events'] = $this->eventDispatcher->getTriggeredEvents(); 151 | ksort($this->data['loaded_dirs']); 152 | ksort($this->data['metadata_files']); 153 | ksort($this->data['metadata']); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Debug/RunsListener.php: -------------------------------------------------------------------------------- 1 | getContext(); 17 | if (!isset($this->runs[$context->getDirection()][spl_object_hash($context)])) { 18 | $this->runs[$context->getDirection()][spl_object_hash($context)] = [ 19 | 'type' => $event->getType() 20 | ]; 21 | } 22 | } 23 | 24 | public function getRuns(): array 25 | { 26 | return $this->runs; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Debug/TraceableEventDispatcher.php: -------------------------------------------------------------------------------- 1 | 0, 23 | 'duration' => 0 24 | ]; 25 | 26 | foreach ($this->storage as $calledOnTypes) { 27 | foreach ($calledOnTypes as $calls) { 28 | $data['count'] += count($calls); 29 | $data['duration'] += $this->calculateTotalDuration($calls); 30 | } 31 | } 32 | return $data; 33 | } 34 | 35 | public function getTriggeredListeners(): array 36 | { 37 | $resultsByListener = []; 38 | 39 | foreach ($this->storage as $eventName => $calledOnTypes) { 40 | foreach ($calledOnTypes as $type => $calls) { 41 | foreach ($calls as $call) { 42 | $listener = $this->findNameForListener($call['listener']); 43 | $resultsByListener[$eventName][$listener][$type][] = $call; 44 | } 45 | } 46 | } 47 | 48 | foreach ($resultsByListener as $eventName => $calledOnListeners) { 49 | foreach ($calledOnListeners as $listener => $calledOnTypes) { 50 | foreach ($calledOnTypes as $type => $calls) { 51 | $resultsByListener[$eventName][$listener][$type] = [ 52 | 'calls' => count($calls), 53 | 'duration' => $this->calculateTotalDuration($calls) 54 | ]; 55 | } 56 | } 57 | } 58 | 59 | return $resultsByListener; 60 | } 61 | 62 | private function findNameForListener($listener): string 63 | { 64 | if (is_array($listener)) { 65 | return (is_string($listener[0]) ? $listener[0] : get_class($listener[0])) . '::' . $listener[1]; 66 | } 67 | return 'unknown'; 68 | } 69 | 70 | public function getNotTriggeredListeners(): array 71 | { 72 | $result = []; 73 | 74 | foreach ($this->getListeners() as $event => $listeners) { 75 | foreach ($listeners as $listener) { 76 | foreach ($this->storage[$event] ?? [] as $calls) { 77 | foreach ($calls as $call) { 78 | if ($call['listener'] == $listener[0]) { 79 | continue 3; 80 | } 81 | } 82 | } 83 | $listenerName = $this->findNameForListener($listener[0]); 84 | $result[$event][$listenerName] = $listenerName; 85 | } 86 | } 87 | 88 | return $result; 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | protected function initializeListeners(string $eventName, string $loweredClass, string $format): array 95 | { 96 | $listeners = parent::initializeListeners($eventName, $loweredClass, $format); 97 | foreach ($listeners as &$listener) { 98 | $listener[0] = $f = function (...$args) use ($listener, &$f) { 99 | $t = microtime(true); 100 | call_user_func_array($listener[0], $args); 101 | 102 | // $args = [$event, $eventName, $class, $format, $dispatcher] 103 | // $listener = [$callable, $class, $format, $interface] 104 | $this->storage[$args[1]][$args[2]][] = [ 105 | 'listener' => $listener[0], 106 | 'format' => $args[3], 107 | 'type' => $args[0]->getType(), 108 | 'duration' => microtime(true) - $t 109 | ]; 110 | }; 111 | } 112 | 113 | return $listeners; 114 | } 115 | 116 | private function calculateTotalDuration(array $calls): float 117 | { 118 | return array_sum(array_column($calls, 'duration')) * 1000; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Debug/TraceableFileLocator.php: -------------------------------------------------------------------------------- 1 | decorated = $decorated; 22 | } 23 | 24 | public function getAttemptedFiles(): array 25 | { 26 | return $this->files; 27 | } 28 | 29 | public function findFileForClass(\ReflectionClass $class, string $extension): ?string 30 | { 31 | $path = $this->decorated->findFileForClass($class, $extension); 32 | 33 | if ($this->decorated instanceof TraceableFileLocatorInterface) { 34 | $this->files[$class->getName()] = array_merge($this->files[$class->getName()] ?? [], $this->decorated->getPossibleFilesForClass($class, $extension)); 35 | } elseif ($path !== null) { 36 | $this->files[$class->getName()][$path] = true; 37 | } 38 | 39 | return $path; 40 | } 41 | 42 | public function findAllClasses(string $extension): array 43 | { 44 | return $this->decorated->findAllClasses($extension); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Debug/TraceableHandlerRegistry.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 25 | } 26 | 27 | public function registerSubscribingHandler(SubscribingHandlerInterface $handler): void 28 | { 29 | $this->registry->registerSubscribingHandler($handler); 30 | } 31 | 32 | public function registerHandler(int $direction, string $typeName, string $format, $handler): void 33 | { 34 | $this->registry->registerHandler($direction, $typeName, $format, $handler); 35 | $name = $this->findNameForHandler($handler); 36 | $this->registeredHandlers[$direction][$typeName][$name] = $name; 37 | $a = &$this->registeredHandlers[$direction]; 38 | ksort($a); 39 | } 40 | 41 | public function getHandler(int $direction, string $typeName, string $format) 42 | { 43 | $handler = $this->registry->getHandler($direction, $typeName, $format); 44 | if ($handler === null) { 45 | return null; 46 | } 47 | return function (...$args) use ($handler, $direction, $typeName, $format) { 48 | try { 49 | $t = microtime(true); 50 | return call_user_func_array($handler, $args); 51 | } finally { 52 | $this->storage[$direction][$typeName][] = [ 53 | 'handler' => $handler, 54 | 'format' => $format, 55 | 'duration' => microtime(true) - $t 56 | ]; 57 | } 58 | }; 59 | } 60 | 61 | private function findNameForHandler($listener): string 62 | { 63 | if (is_array($listener)) { 64 | return (is_string($listener[0]) ? $listener[0] : get_class($listener[0])) . '::' . $listener[1]; 65 | } 66 | return 'unknown'; 67 | } 68 | 69 | public function getTriggeredHandlers(): array 70 | { 71 | $result = []; 72 | 73 | foreach ($this->storage as $direction => $handlersByType) { 74 | foreach ($handlersByType as $type => $calls) { 75 | foreach ($calls as $call) { 76 | $handlerName = $this->findNameForHandler($call['handler']); 77 | if (!isset($result[$direction][$type][$handlerName])) { 78 | $result[$direction][$type][$handlerName] = [ 79 | 'calls' => 0, 80 | 'duration' => 0, 81 | ]; 82 | } 83 | $result[$direction][$type][$handlerName] = [ 84 | 'handler' => $handlerName, 85 | 'calls' => $result[$direction][$type][$handlerName]['calls'] + 1, 86 | 'duration' => $result[$direction][$type][$handlerName]['duration'] + $call['duration'], 87 | ]; 88 | } 89 | } 90 | } 91 | 92 | return $result; 93 | } 94 | 95 | public function getNotTriggeredHandlers(): array 96 | { 97 | $registered = $this->registeredHandlers; 98 | 99 | foreach ($this->storage as $direction => $handlersByType) { 100 | foreach ($handlersByType as $type => $calls) { 101 | foreach ($calls as $call) { 102 | $handlerName = $this->findNameForHandler($call['handler']); 103 | unset($registered[$direction][$type][$handlerName]); 104 | } 105 | 106 | if (!count($registered[$direction][$type])) { 107 | unset($registered[$direction][$type]); 108 | } 109 | } 110 | } 111 | 112 | return $registered; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Debug/TraceableMetadataFactory.php: -------------------------------------------------------------------------------- 1 | metadataFactory = $metadataFactory; 25 | } 26 | 27 | public function getLoadedMetadata(): array 28 | { 29 | return $this->storage; 30 | } 31 | 32 | public function getAllClassNames(): array 33 | { 34 | return $this->metadataFactory->getAllClassNames(); 35 | } 36 | 37 | /** 38 | * @return ClassHierarchyMetadata|MergeableClassMetadata|null 39 | */ 40 | public function getMetadataForClass(string $className) 41 | { 42 | $metadata = $this->metadataFactory->getMetadataForClass($className); 43 | if ($metadata instanceof ClassMetadata) { 44 | $this->trackMetadata($metadata); 45 | } 46 | 47 | return $metadata; 48 | } 49 | 50 | protected function trackMetadata(ClassMetadata $metadata): void 51 | { 52 | $class = $metadata->name; 53 | $this->storage[$class] = array_merge( 54 | $this->storage[$class] ?? [], $metadata->fileResources 55 | ); 56 | $this->storage[$class] = array_unique($this->storage[$class]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/AdjustDecorationPass.php: -------------------------------------------------------------------------------- 1 | getDefinition('xxx') 16 | * ->setDecoratedService('jms_serializer.object_constructor') 17 | * 18 | * You do not need to worry to which serializer instance jms_serializer.object_constructor refers to. 19 | * 20 | * @internal 21 | */ 22 | final class AdjustDecorationPass implements CompilerPassInterface 23 | { 24 | public function process(ContainerBuilder $container): void 25 | { 26 | DIUtils::adjustDecorators($container); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/AssignVisitorsPass.php: -------------------------------------------------------------------------------- 1 | getDefinition('jms_serializer.serializer'); 16 | $serializers = []; 17 | foreach ($container->findTaggedServiceIds('jms_serializer.serialization_visitor') as $id => $multipleTags) { 18 | foreach ($multipleTags as $attributes) { 19 | $serializers[$attributes['format']] = new Reference($id); 20 | } 21 | } 22 | 23 | $def->replaceArgument(2, $serializers); 24 | 25 | $deserializers = []; 26 | foreach ($container->findTaggedServiceIds('jms_serializer.deserialization_visitor') as $id => $multipleTags) { 27 | foreach ($multipleTags as $attributes) { 28 | $deserializers[$attributes['format']] = new Reference($id); 29 | } 30 | } 31 | 32 | $def->replaceArgument(3, $deserializers); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/CustomHandlersPass.php: -------------------------------------------------------------------------------- 1 | findHandlers($container); 20 | $handlerRegistryDef = $container->findDefinition('jms_serializer.handler_registry'); 21 | 22 | $isLazyHandlerRegistry = is_a($handlerRegistryDef->getClass(), LazyHandlerRegistry::class, true); 23 | 24 | $handlerServices = []; 25 | $handlers = []; 26 | foreach ($handlersByDirection as $direction => $handlersByType) { 27 | foreach ($handlersByType as $type => $handlersByFormat) { 28 | foreach ($handlersByFormat as $format => $handlerCallable) { 29 | $id = (string) $handlerCallable[0]; 30 | 31 | $handlerServices[$id] = new ServiceClosureArgument($handlerCallable[0]); 32 | $handlerCallable[0] = $id; 33 | 34 | if (!$isLazyHandlerRegistry) { 35 | $handlerRegistryDef->addMethodCall('registerHandler', [$direction, $type, $format, $handlerCallable]); 36 | } else { 37 | $handlers[$direction][$type][$format] = $handlerCallable; 38 | } 39 | } 40 | } 41 | } 42 | 43 | if ($isLazyHandlerRegistry) { 44 | $handlerRegistryDef->addArgument($handlers); 45 | } 46 | 47 | $container->findDefinition('jms_serializer.handler_registry.service_locator') 48 | ->setArgument(0, $handlerServices); 49 | } 50 | 51 | private function findHandlers(ScopedContainer $container): array 52 | { 53 | $handlers = []; 54 | foreach ($container->findTaggedServiceIds('jms_serializer.handler') as $id => $tags) { 55 | foreach ($tags as $attrs) { 56 | if (!isset($attrs['type'], $attrs['format'])) { 57 | throw new \RuntimeException(sprintf('Each tag named "jms_serializer.handler" of service "%s" must have at least two attributes: "type" and "format".', $id)); 58 | } 59 | 60 | $directions = [GraphNavigatorInterface::DIRECTION_DESERIALIZATION, GraphNavigatorInterface::DIRECTION_SERIALIZATION]; 61 | if (isset($attrs['direction'])) { 62 | if (!defined($directionConstant = 'JMS\Serializer\GraphNavigatorInterface::DIRECTION_' . strtoupper($attrs['direction']))) { 63 | throw new \RuntimeException(sprintf('The direction "%s" of tag "jms_serializer.handler" of service "%s" does not exist.', $attrs['direction'], $id)); 64 | } 65 | 66 | $directions = [constant($directionConstant)]; 67 | } 68 | 69 | foreach ($directions as $direction) { 70 | $method = $attrs['method'] ?? HandlerRegistry::getDefaultMethod($direction, $attrs['type'], $attrs['format']); 71 | $priority = isset($attrs['priority']) ? intval($attrs['priority']) : 0; 72 | 73 | $handlers[] = [$direction, $attrs['type'], $attrs['format'], $priority, new Reference($id), $method]; 74 | } 75 | } 76 | } 77 | 78 | foreach ($container->findTaggedServiceIds('jms_serializer.subscribing_handler') as $id => $tags) { 79 | $def = $container->getDefinition($id); 80 | $class = $def->getClass(); 81 | 82 | $ref = new \ReflectionClass($class); 83 | if (!$ref->implementsInterface('JMS\Serializer\Handler\SubscribingHandlerInterface')) { 84 | throw new \RuntimeException(sprintf('The service "%s" must implement the SubscribingHandlerInterface.', $id)); 85 | } 86 | 87 | foreach (call_user_func([$class, 'getSubscribingMethods']) as $methodData) { 88 | if (!isset($methodData['format'], $methodData['type'])) { 89 | throw new \RuntimeException(sprintf('Each method returned from getSubscribingMethods of service "%s" must have a "type", and "format" attribute.', $id)); 90 | } 91 | 92 | $directions = [GraphNavigatorInterface::DIRECTION_DESERIALIZATION, GraphNavigatorInterface::DIRECTION_SERIALIZATION]; 93 | if (isset($methodData['direction'])) { 94 | $directions = [$methodData['direction']]; 95 | } 96 | 97 | foreach ($directions as $direction) { 98 | $priority = isset($methodData['priority']) ? intval($methodData['priority']) : 0; 99 | $method = $methodData['method'] ?? HandlerRegistry::getDefaultMethod($direction, $methodData['type'], $methodData['format']); 100 | 101 | $handlers[] = [$direction, $methodData['type'], $methodData['format'], $priority, new Reference($id), $method]; 102 | } 103 | } 104 | } 105 | 106 | return $this->sortAndFlattenHandlersList($handlers); 107 | } 108 | 109 | private function sortAndFlattenHandlersList(array $allHandlers) 110 | { 111 | $sorter = static function ($a, $b) { 112 | return $b[3] === $a[3] ? 0 : ($b[3] > $a[3] ? 1 : -1); 113 | }; 114 | self::stable_uasort($allHandlers, $sorter); 115 | $handlers = []; 116 | foreach ($allHandlers as $handler) { 117 | [$direction, $type, $format, $priority, $service, $method] = $handler; 118 | $handlers[$direction][$type][$format] = [$service, $method]; 119 | } 120 | 121 | return $handlers; 122 | } 123 | 124 | /** 125 | * Performs stable sorting. Copied from http://php.net/manual/en/function.uasort.php#121283 126 | * 127 | * @param array $array 128 | * @param callable $value_compare_func 129 | * 130 | * @return bool 131 | */ 132 | private static function stable_uasort(array &$array, callable $value_compare_func) 133 | { 134 | $index = 0; 135 | foreach ($array as &$item) { 136 | $item = [$index++, $item]; 137 | } 138 | 139 | $result = uasort($array, static function ($a, $b) use ($value_compare_func) { 140 | $result = call_user_func($value_compare_func, $a[1], $b[1]); 141 | 142 | return 0 === $result ? $a[0] - $b[0] : $result; 143 | }); 144 | foreach ($array as &$item) { 145 | $item = $item[1]; 146 | } 147 | 148 | return $result; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/DoctrinePass.php: -------------------------------------------------------------------------------- 1 | 'doctrine', 17 | 'doctrine_phpcr.odm.document_manager' => 'doctrine_phpcr', 18 | ]; 19 | 20 | foreach ($registries as $managerId => $registry) { 21 | if (!$container->has($managerId)) { 22 | $container->removeDefinition(sprintf('jms_serializer.metadata.%s_type_driver', $registry)); 23 | unset($registries[$managerId]); 24 | } 25 | } 26 | 27 | foreach ($registries as $registry) { 28 | if ($container->hasDefinition(sprintf('jms_serializer.metadata.%s_type_driver', $registry))) { 29 | $container->getDefinition(sprintf('jms_serializer.metadata.%s_type_driver', $registry)) 30 | ->setDecoratedService('jms_serializer.metadata_driver') 31 | ->replaceArgument(0, new Reference(sprintf('jms_serializer.metadata.%s_type_driver.inner', $registry))); 32 | } 33 | 34 | if ($container->hasDefinition(sprintf('jms_serializer.%s_object_constructor', $registry))) { 35 | $container->getDefinition(sprintf('jms_serializer.%s_object_constructor', $registry)) 36 | ->setDecoratedService('jms_serializer.object_constructor') 37 | ->replaceArgument(1, new Reference(sprintf('jms_serializer.%s_object_constructor.inner', $registry))); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ExpressionFunctionProviderPass.php: -------------------------------------------------------------------------------- 1 | findDefinition('jms_serializer.expression_language'); 18 | 19 | foreach (array_keys($container->findTaggedServiceIds('jms.expression.function_provider')) as $id) { 20 | $registryDefinition->addMethodCall('registerProvider', [new Reference($id)]); 21 | } 22 | } catch (ServiceNotFoundException $exception) { 23 | } 24 | 25 | if ($container->has('security.authorization_checker')) { 26 | $container->setAlias('jms_serializer.authorization_checker', 'security.authorization_checker') 27 | ->setPublic(true); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/FormErrorHandlerTranslationDomainPass.php: -------------------------------------------------------------------------------- 1 | hasParameter('validator.translation_domain')) { 15 | return; 16 | } 17 | 18 | $container->findDefinition('jms_serializer.form_error_handler') 19 | ->setArgument(1, '%validator.translation_domain%'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/PerInstancePass.php: -------------------------------------------------------------------------------- 1 | processInstance($scopedContainer); 18 | } 19 | } 20 | 21 | /** 22 | * @param ContainerBuilder $container 23 | * 24 | * @return ScopedContainer[] 25 | */ 26 | private static function getSerializers(ContainerBuilder $container): array 27 | { 28 | $serializers = []; 29 | 30 | foreach ($container->findTaggedServiceIds('jms_serializer.serializer') as $serializerId => $serializerAttributes) { 31 | foreach ($serializerAttributes as $serializerAttribute) { 32 | $serializers[$serializerId] = new ScopedContainer($container, $serializerAttribute['name']); 33 | } 34 | } 35 | 36 | return $serializers; 37 | } 38 | 39 | abstract protected function processInstance(ScopedContainer $container): void; 40 | } 41 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RegisterEventListenersAndSubscribersPass.php: -------------------------------------------------------------------------------- 1 | findListeners($container); 18 | 19 | $dispatcherDef = $container->findDefinition('jms_serializer.event_dispatcher'); 20 | $listenerServices = []; 21 | 22 | foreach ($listeners as &$events) { 23 | $events = array_merge(...$events); 24 | } 25 | 26 | foreach ($listeners as $event => $listenersPerEvent) { 27 | foreach ($listenersPerEvent as $singleListener) { 28 | $id = (string) $singleListener[0][0]; 29 | 30 | $listenerServices[$id] = new ServiceClosureArgument($singleListener[0][0]); 31 | $singleListener[0][0] = $id; 32 | 33 | $dispatcherDef->addMethodCall('addListener', array_merge([$event], $singleListener)); 34 | } 35 | } 36 | 37 | $container->findDefinition('jms_serializer.event_dispatcher.service_locator') 38 | ->setArgument(0, $listenerServices); 39 | } 40 | 41 | private function findListeners(ScopedContainer $container): array 42 | { 43 | $listeners = []; 44 | 45 | foreach ($container->findTaggedServiceIds('jms_serializer.event_listener') as $id => $tags) { 46 | foreach ($tags as $attributes) { 47 | if (!isset($attributes['event'])) { 48 | throw new \RuntimeException(sprintf('The tag "jms_serializer.event_listener" of service "%s" requires an attribute named "event".', $id)); 49 | } 50 | 51 | $class = isset($attributes['class']) 52 | ? $container->getParameterBag()->resolveValue($attributes['class']) 53 | : null; 54 | 55 | $format = $attributes['format'] ?? null; 56 | $method = $attributes['method'] ?? EventDispatcher::getDefaultMethodName($attributes['event']); 57 | $priority = isset($attributes['priority']) ? (int) $attributes['priority'] : 0; 58 | $interface = $attributes['interface'] ?? null; 59 | 60 | $listeners[$attributes['event']][$priority][] = [[new Reference($id), $method], $class, $format, $interface]; 61 | } 62 | } 63 | 64 | foreach ($container->findTaggedServiceIds('jms_serializer.event_subscriber') as $id => $tags) { 65 | $subscriberClass = $container->getDefinition($id)->getClass(); 66 | 67 | $subscriberClassReflectionObj = new \ReflectionClass($subscriberClass); 68 | 69 | if (!$subscriberClassReflectionObj->implementsInterface('JMS\Serializer\EventDispatcher\EventSubscriberInterface')) { 70 | throw new \RuntimeException(sprintf('The service "%s" (class: %s) does not implement the EventSubscriberInterface.', $id, $subscriberClass)); 71 | } 72 | 73 | foreach (call_user_func([$subscriberClass, 'getSubscribedEvents']) as $eventData) { 74 | if (!isset($eventData['event'])) { 75 | throw new \RuntimeException(sprintf('The service "%s" (class: %s) must return an event for each subscribed event.', $id, $subscriberClass)); 76 | } 77 | 78 | $class = $eventData['class'] ?? null; 79 | $format = $eventData['format'] ?? null; 80 | $method = $eventData['method'] ?? EventDispatcher::getDefaultMethodName($eventData['event']); 81 | $priority = isset($eventData['priority']) ? (int) $eventData['priority'] : 0; 82 | $interface = $eventData['interface'] ?? null; 83 | 84 | $listeners[$eventData['event']][$priority][] = [[new Reference($id), $method], $class, $format, $interface]; 85 | } 86 | } 87 | 88 | array_walk($listeners, static function (&$value, $key) { 89 | ksort($value); 90 | }); 91 | 92 | return $listeners; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/TwigExtensionPass.php: -------------------------------------------------------------------------------- 1 | getParameter('jms_serializer.twig_enabled') !== $container->getInstanceName()) { 15 | return; 16 | } 17 | 18 | // if there is no support for twig runtime extensions, remove the services 19 | if (!$container->hasDefinition('twig.runtime_loader')) { 20 | $container->removeDefinition('jms_serializer.twig_extension.runtime_serializer'); 21 | $container->removeDefinition('jms_serializer.twig_extension.serializer_runtime_helper'); 22 | } 23 | 24 | // if there is no twig, remove the standard extension 25 | // or twig is there with runtime extensions, thus the "legacy" extensions are not needed 26 | if (!$container->hasDefinition('twig') || $container->hasDefinition('twig.runtime_loader')) { 27 | $container->removeDefinition('jms_serializer.twig_extension.serializer'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | debug = $debug; 22 | } 23 | 24 | public function getConfigTreeBuilder(): TreeBuilder 25 | { 26 | $tb = new TreeBuilder('jms_serializer'); 27 | 28 | if (method_exists($tb, 'getRootNode')) { 29 | $root = $tb->getRootNode()->children(); 30 | } else { 31 | $root = $tb->root('jms_serializer')->children(); 32 | } 33 | 34 | $root->scalarNode('twig_enabled')->defaultValue('default')->end(); 35 | $this->addProfilerSection($root); 36 | $this->addConfigNodes($root); 37 | 38 | $instanceRoot = $root->arrayNode('instances') 39 | ->useAttributeAsKey('name') 40 | ->prototype('array') 41 | ->children(); 42 | 43 | $instanceRoot->booleanNode('inherit')->defaultFalse()->end(); 44 | 45 | $this->addConfigNodes($instanceRoot); 46 | 47 | return $tb; 48 | } 49 | 50 | private function addConfigNodes($root): void 51 | { 52 | $root->scalarNode('enum_support')->defaultValue(false)->end(); 53 | $root->scalarNode('default_value_property_reader_support')->defaultValue(false)->end(); 54 | $this->addHandlersSection($root); 55 | $this->addSubscribersSection($root); 56 | $this->addObjectConstructorsSection($root); 57 | $this->addSerializersSection($root); 58 | $this->addMetadataSection($root); 59 | $this->addVisitorsSection($root); 60 | $this->addContextSection($root); 61 | } 62 | 63 | private function addProfilerSection(NodeBuilder $builder): void 64 | { 65 | $builder->scalarNode('profiler') 66 | ->defaultValue($this->debug) 67 | ->validate() 68 | ->always(static function ($v): ?bool { 69 | if (!is_bool($v) && null !== $v) { 70 | throw new InvalidArgumentException('The profiler setting must be null or a boolean'); 71 | } 72 | 73 | return $v; 74 | }) 75 | ->end() 76 | ->end(); 77 | } 78 | 79 | private function addHandlersSection(NodeBuilder $builder) 80 | { 81 | $builder 82 | ->arrayNode('handlers') 83 | ->addDefaultsIfNotSet() 84 | ->children() 85 | ->arrayNode('datetime') 86 | ->addDefaultsIfNotSet() 87 | ->children() 88 | ->scalarNode('default_format')->defaultValue(\DateTime::RFC3339)->end() 89 | ->arrayNode('default_deserialization_formats') 90 | ->scalarPrototype()->end() 91 | ->defaultValue([]) 92 | ->end() 93 | ->scalarNode('default_timezone')->defaultValue(date_default_timezone_get())->end() 94 | ->scalarNode('cdata')->defaultTrue()->end() 95 | ->end() 96 | ->end() 97 | ->arrayNode('array_collection') 98 | ->addDefaultsIfNotSet() 99 | ->children() 100 | ->booleanNode('initialize_excluded')->defaultFalse()->end() 101 | ->end() 102 | ->end() 103 | ->arrayNode('symfony_uid') 104 | ->addDefaultsIfNotSet() 105 | ->children() 106 | ->scalarNode('default_format')->defaultValue('canonical')->end() // Same as JMS\Serializer\Handler\SymfonyUidHandler::FORMAT_CANONICAL 107 | ->scalarNode('cdata')->defaultTrue()->end() 108 | ->end() 109 | ->end() 110 | ->end() 111 | ->end() 112 | ->end(); 113 | } 114 | 115 | private function addSubscribersSection(NodeBuilder $builder) 116 | { 117 | $builder 118 | ->arrayNode('subscribers') 119 | ->addDefaultsIfNotSet() 120 | ->children() 121 | ->arrayNode('doctrine_proxy') 122 | ->addDefaultsIfNotSet() 123 | ->children() 124 | ->booleanNode('initialize_excluded')->defaultFalse()->end() 125 | ->booleanNode('initialize_virtual_types')->defaultFalse()->end() 126 | ->end() 127 | ->end() 128 | ->end() 129 | ->end(); 130 | } 131 | 132 | private function addObjectConstructorsSection(NodeBuilder $builder) 133 | { 134 | $builder 135 | ->arrayNode('object_constructors') 136 | ->addDefaultsIfNotSet() 137 | ->children() 138 | ->arrayNode('doctrine') 139 | ->canBeDisabled() 140 | ->addDefaultsIfNotSet() 141 | ->children() 142 | ->enumNode('fallback_strategy') 143 | ->defaultValue('null') 144 | ->values(['null', 'exception', 'fallback']) 145 | ->end() 146 | ->end() 147 | ->end() 148 | ->end() 149 | ->end(); 150 | } 151 | 152 | private function addSerializersSection(NodeBuilder $builder) 153 | { 154 | $builder 155 | ->arrayNode('property_naming') 156 | ->addDefaultsIfNotSet() 157 | ->beforeNormalization() 158 | ->ifString() 159 | ->then(static function ($id) { 160 | return ['id' => $id]; 161 | }) 162 | ->end() 163 | ->children() 164 | ->scalarNode('id')->cannotBeEmpty()->end() 165 | ->scalarNode('separator')->defaultValue('_')->end() 166 | ->booleanNode('lower_case')->defaultTrue()->end() 167 | ->end() 168 | ->end() 169 | ->arrayNode('expression_evaluator') 170 | ->addDefaultsIfNotSet() 171 | ->beforeNormalization() 172 | ->ifString() 173 | ->then(static function ($id) { 174 | return ['id' => $id]; 175 | }) 176 | ->end() 177 | ->children() 178 | ->scalarNode('id') 179 | ->defaultValue(static function () { 180 | if (interface_exists('Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface')) { 181 | return 'jms_serializer.expression_evaluator'; 182 | } 183 | 184 | return null; 185 | }) 186 | ->validate() 187 | ->always(static function ($v) { 188 | if (!empty($v) && !interface_exists('Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface')) { 189 | throw new InvalidArgumentException('You need at least symfony/expression-language v2.6 or v3.0 to use the expression evaluator features'); 190 | } 191 | 192 | return $v; 193 | }) 194 | ->end() 195 | ->end() 196 | ->end(); 197 | } 198 | 199 | private function addMetadataSection(NodeBuilder $builder) 200 | { 201 | $builder 202 | ->arrayNode('metadata') 203 | ->addDefaultsIfNotSet() 204 | ->fixXmlConfig('directory', 'directories') 205 | ->children() 206 | 207 | ->arrayNode('warmup') 208 | ->addDefaultsIfNotSet() 209 | ->children() 210 | ->arrayNode('paths') 211 | ->addDefaultsIfNotSet() 212 | ->children() 213 | ->arrayNode('included') 214 | ->prototype('scalar')->end() 215 | ->end() 216 | ->arrayNode('excluded') 217 | ->prototype('scalar')->end() 218 | ->end() 219 | ->end() 220 | ->end() 221 | ->end() 222 | ->end() 223 | 224 | ->scalarNode('cache')->defaultValue('file')->end() 225 | ->booleanNode('debug')->defaultValue($this->debug)->end() 226 | ->arrayNode('file_cache') 227 | ->addDefaultsIfNotSet() 228 | ->children() 229 | ->scalarNode('dir')->defaultValue(null)->end() 230 | ->end() 231 | ->end() 232 | ->booleanNode('include_interfaces')->defaultFalse()->end() 233 | ->booleanNode('auto_detection')->defaultTrue()->end() 234 | ->booleanNode('infer_types_from_doc_block')->defaultFalse()->end() 235 | ->booleanNode('infer_types_from_doctrine_metadata') 236 | ->info('Infers type information from Doctrine metadata if no explicit type has been defined for a property.') 237 | ->defaultTrue() 238 | ->end() 239 | ->arrayNode('directories') 240 | ->useAttributeAsKey('name') 241 | ->prototype('array') 242 | ->children() 243 | ->scalarNode('path')->isRequired()->end() 244 | ->scalarNode('namespace_prefix')->defaultValue('')->end() 245 | ->end() 246 | ->end() 247 | ->end() 248 | ->end() 249 | ->end(); 250 | } 251 | 252 | private function addVisitorsSection(NodeBuilder $builder) 253 | { 254 | $arrayNormalization = static function ($v) { 255 | $options = 0; 256 | foreach ($v as $option) { 257 | if (is_numeric($option)) { 258 | $options |= (int) $option; 259 | } elseif (defined($option)) { 260 | $options |= constant($option); 261 | } else { 262 | throw new InvalidArgumentException('Expected either an integer representing one of the JSON_ constants, or a string of the constant itself.'); 263 | } 264 | } 265 | 266 | return $options; 267 | }; 268 | $stringNormalization = static function ($v) { 269 | if (is_numeric($v)) { 270 | $value = (int) $v; 271 | } elseif (defined($v)) { 272 | $value = constant($v); 273 | } else { 274 | throw new InvalidArgumentException('Expected either an integer representing one of the JSON_ constants, or a string of the constant itself.'); 275 | } 276 | 277 | return $value; 278 | }; 279 | $arrayNormalizationXML = static function ($v) { 280 | $options = 0; 281 | foreach ($v as $option) { 282 | if (is_numeric($option)) { 283 | $options |= (int) $option; 284 | } elseif (defined($option)) { 285 | $options |= constant($option); 286 | } else { 287 | throw new InvalidArgumentException('Expected either an integer representing one of the LIBXML_ constants, or a string of the constant itself.'); 288 | } 289 | } 290 | 291 | return $options; 292 | }; 293 | $stringNormalizationXML = static function ($v) { 294 | if (is_numeric($v)) { 295 | $value = (int) $v; 296 | } elseif (defined($v)) { 297 | $value = constant($v); 298 | } else { 299 | throw new InvalidArgumentException('Expected either an integer representing one of the LIBXML_ constants, or a string of the constant itself.'); 300 | } 301 | 302 | return $value; 303 | }; 304 | 305 | $jsonValidation = static function ($v) { 306 | if (!is_int($v)) { 307 | throw new InvalidArgumentException('Expected either integer value or a array of the JSON_ constants.'); 308 | } 309 | 310 | return $v; 311 | }; 312 | $xmlValidation = static function ($v) { 313 | if (!is_int($v)) { 314 | throw new InvalidArgumentException('Expected either integer value or a array of the LIBXML_ constants.'); 315 | } 316 | 317 | return $v; 318 | }; 319 | 320 | $builder 321 | ->arrayNode('visitors') 322 | ->addDefaultsIfNotSet() 323 | ->children() 324 | ->arrayNode('json_serialization') 325 | ->addDefaultsIfNotSet() 326 | ->children() 327 | ->scalarNode('depth')->end() 328 | ->scalarNode('options') 329 | ->defaultValue(1024 /*JSON_PRESERVE_ZERO_FRACTION*/) 330 | ->beforeNormalization() 331 | ->ifArray()->then($arrayNormalization) 332 | ->end() 333 | ->beforeNormalization() 334 | ->ifString()->then($stringNormalization) 335 | ->end() 336 | ->validate() 337 | ->always($jsonValidation) 338 | ->end() 339 | ->end() 340 | ->end() 341 | ->end() 342 | ->arrayNode('json_deserialization') 343 | ->addDefaultsIfNotSet() 344 | ->children() 345 | ->scalarNode('options') 346 | ->defaultValue(0) 347 | ->beforeNormalization() 348 | ->ifArray()->then($arrayNormalization) 349 | ->end() 350 | ->beforeNormalization() 351 | ->ifString()->then($stringNormalization) 352 | ->end() 353 | ->validate() 354 | ->always($jsonValidation) 355 | ->end() 356 | ->end() 357 | ->booleanNode('strict') 358 | ->defaultValue(false) 359 | ->end() 360 | ->end() 361 | ->end() 362 | ->arrayNode('xml_serialization') 363 | ->fixXmlConfig('whitelisted-doctype', 'doctype_whitelist') 364 | ->addDefaultsIfNotSet() 365 | ->children() 366 | ->scalarNode('version') 367 | ->end() 368 | ->scalarNode('encoding') 369 | ->end() 370 | ->booleanNode('format_output') 371 | ->defaultFalse() 372 | ->end() 373 | ->scalarNode('default_root_name') 374 | ->end() 375 | ->scalarNode('default_root_ns') 376 | ->defaultValue('') 377 | ->end() 378 | ->end() 379 | ->end() 380 | ->arrayNode('xml_deserialization') 381 | ->fixXmlConfig('whitelisted-doctype', 'doctype_whitelist') 382 | ->addDefaultsIfNotSet() 383 | ->children() 384 | ->arrayNode('doctype_whitelist') 385 | ->prototype('scalar')->end() 386 | ->end() 387 | ->booleanNode('external_entities') 388 | ->defaultFalse() 389 | ->end() 390 | ->scalarNode('options') 391 | ->defaultValue(0) 392 | ->beforeNormalization() 393 | ->ifArray()->then($arrayNormalizationXML) 394 | ->end() 395 | ->beforeNormalization() 396 | ->ifString()->then($stringNormalizationXML) 397 | ->end() 398 | ->validate() 399 | ->always($xmlValidation) 400 | ->end() 401 | ->end() 402 | ->end() 403 | ->end() 404 | ->end() 405 | ->end(); 406 | } 407 | 408 | private function addContextSection(NodeBuilder $builder) 409 | { 410 | $root = $builder 411 | ->arrayNode('default_context') 412 | ->addDefaultsIfNotSet(); 413 | 414 | $this->createContextNode($root->children(), 'serialization'); 415 | $this->createContextNode($root->children(), 'deserialization'); 416 | } 417 | 418 | private function createContextNode(NodeBuilder $builder, $name) 419 | { 420 | $builder 421 | ->arrayNode($name) 422 | ->addDefaultsIfNotSet() 423 | ->beforeNormalization() 424 | ->ifString() 425 | ->then(static function ($id) { 426 | return ['id' => $id]; 427 | }) 428 | ->end() 429 | ->validate()->always(static function ($v) { 430 | if (!empty($v['id'])) { 431 | return ['id' => $v['id']]; 432 | } 433 | 434 | return $v; 435 | })->end() 436 | ->children() 437 | ->scalarNode('id')->cannotBeEmpty()->end() 438 | ->scalarNode('serialize_null') 439 | ->validate()->always(static function ($v) { 440 | if (!in_array($v, [true, false, null], true)) { 441 | throw new InvalidTypeException('Expected boolean or NULL for the serialize_null option'); 442 | } 443 | 444 | return $v; 445 | }) 446 | ->ifNull()->thenUnset() 447 | ->end() 448 | ->info('Flag if null values should be serialized') 449 | ->end() 450 | ->scalarNode('enable_max_depth_checks') 451 | ->info('Flag to enable the max-depth exclusion strategy') 452 | ->end() 453 | ->arrayNode('attributes') 454 | ->fixXmlConfig('attribute') 455 | ->useAttributeAsKey('key') 456 | ->prototype('scalar')->end() 457 | ->info('Arbitrary key-value data for context') 458 | ->end() 459 | ->arrayNode('groups') 460 | ->fixXmlConfig('group') 461 | ->prototype('scalar')->end() 462 | ->info('Default serialization groups') 463 | ->end() 464 | ->scalarNode('version') 465 | ->validate()->ifNull()->thenUnset()->end() 466 | ->info('Application version to use in exclusion strategies') 467 | ->end() 468 | ->end() 469 | ->end(); 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /DependencyInjection/DIUtils.php: -------------------------------------------------------------------------------- 1 | getDefinitions() as $definition) { 19 | $tagData = $definition->getTag('jms_serializer.instance'); 20 | if (empty($tagData) || 'default' === $tagData[0]['name']) { 21 | continue; 22 | } 23 | 24 | $decorationServiceDefinition = $definition->getDecoratedService(); 25 | if ($decorationServiceDefinition) { 26 | // if we are referring to a jms service, but that service is not per-instance 27 | if ( 28 | false !== strpos($decorationServiceDefinition[0], 'jms_serializer.') 29 | && 30 | false === strpos($decorationServiceDefinition[0], 'jms_serializer.instance.') 31 | ) { 32 | if ($container->hasDefinition($decorationServiceDefinition[0])) { 33 | $decorationDefinition = $container->getDefinition($decorationServiceDefinition[0]); 34 | if ($decorationDefinition->hasTag('jms_serializer.instance_global')) { 35 | throw new \LogicException('It is not possible to decorate global JMS services'); 36 | } 37 | } 38 | 39 | $decorationServiceDefinition[0] = self::getRealId($tagData[0]['name'], $decorationServiceDefinition[0]); 40 | call_user_func_array([$definition, 'setDecoratedService'], $decorationServiceDefinition); 41 | } 42 | } 43 | } 44 | } 45 | 46 | public static function cloneDefinitions(ContainerBuilder $container, array $instances) 47 | { 48 | $definitions = $container->getDefinitions(); 49 | $aliases = $container->getAliases(); 50 | 51 | foreach ($instances as $instance) { 52 | foreach ($definitions as $id => $definition) { 53 | self::cloneDefinition($instance, $id, $definition, $container); 54 | } 55 | 56 | foreach ($aliases as $alias => $aliasDef) { 57 | if (!self::shouldTranslateId($instance, (string) $alias)) { 58 | continue; 59 | } 60 | 61 | if (!self::shouldTranslateId($instance, (string) $aliasDef)) { 62 | $newAliasDef = $aliasDef; 63 | } else { 64 | $newAliasDef = new Alias(self::translateId($instance, (string) $aliasDef), $aliasDef->isPublic()); 65 | } 66 | 67 | $container->setAlias(self::translateId($instance, $alias), $newAliasDef); 68 | } 69 | } 70 | } 71 | 72 | private static function handleRef(Reference $argument, string $instance): Reference 73 | { 74 | if (!self::shouldTranslateId($instance, (string) $argument)) { 75 | return $argument; 76 | } 77 | 78 | $target = self::getRealId($instance, (string) $argument); 79 | 80 | return new Reference($target, $argument->getInvalidBehavior()); 81 | } 82 | 83 | public static function getRealId(string $instance, string $id): string 84 | { 85 | if (!self::shouldTranslateId($instance, $id)) { 86 | return $id; 87 | } 88 | 89 | return self::translateId($instance, $id); 90 | } 91 | 92 | private static function translateId(string $instance, string $id): string 93 | { 94 | return sprintf('jms_serializer.instance.%s.%s', $instance, substr($id, 15)); 95 | } 96 | 97 | private static function shouldTranslateId(string $instance, string $id): bool 98 | { 99 | return !( 100 | 0 === strpos($id, 'jms_serializer.instance.') || 101 | 0 !== strpos($id, 'jms_serializer.') || 102 | 'default' === $instance 103 | ); 104 | } 105 | 106 | private static function cloneDefinition(string $instance, string $id, Definition $parentDef, ContainerBuilder $container) 107 | { 108 | if (0 !== strpos($id, 'jms_serializer.') || 0 === strpos($id, 'jms_serializer.instance.')) { 109 | return; 110 | } 111 | 112 | $name = self::translateId($instance, $id); 113 | 114 | // add jms_serializer.instance.%s.%s for any jms service 115 | $container->setAlias($name, new Alias((string) $id, false)); 116 | 117 | if ($parentDef->hasTag('jms_serializer.instance_global')) { 118 | return; 119 | } 120 | 121 | if ($parentDef->hasTag('jms_serializer.instance') && $parentDef->getTag('jms_serializer.instance')[0]['name'] === $instance) { 122 | return; 123 | } 124 | 125 | if ('default' === $instance) { 126 | if (!$parentDef->hasTag('jms_serializer.instance')) { 127 | $parentDef->addTag('jms_serializer.instance', ['name' => $instance]); 128 | } 129 | 130 | return; 131 | } 132 | 133 | $newDef = new Definition($parentDef->getClass()); 134 | $container->setDefinition($name, $newDef); 135 | 136 | $decoration = $parentDef->getDecoratedService(); 137 | if ($decoration) { 138 | $decoration[0] = self::getRealId($instance, $decoration[0]); 139 | 140 | call_user_func_array([$newDef, 'setDecoratedService'], $decoration); 141 | } 142 | 143 | $tags = $parentDef->getTags(); 144 | unset($tags['jms_serializer.instance']); 145 | 146 | // we have one data collector for each serializer instance 147 | if (!empty($tags['data_collector'])) { 148 | $tags['data_collector'][0]['id'] = 'jms_serializer_' . $instance; 149 | } 150 | 151 | $newDef->setTags($tags); 152 | $newDef->addTag('jms_serializer.instance', ['name' => $instance]); 153 | 154 | $newDef->setArguments(self::handleArgs($parentDef->getArguments(), $container, $instance)); 155 | 156 | $calls = []; 157 | foreach ($parentDef->getMethodCalls() as $call) { 158 | $calls[] = [ 159 | $call[0], 160 | self::handleArgs($call[1], $container, $instance), 161 | ]; 162 | } 163 | 164 | $newDef->setMethodCalls($calls); 165 | } 166 | 167 | private static function handleArgs(array $args, ContainerBuilder $container, string $instance): array 168 | { 169 | foreach ($args as $n => $arg) { 170 | if (is_array($arg)) { 171 | $args[$n] = self::handleArgs($arg, $container, $instance); 172 | } elseif ($arg instanceof Reference) { 173 | $args[$n] = self::handleRef($arg, $instance); 174 | } 175 | } 176 | 177 | return $args; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /DependencyInjection/JMSSerializerExtension.php: -------------------------------------------------------------------------------- 1 | processNestedConfigs($rawConfigs, $container); 37 | 38 | $loader = new XmlFileLoader($container, new FileLocator([__DIR__ . '/../Resources/config/'])); 39 | $loader->load('services.xml'); 40 | 41 | if ($configs['profiler']) { 42 | $loader->load('debug.xml'); 43 | } 44 | 45 | $this->configureMetadataDrivers($container); 46 | 47 | DIUtils::cloneDefinitions($container, array_keys($configs['instances'])); 48 | 49 | // twig can be enabled only on one instance at the time 50 | $container->setParameter('jms_serializer.twig_enabled', $configs['twig_enabled']); 51 | 52 | foreach ($configs['instances'] as $name => $instanceConfigs) { 53 | $scopedContainer = new ScopedContainer($container, $name); 54 | $scopedContainer->getDefinition('jms_serializer.serializer') 55 | ->addTag('jms_serializer.serializer', ['name' => $name]); 56 | 57 | $this->loadInternal($instanceConfigs, $scopedContainer, $configs); 58 | } 59 | 60 | $container 61 | ->registerForAutoconfiguration(EventSubscriberInterface::class) 62 | ->addTag('jms_serializer.event_subscriber'); 63 | 64 | $container 65 | ->registerForAutoconfiguration(SubscribingHandlerInterface::class) 66 | ->addTag('jms_serializer.subscribing_handler'); 67 | } 68 | 69 | private function configureMetadataDrivers(ContainerBuilder $container): void 70 | { 71 | // The old annotation driver service is now always removed from the container in favor of the combined annotation/attribute driver 72 | $container->removeDefinition('jms_serializer.metadata.annotation_driver'); 73 | 74 | /* 75 | * Build the sorted list of metadata drivers based on the environment. The final order should be: 76 | * 77 | * - YAML Driver 78 | * - XML Driver 79 | * - Annotations/Attributes Driver 80 | * - Null (Fallback) Driver 81 | */ 82 | $metadataDrivers = []; 83 | 84 | if (class_exists(Yaml::class)) { 85 | $metadataDrivers[] = new Reference('jms_serializer.metadata.yaml_driver'); 86 | } else { 87 | $container->removeDefinition('jms_serializer.metadata.yaml_driver'); 88 | } 89 | 90 | // The XML driver is always available 91 | $metadataDrivers[] = new Reference('jms_serializer.metadata.xml_driver'); 92 | 93 | // The combined annotation/attribute driver is available if `doctrine/annotations` is installed or when running PHP 8 94 | if (interface_exists(Reader::class) || PHP_VERSION_ID >= 80000) { 95 | $metadataDrivers[] = new Reference('jms_serializer.metadata.annotation_or_attribute_driver'); 96 | } else { 97 | $container->removeDefinition('jms_serializer.metadata.annotation_or_attribute_driver'); 98 | } 99 | 100 | // The null driver is always available 101 | $metadataDrivers[] = new Reference('jms_serializer.metadata.null_driver'); 102 | 103 | $container 104 | ->getDefinition('jms_serializer.metadata_driver') 105 | ->replaceArgument(0, $metadataDrivers); 106 | } 107 | 108 | /** 109 | * @param array $rawConfigs 110 | * @param ContainerBuilder $container 111 | * 112 | * @return array 113 | */ 114 | private function loadConfigArray(array $rawConfigs, ContainerBuilder $container): array 115 | { 116 | $configs = $this->processConfiguration($this->getConfiguration($rawConfigs, $container), $rawConfigs); 117 | $defConf = $configs; 118 | unset($defConf['instances']); 119 | $configs['instances'] = array_merge(['default' => $defConf], $configs['instances']); 120 | 121 | return $configs; 122 | } 123 | 124 | private function loadInternal(array $config, ScopedContainer $container, array $mainConfig): void 125 | { 126 | // Built-in handlers. 127 | $container->getDefinition('jms_serializer.datetime_handler') 128 | ->replaceArgument(0, $config['handlers']['datetime']['default_format']) 129 | ->replaceArgument(1, $config['handlers']['datetime']['default_timezone']) 130 | ->replaceArgument(2, $config['handlers']['datetime']['cdata']) 131 | ->replaceArgument(3, [] === $config['handlers']['datetime']['default_deserialization_formats'] ? [$config['handlers']['datetime']['default_format']] : $config['handlers']['datetime']['default_deserialization_formats']); 132 | 133 | $container->getDefinition('jms_serializer.array_collection_handler') 134 | ->replaceArgument(0, $config['handlers']['array_collection']['initialize_excluded']); 135 | 136 | if (class_exists(SymfonyUidHandler::class) && class_exists(AbstractUid::class)) { 137 | $container->getDefinition('jms_serializer.symfony_uid_handler') 138 | ->replaceArgument(0, $config['handlers']['symfony_uid']['default_format']) 139 | ->replaceArgument(1, $config['handlers']['symfony_uid']['cdata']); 140 | } else { 141 | $container->removeDefinition('jms_serializer.symfony_uid_handler'); 142 | } 143 | 144 | // Built-in subscribers. 145 | $container->getDefinition('jms_serializer.doctrine_proxy_subscriber') 146 | ->replaceArgument(0, !$config['subscribers']['doctrine_proxy']['initialize_virtual_types']) 147 | ->replaceArgument(1, $config['subscribers']['doctrine_proxy']['initialize_excluded']); 148 | 149 | // Built-in object constructor. 150 | $container->getDefinition('jms_serializer.doctrine_object_constructor') 151 | ->replaceArgument(2, $config['object_constructors']['doctrine']['fallback_strategy']); 152 | 153 | // property naming 154 | $container->getDefinition('jms_serializer.camel_case_naming_strategy') 155 | ->replaceArgument(0, $config['property_naming']['separator']) 156 | ->replaceArgument(1, $config['property_naming']['lower_case']); 157 | 158 | if (!empty($config['property_naming']['id'])) { 159 | $container->setAlias('jms_serializer.naming_strategy', $config['property_naming']['id']); 160 | } 161 | 162 | if (!class_exists(Helper::class)) { 163 | $container->removeDefinition('jms_serializer.templating.helper.serializer'); 164 | } 165 | 166 | $bundles = $container->getParameter('kernel.bundles'); 167 | 168 | // remove twig services if the bundle is not loaded or if we are configuring an instance fof which twig is not enabled 169 | // only one instance can have twig enabled 170 | if (!isset($bundles['TwigBundle']) || $mainConfig['twig_enabled'] !== $container->getInstanceName()) { 171 | $container->removeDefinition('jms_serializer.twig_extension.serializer'); 172 | $container->removeDefinition('jms_serializer.twig_extension.runtime_serializer'); 173 | $container->removeDefinition('jms_serializer.twig_extension.serializer_runtime_helper'); 174 | } 175 | 176 | if (!empty($config['expression_evaluator']['id'])) { 177 | $evaluator = new Reference($config['expression_evaluator']['id']); 178 | 179 | $container 180 | ->getDefinition('jms_serializer.deserialization_graph_navigator_factory') 181 | ->replaceArgument(5, $evaluator); 182 | 183 | $container 184 | ->getDefinition('jms_serializer.serialization_graph_navigator_factory') 185 | ->replaceArgument(4, $evaluator); 186 | 187 | $container 188 | ->getDefinition('jms_serializer.accessor_strategy.default') 189 | ->replaceArgument(0, $evaluator); 190 | 191 | if (is_a($container->findDefinition($config['expression_evaluator']['id'])->getClass(), CompilableExpressionEvaluatorInterface::class, true)) { 192 | try { 193 | $container 194 | ->getDefinition('jms_serializer.metadata.yaml_driver') 195 | ->replaceArgument(3, $evaluator); 196 | } catch (ServiceNotFoundException $exception) { 197 | // Removed by conditional checks earlier 198 | } 199 | 200 | $container 201 | ->getDefinition('jms_serializer.metadata.xml_driver') 202 | ->replaceArgument(3, $evaluator); 203 | 204 | try { 205 | $container 206 | ->getDefinition('jms_serializer.metadata.annotation_or_attribute_driver') 207 | ->replaceArgument(2, $evaluator); 208 | } catch (ServiceNotFoundException $exception) { 209 | // Removed by conditional checks earlier 210 | } 211 | } 212 | } else { 213 | $container->removeDefinition('jms_serializer.expression_evaluator'); 214 | } 215 | 216 | // metadata 217 | if ('none' === $config['metadata']['cache']) { 218 | $container->removeAlias('jms_serializer.metadata.cache'); 219 | $container->removeDefinition('jms_serializer.cache.cache_clearer'); 220 | } elseif ('file' === $config['metadata']['cache']) { 221 | $instance = $container->getInstanceName(); 222 | 223 | // make sure that the cache dir is different for each instance 224 | $dirParam = $config['metadata']['file_cache']['dir'] ?: '%kernel.cache_dir%/jms_serializer' . ($instance ? '_' . $instance : ''); 225 | 226 | $container->getDefinition('jms_serializer.metadata.cache.file_cache') 227 | ->replaceArgument(0, $dirParam); 228 | 229 | $dir = $container->getParameterBag()->resolveValue($dirParam); 230 | if (!is_dir($dir) && !@mkdir($dir, 0777, true) && !is_dir($dir)) { 231 | throw new RuntimeException(sprintf('Could not create cache directory "%s".', $dir)); 232 | } 233 | } else { 234 | $container->setAlias('jms_serializer.metadata.cache', new Alias($config['metadata']['cache'], false)); 235 | } 236 | 237 | if (false === $config['metadata']['infer_types_from_doctrine_metadata']) { 238 | $container->removeDefinition('jms_serializer.metadata.doctrine_type_driver'); 239 | $container->removeDefinition('jms_serializer.metadata.doctrine_doctrine_phpcr_type_driver'); 240 | } 241 | 242 | if (false === $config['object_constructors']['doctrine']['enabled']) { 243 | $container->removeDefinition('jms_serializer.doctrine_object_constructor'); 244 | $container->removeDefinition('jms_serializer.doctrine_doctrine_phpcr__object_constructor'); 245 | } 246 | 247 | if ($config['metadata']['infer_types_from_doc_block'] && class_exists(DocBlockDriver::class)) { 248 | $container->getDefinition('jms_serializer.metadata.doc_block_driver') 249 | ->setDecoratedService('jms_serializer.metadata_driver') 250 | ->setPublic(false); 251 | } else { 252 | $container->removeDefinition('jms_serializer.metadata.doc_block_driver'); 253 | } 254 | 255 | // enable the typed props reader 256 | $container->getDefinition('jms_serializer.metadata.typed_properties_driver') 257 | ->setDecoratedService('jms_serializer.metadata_driver') 258 | ->setPublic(false); 259 | 260 | if ($config['enum_support']) { 261 | $container->getDefinition('jms_serializer.metadata.enum_driver') 262 | ->setDecoratedService('jms_serializer.metadata_driver', null, 50) 263 | ->setPublic(false); 264 | } else { 265 | $container->removeDefinition('jms_serializer.metadata.enum_driver'); 266 | $container->removeDefinition('jms_serializer.enum_handler'); 267 | $container->removeDefinition('jms_serializer.enum_subscriber'); 268 | } 269 | 270 | // enable the default value property reader on php 8.0+ 271 | if (PHP_VERSION_ID >= 80000 && $config['default_value_property_reader_support']) { 272 | $container->getDefinition('jms_serializer.metadata.default_value_property_driver') 273 | ->setDecoratedService('jms_serializer.metadata_driver') 274 | ->setPublic(false); 275 | } else { 276 | $container->removeDefinition('jms_serializer.metadata.default_value_property_driver'); 277 | } 278 | 279 | $container 280 | ->getDefinition('jms_serializer.metadata_factory') 281 | ->replaceArgument(2, $config['metadata']['debug']) 282 | ->addMethodCall('setIncludeInterfaces', [$config['metadata']['include_interfaces']]); 283 | 284 | // warmup 285 | if (!empty($config['metadata']['warmup']['paths']['included']) && class_exists(Finder::class)) { 286 | $container 287 | ->getDefinition('jms_serializer.cache.cache_warmer') 288 | ->replaceArgument(0, $config['metadata']['warmup']['paths']['included']) 289 | ->replaceArgument(2, $config['metadata']['warmup']['paths']['excluded']); 290 | } else { 291 | $container->removeDefinition('jms_serializer.cache.cache_warmer'); 292 | } 293 | 294 | $directories = $this->detectMetadataDirectories($config['metadata'], $container->getParameter('kernel.bundles_metadata')); 295 | 296 | $container 297 | ->getDefinition('jms_serializer.metadata.file_locator') 298 | ->replaceArgument(0, $directories); 299 | 300 | // the profiler setting is global 301 | if ($mainConfig['profiler']) { 302 | $container 303 | ->getDefinition('jms_serializer.data_collector') 304 | ->replaceArgument(0, $container->getInstanceName()) 305 | ->replaceArgument(1, $directories); 306 | } else { 307 | // remove profiler DI defintions if the profiler is not enabled 308 | array_map([$container, 'removeDefinition'], array_keys($container->findTaggedServiceIds('jms_serializer.profiler'))); 309 | } 310 | 311 | $this->setVisitorOptions($config, $container); 312 | 313 | if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) { 314 | $container->getDefinition('jms_serializer.stopwatch_subscriber') 315 | ->replaceArgument(1, sprintf('jms_serializer.%s', $container->getInstanceName())); 316 | } else { 317 | $container->removeDefinition('jms_serializer.stopwatch_subscriber'); 318 | } 319 | 320 | $this->setContextFactories($container, $config); 321 | } 322 | 323 | /** @return ConfigurationInterface */ 324 | public function getConfiguration(array $config, ContainerBuilder $container) 325 | { 326 | return new Configuration($container->getParameterBag()->resolveValue('%kernel.debug%')); 327 | } 328 | 329 | private function processNestedConfigs(array $rawConfigs, ContainerBuilder $container): array 330 | { 331 | $configs = $this->loadConfigArray($rawConfigs, $container); 332 | 333 | $needReConfig = false; 334 | foreach ($configs['instances'] as $name => $value) { 335 | // if we inherit from the default configs, we merge/append the default confs into the current instance 336 | if (!empty($value['inherit'])) { 337 | // the twig setting is not per instance, so we need to remove it before merging the configs 338 | unset($configs['instances']['default']['twig_enabled']); 339 | unset($configs['instances']['default']['profiler']); 340 | array_unshift($rawConfigs, [ 341 | 'instances' => [$name => $configs['instances']['default']], 342 | ]); 343 | $needReConfig = true; 344 | } 345 | } 346 | 347 | // if we had to merge at least one config, we need to re-merge them 348 | if ($needReConfig) { 349 | $configs = $this->loadConfigArray($rawConfigs, $container); 350 | } 351 | 352 | unset($value); 353 | 354 | return $configs; 355 | } 356 | 357 | private function setContextFactories(ScopedContainer $container, array $config): void 358 | { 359 | // context factories 360 | $services = [ 361 | 'serialization' => 'jms_serializer.configured_serialization_context_factory', 362 | 'deserialization' => 'jms_serializer.configured_deserialization_context_factory', 363 | ]; 364 | foreach ($services as $configKey => $serviceId) { 365 | $contextFactory = $container->getDefinition($serviceId); 366 | 367 | if (isset($config['default_context'][$configKey]['id'])) { 368 | $container->setAlias('jms_serializer.' . $configKey . '_context_factory', new Alias($config['default_context'][$configKey]['id'], true)); 369 | $container->setAlias('JMS\\Serializer\\ContextFactory\\' . ucfirst($configKey) . 'ContextFactoryInterface', new Alias($config['default_context'][$configKey]['id'], true)); 370 | $container->removeDefinition($serviceId); 371 | continue; 372 | } 373 | 374 | if (isset($config['default_context'][$configKey]['version'])) { 375 | $contextFactory->addMethodCall('setVersion', [$config['default_context'][$configKey]['version']]); 376 | } 377 | 378 | if (isset($config['default_context'][$configKey]['serialize_null'])) { 379 | $contextFactory->addMethodCall('setSerializeNulls', [$config['default_context'][$configKey]['serialize_null']]); 380 | } 381 | 382 | if (!empty($config['default_context'][$configKey]['attributes'])) { 383 | $contextFactory->addMethodCall('setAttributes', [$config['default_context'][$configKey]['attributes']]); 384 | } 385 | 386 | if (!empty($config['default_context'][$configKey]['groups'])) { 387 | $contextFactory->addMethodCall('setGroups', [$config['default_context'][$configKey]['groups']]); 388 | } 389 | 390 | if (!empty($config['default_context'][$configKey]['enable_max_depth_checks'])) { 391 | $contextFactory->addMethodCall('enableMaxDepthChecks'); 392 | } 393 | } 394 | } 395 | 396 | private function setVisitorOptions(array $config, ScopedContainer $container): void 397 | { 398 | // json (serialization) 399 | if (isset($config['visitors']['json_serialization']['options'])) { 400 | $container->getDefinition('jms_serializer.json_serialization_visitor') 401 | ->addMethodCall('setOptions', [$config['visitors']['json_serialization']['options']]); 402 | } 403 | 404 | if (isset($config['visitors']['json_serialization']['depth'])) { 405 | $container->getDefinition('jms_serializer.json_serialization_visitor') 406 | ->addMethodCall('setDepth', [$config['visitors']['json_serialization']['depth']]); 407 | } 408 | 409 | // json (deserialization) 410 | if (isset($config['visitors']['json_deserialization']['options'])) { 411 | $container->getDefinition('jms_serializer.json_deserialization_visitor') 412 | ->addMethodCall('setOptions', [$config['visitors']['json_deserialization']['options']]); 413 | } 414 | 415 | $container->getDefinition('jms_serializer.json_deserialization_visitor') 416 | ->replaceArgument(0, (bool) $config['visitors']['json_deserialization']['strict']); 417 | 418 | // xml (serialization) 419 | if (!empty($config['visitors']['xml_serialization']['default_root_name'])) { 420 | $container->getDefinition('jms_serializer.xml_serialization_visitor') 421 | ->addMethodCall('setDefaultRootName', [ 422 | $config['visitors']['xml_serialization']['default_root_name'], 423 | $config['visitors']['xml_serialization']['default_root_ns'], 424 | ]); 425 | } 426 | 427 | if (!empty($config['visitors']['xml_serialization']['version'])) { 428 | $container->getDefinition('jms_serializer.xml_serialization_visitor') 429 | ->addMethodCall('setDefaultVersion', [$config['visitors']['xml_serialization']['version']]); 430 | } 431 | 432 | if (!empty($config['visitors']['xml_serialization']['encoding'])) { 433 | $container->getDefinition('jms_serializer.xml_serialization_visitor') 434 | ->addMethodCall('setDefaultEncoding', [$config['visitors']['xml_serialization']['encoding']]); 435 | } 436 | 437 | if (!empty($config['visitors']['xml_serialization']['format_output'])) { 438 | $container->getDefinition('jms_serializer.xml_serialization_visitor') 439 | ->addMethodCall('setFormatOutput', [$config['visitors']['xml_serialization']['format_output']]); 440 | } 441 | 442 | // xml (deserialization) 443 | if (!empty($config['visitors']['xml_deserialization']['doctype_whitelist'])) { 444 | $container->getDefinition('jms_serializer.xml_deserialization_visitor') 445 | ->addMethodCall('setDoctypeWhitelist', [$config['visitors']['xml_deserialization']['doctype_whitelist']]); 446 | } 447 | 448 | if (!empty($config['visitors']['xml_deserialization']['external_entities'])) { 449 | $container->getDefinition('jms_serializer.xml_deserialization_visitor') 450 | ->addMethodCall('enableExternalEntities', [$config['visitors']['xml_deserialization']['external_entities']]); 451 | } 452 | 453 | if (!empty($config['visitors']['xml_deserialization']['options'])) { 454 | $container->getDefinition('jms_serializer.xml_deserialization_visitor') 455 | ->addMethodCall('setOptions', [$config['visitors']['xml_deserialization']['options']]); 456 | } 457 | } 458 | 459 | private function detectMetadataDirectories(array $metadata, array $bundlesMetadata): array 460 | { 461 | $directories = []; 462 | if ($metadata['auto_detection']) { 463 | foreach ($bundlesMetadata as $bundle) { 464 | if (is_dir($dir = $bundle['path'] . '/Resources/config/serializer') || is_dir($dir = $bundle['path'] . '/config/serializer')) { 465 | $directories[$bundle['namespace']] = $dir; 466 | } 467 | } 468 | } 469 | 470 | foreach ($metadata['directories'] as $directory) { 471 | $directory['path'] = rtrim(str_replace('\\', '/', $directory['path']), '/'); 472 | 473 | if ('@' === $directory['path'][0]) { 474 | $pathParts = explode('/', $directory['path'], 2); 475 | $bundleName = substr($pathParts[0], 1); 476 | 477 | if (!isset($bundlesMetadata[$bundleName])) { 478 | throw new RuntimeException(sprintf('The bundle "%s" has not been registered with AppKernel. Available bundles: %s', $bundleName, implode(', ', array_keys($bundlesMetadata)))); 479 | } 480 | 481 | $directory['path'] = $bundlesMetadata[$bundleName]['path'] . substr($directory['path'], strlen('@' . $bundleName)); 482 | } 483 | 484 | $dir = rtrim($directory['path'], '\\/'); 485 | if (!file_exists($dir)) { 486 | throw new RuntimeException(sprintf('The metadata directory "%s" does not exist for the namespace "%s"', $dir, $directory['namespace_prefix'])); 487 | } 488 | 489 | $directories[rtrim($directory['namespace_prefix'], '\\')] = $dir; 490 | } 491 | 492 | return $directories; 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /DependencyInjection/ScopedContainer.php: -------------------------------------------------------------------------------- 1 | container = $container; 19 | $this->instance = $instance; 20 | $container->setAlias('jms_serializer.instances.' . $instance, new Alias($this->getDefinitionRealId('jms_serializer.serializer'), true)); 21 | } 22 | 23 | public function getInstanceName(): string 24 | { 25 | return $this->instance; 26 | } 27 | 28 | public function removeDefinition($id) 29 | { 30 | $this->container->removeDefinition($this->getDefinitionRealId($id)); 31 | } 32 | 33 | public function register(string $id, ?string $class = null) 34 | { 35 | return $this->setDefinition($id, new Definition($class)); 36 | } 37 | 38 | public function findDefinition(string $id): Definition 39 | { 40 | return $this->container->findDefinition($this->getDefinitionRealId($id)); 41 | } 42 | 43 | public function findTaggedServiceIds($tag): array 44 | { 45 | $serviceIds = []; 46 | foreach ($this->container->findTaggedServiceIds($tag) as $id => $tags) { 47 | $def = $this->container->findDefinition($id); 48 | 49 | if ($def->hasTag('jms_serializer.instance')) { 50 | if ($def->getTag('jms_serializer.instance')[0]['name'] !== $this->instance) { 51 | continue; 52 | } 53 | } 54 | 55 | foreach ($tags as $attributes) { 56 | if (empty($attributes['instance']) || $attributes['instance'] === $this->instance) { 57 | $serviceIds[$id][] = $attributes; 58 | } 59 | } 60 | } 61 | 62 | return $serviceIds; 63 | } 64 | 65 | private function getDefinitionRealId(string $id): string 66 | { 67 | return DIUtils::getRealId($this->instance, $id); 68 | } 69 | 70 | public function getDefinition($id): Definition 71 | { 72 | return $this->container->getDefinition($this->getDefinitionRealId($id)); 73 | } 74 | 75 | public function hasDefinition($id): bool 76 | { 77 | return $this->container->hasDefinition($this->getDefinitionRealId($id)); 78 | } 79 | 80 | public function removeAlias(string $alias) 81 | { 82 | $this->container->removeAlias($this->getDefinitionRealId($alias)); 83 | } 84 | 85 | public function setAlias(string $alias, $id) 86 | { 87 | if (is_string($id)) { 88 | $id = new Alias($id); 89 | } 90 | 91 | $alias = $this->getDefinitionRealId($alias); 92 | 93 | $id = new Alias($this->getDefinitionRealId((string) $id), $id->isPublic()); 94 | 95 | return $this->container->setAlias($alias, $id); 96 | } 97 | 98 | public function __call($name, $args) 99 | { 100 | return call_user_func_array([$this->container, $name], $args); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ExpressionLanguage/BasicSerializerFunctionsProvider.php: -------------------------------------------------------------------------------- 1 | get(%s)', $arg); 18 | }, static function (array $variables, $value) { 19 | return $variables['container']->get($value); 20 | }), 21 | new ExpressionFunction('parameter', static function ($arg) { 22 | return sprintf('$this->getParameter(%s)', $arg); 23 | }, static function (array $variables, $value) { 24 | return $variables['container']->getParameter($value); 25 | }), 26 | new ExpressionFunction('is_granted', static function ($attribute, $object = null) { 27 | return sprintf('call_user_func_array(array($this->get(\'jms_serializer.authorization_checker\'), \'isGranted\'), array(%s, %s))', $attribute, $object); 28 | }, static function (array $variables, $attribute, $object = null) { 29 | return call_user_func_array( 30 | [$variables['container']->get('jms_serializer.authorization_checker'), 'isGranted'], 31 | [$attribute, $object], 32 | ); 33 | }), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /JMSSerializerBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new AssignVisitorsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); 22 | 23 | // Should run before Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass 24 | $builder->addCompilerPass(new TwigExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); 25 | 26 | $builder->addCompilerPass(new FormErrorHandlerTranslationDomainPass()); 27 | $builder->addCompilerPass(new ExpressionFunctionProviderPass()); 28 | $builder->addCompilerPass(new DoctrinePass()); 29 | 30 | $builder->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_OPTIMIZE); 31 | $builder->addCompilerPass(new CustomHandlersPass(), PassConfig::TYPE_OPTIMIZE); 32 | 33 | $builder->addCompilerPass(new AdjustDecorationPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -100); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Johannes M. Schmitt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /META.md: -------------------------------------------------------------------------------- 1 | # Generating changelog 2 | 3 | Use: https://github.com/skywinder/Github-Changelog-Generator 4 | 5 | ```bash 6 | github_changelog_generator --user=schmittjoh --project=JMSSerializerBundle --pull-requests --no-compare-link --future-release=RELEASE_NR -t GITHUB-TOKEN 7 | ``` 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > # UKRAINE NEEDS YOUR HELP NOW! 2 | > 3 | > On 24 February 2022, Russian [President Vladimir Putin ordered an invasion of Ukraine by Russian Armed Forces](https://www.bbc.com/news/world-europe-60504334). 4 | > 5 | > Your support is urgently needed. 6 | > 7 | > - Donate to the volunteers. Here is the volunteer fund helping the Ukrainian army to provide all the necessary equipment: 8 | > https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi or https://savelife.in.ua/en/donate/ 9 | > - Triple-check social media sources. Russian disinformation is attempting to coverup and distort the reality in Ukraine. 10 | > - Help Ukrainian refugees who are fleeing Russian attacks and shellings: https://www.globalcitizen.org/en/content/ways-to-help-ukraine-conflict/ 11 | > - Put pressure on your political representatives to provide help to Ukraine. 12 | > - Believe in the Ukrainian people, they will not surrender, they don't have another Ukraine. 13 | > 14 | > THANK YOU! 15 | ---- 16 | 17 | JMSSerializerBundle 18 | =================== 19 | 20 | [![GitHub Actions][GA Image]][GA Link] 21 | [![Code Coverage][Coverage Image]][CodeCov Link] 22 | [![Packagist][Packagist Image]][Packagist Link] 23 | 24 | This bundle integrates the [serializer library](https://github.com/schmittjoh/serializer) into Symfony. 25 | 26 | Please open new issues or feature request which are related to the library on the new repository. 27 | 28 | ## Documentation 29 | 30 | You can learn more about the bundle in its [documentation](http://jmsyst.com/bundles/JMSSerializerBundle). 31 | 32 | ## Professional Support 33 | 34 | For eventual paid support please write an email to [goetas@gmail.com](mailto:goetas@gmail.com). 35 | 36 | [GA Image]: https://github.com/schmittjoh/JMSSerializerBundle/workflows/CI/badge.svg 37 | 38 | [GA Link]: https://github.com/schmittjoh/JMSSerializerBundle/actions?query=workflow%3A%22CI%22+branch%3Amaster 39 | 40 | [Coverage Image]: https://codecov.io/gh/schmittjoh/JMSSerializerBundle/branch/master/graph/badge.svg 41 | 42 | [CodeCov Link]: https://codecov.io/gh/schmittjoh/JMSSerializerBundle/branch/master 43 | 44 | [Packagist Image]: https://img.shields.io/packagist/v/jms/serializer-bundle.svg 45 | 46 | [Packagist Link]: https://packagist.org/packages/jms/serializer-bundle 47 | -------------------------------------------------------------------------------- /Resources/config/debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | default 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | true 28 | false 29 | 30 | 31 | 32 | 33 | 34 | 35 | jms_serializer 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | false 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | NULL 108 | 109 | 110 | 111 | 112 | 113 | NULL 114 | 115 | 116 | 117 | 118 | 119 | 120 | NULL 121 | 122 | 123 | 124 | 125 | NULL 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | NULL 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | metadata_driver 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Metadata\ClassHierarchyMetadata 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | null 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | NULL 250 | 251 | 252 | 253 | 254 | 255 | 256 | NULL 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | jms_ 288 | 289 | 290 | 291 | 292 | jms_ 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | NULL 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | false 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 344 | 347 | 348 | 349 | 350 | 351 | 352 | -------------------------------------------------------------------------------- /Resources/doc/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | Handlers 5 | -------- 6 | You can register any service as a handler by adding either the ``jms_serializer.handler``, 7 | or the ``jms_serializer.subscribing_handler`` tag. 8 | 9 | .. configuration-block :: 10 | 11 | .. code-block :: xml 12 | 13 | 14 | 16 | 17 | 18 | .. code-block :: yaml 19 | 20 | my_handler: 21 | class: MyHandler 22 | tags: 23 | - name: jms_serializer.handler 24 | type: DateTime 25 | direction: serialization 26 | format: json 27 | method: serializeDateTimeToJson 28 | 29 | 30 | The possible tag attributes are the following: 31 | 32 | - *format*: The format that you want to handle; defaults to all formats. 33 | - *type*: The type name that you want to handle; defaults to all types. 34 | - *direction*: The direction (serialization, or deserialization); defaults to both. 35 | - *method*: The method to invoke on the ``my_handler`` service. 36 | - *instance*: The specific serializer instance name; defaults to all types when not specified., ``default`` if you want to apply it only to 37 | the main instance. 38 | 39 | .. tip :: 40 | 41 | The ``direction`` attribute is not required if you want to support both directions. Likewise can the 42 | ``method`` attribute be omitted, then a default using the scheme ``serializeTypeToFormat``, 43 | or ``deserializeTypeFromFormat`` will be used for serialization or deserialization 44 | respectively. 45 | - *instance*: The specific serializer instance name; defaults to all types when not specified., ``default`` if you want to apply it only to 46 | the main instance. 47 | 48 | 49 | Event Dispatcher 50 | ---------------- 51 | You can use the tags ``jms_serializer.event_listener``, or ``jms_serializer.event_subscriber`` 52 | in order to register a listener. 53 | 54 | The semantics are mainly the same as registering a regular Symfony event listener 55 | except that you can specify some additional attributes: 56 | 57 | - *format*: The format that you want to listen to; defaults to all formats. 58 | - *class*: The type name that you want to listen to; defaults to all types. 59 | - *direction*: The direction (serialization, or deserialization); defaults to both. 60 | - *instance*: The specific serializer instance name; defaults to all types., ``default`` if you want to apply it only to 61 | the main instance. 62 | 63 | .. note :: 64 | 65 | Events are not dispatched by Symfony's event dispatcher as such 66 | you cannot register listeners with the ``kernel.event_listener`` tag, 67 | or the ``@DI\Observe`` annotation. Please see above. 68 | 69 | 70 | Expression Language 71 | ------------------- 72 | 73 | You can add custom expression functions using the ``jms.expression.function_provider`` tag. 74 | 75 | .. configuration-block :: 76 | 77 | .. code-block :: xml 78 | 79 | 80 | 81 | 82 | 83 | .. code-block :: yaml 84 | 85 | my_function_provider: 86 | class: MyFunctionProvider 87 | tags: 88 | - jms.expression.function_provider 89 | 90 | 91 | A functions provider for the Symfony Expression Language might look something as this: 92 | 93 | .. code-block :: php 94 | 95 | use Symfony\Component\ExpressionLanguage\ExpressionFunction; 96 | use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; 97 | 98 | class MyFunctionProvider implements ExpressionFunctionProviderInterface 99 | { 100 | public function getFunctions() 101 | { 102 | return [ 103 | new ExpressionFunction('str_rot13', function ($arg) { 104 | return sprintf('str_rot13(%s)', $arg); 105 | }, function (array $variables, $value) { 106 | return str_rot13($value); 107 | }) 108 | ]; 109 | } 110 | } 111 | 112 | 113 | You can read more about it on the official `expression function providers`_ documentation. 114 | 115 | Defining Metadata 116 | ----------------- 117 | To define the metadata using YAML or XML, you need to specify their location and to which PHP namespace prefix they refer. 118 | 119 | .. configuration-block :: 120 | 121 | .. code-block :: yaml 122 | 123 | jms_serializer: 124 | metadata: 125 | directories: 126 | App: 127 | namespace_prefix: "App\\Entity" 128 | path: "%kernel.project_dir%/serializer/app" 129 | FOSUB: 130 | namespace_prefix: "FOS\\UserBundle" 131 | path: "%kernel.project_dir%/serializer/FOSUB" 132 | 133 | .. code-block :: xml 134 | 135 | 136 | 137 | 139 | 141 | 142 | 143 | 144 | .. note :: 145 | 146 | - ``path`` must not contain trailing slashes 147 | - If you are using YAML files as metadata format, the file extension to use is ``.yml`` 148 | 149 | 150 | Suppose you want to define the metadata using YAML for the classes in the ``App\\Entity`` namespace prefix 151 | and the configured path is ``%kernel.project_dir%/serializer/app``, then your metadata file **must** be named: 152 | ``%kernel.project_dir%/serializer/app/Product.yml``. 153 | 154 | 155 | This feature is also useful for **Overriding Third-Party Metadata**. 156 | Sometimes you want to serialize objects which are shipped by a third-party bundle. 157 | Such a third-party bundle might not ship with metadata that suits your needs, or 158 | possibly none, at all. In such a case, you can override the default location that 159 | is searched for metadata with a path that is under your control. 160 | 161 | 162 | Changing the Object Constructor 163 | ---------------------------------- 164 | A Constructor class is used to construct new objects during deserialization. The 165 | default constructor uses the `unserialize` function to construct objects. Other 166 | constructors are configured as services. You can set the constructor by changing 167 | the service alias: 168 | 169 | .. configuration-block :: 170 | 171 | .. code-block :: yaml 172 | 173 | services: 174 | jms_serializer.object_constructor: 175 | alias: jms_serializer.doctrine_object_constructor 176 | public: false 177 | 178 | .. code-block :: xml 179 | 180 | 181 | 182 | 183 | 184 | 185 | Extension Reference 186 | ------------------- 187 | 188 | Below you find a reference of all configuration options with their default 189 | values: 190 | 191 | .. configuration-block :: 192 | 193 | .. code-block :: yaml 194 | 195 | # config.yml 196 | jms_serializer: 197 | profiler: %kernel.debug% 198 | enum_support: true # PHP 8.1 Enums support, false by default for backward compatibility 199 | default_value_property_reader_support: true # PHP 8.0 Constructor Promotion default value support, false by default for backward compatibility 200 | twig_enabled: 'default' # on which instance is twig enabled 201 | handlers: 202 | datetime: 203 | default_format: "Y-m-d\\TH:i:sP" # ATOM 204 | default_deserialization_formats: 205 | - "Y-m-d\\TH:i:sP" # ATOM 206 | default_timezone: "UTC" # defaults to whatever timezone set in php.ini or via date_default_timezone_set 207 | array_collection: 208 | initialize_excluded: false 209 | symfony_uid: 210 | default_format: "canonical" 211 | cdata: true 212 | 213 | subscribers: 214 | doctrine_proxy: 215 | initialize_virtual_types: false 216 | initialize_excluded: false 217 | 218 | object_constructors: 219 | doctrine: 220 | enabled: true 221 | fallback_strategy: "null" # possible values ("null" | "exception" | "fallback") 222 | 223 | property_naming: 224 | id: ~ 225 | separator: _ 226 | lower_case: true 227 | 228 | metadata: 229 | cache: file 230 | debug: "%kernel.debug%" 231 | file_cache: 232 | dir: "%kernel.cache_dir%/serializer" 233 | 234 | include_interfaces: false 235 | infer_types_from_doc_block: false 236 | infer_types_from_doctrine_metadata: true 237 | 238 | # Using auto-detection, the mapping files for each bundle will be 239 | # expected in the Resources/config/serializer directory. 240 | # 241 | # Example: 242 | # class: My\FooBundle\Entity\User 243 | # expected path: @MyFooBundle/Resources/config/serializer/Entity.User.(yml|xml|php) 244 | auto_detection: true 245 | 246 | # if you don't want to use auto-detection, you can also define the 247 | # namespace prefix and the corresponding directory explicitly 248 | directories: 249 | any-name: 250 | namespace_prefix: "My\\FooBundle" 251 | path: "@MyFooBundle/Resources/config/serializer" 252 | another-name: 253 | namespace_prefix: "My\\BarBundle" 254 | path: "@MyBarBundle/Resources/config/serializer" 255 | warmup: 256 | # list of directories to scan searching for php classes to use when warming up the cache 257 | paths: 258 | included: [] 259 | excluded: [] 260 | 261 | expression_evaluator: 262 | id: jms_serializer.expression_evaluator # auto detected 263 | 264 | default_context: 265 | serialization: 266 | serialize_null: false 267 | version: ~ 268 | attributes: {} 269 | groups: ['Default'] 270 | enable_max_depth_checks: false 271 | deserialization: 272 | serialize_null: false 273 | version: ~ 274 | attributes: {} 275 | groups: ['Default'] 276 | enable_max_depth_checks: false 277 | 278 | visitors: 279 | json_serialization: 280 | options: 0 # json_encode options bitmask, suggested JSON_PRETTY_PRINT in development 281 | depth: 512 282 | json_deserialization: 283 | options: 0 # json_decode options bitmask 284 | strict: false # `true` enables strict deserialization 285 | xml_serialization: 286 | format_output: false 287 | version: "1.0" 288 | encoding: "UTF-8" 289 | default_root_name: "result" 290 | default_root_ns: null 291 | xml_deserialization: 292 | options: 0 # simplexml_load_string options bitmask 293 | external_entities: false 294 | doctype_whitelist: 295 | - '' # an authorized document type for xml deserialization 296 | instances: 297 | foo: ~ 298 | inherit: false 299 | # + all the configurations above, but for a independent 'jms_serializer.instances.foo' serializer instance 300 | # as example: 301 | property_naming: 302 | separator: - 303 | lower_case: false # the `jms_serializer.instances.foo` will use a different naming strategy compared to `jms_serializer.instances.default` 304 | bar: ~ 305 | # all the configurations above, but for a independent 'jms_serializer.instances.bar' serializer instance 306 | # more instances here ... 307 | 308 | 309 | .. _expression function providers: https://symfony.com/doc/current/components/expression_language/extending.html#using-expression-providers 310 | -------------------------------------------------------------------------------- /Resources/doc/cookbook.rst: -------------------------------------------------------------------------------- 1 | Cookbook 2 | ======== 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/doc/cookbook/exclusion_strategies.rst: -------------------------------------------------------------------------------- 1 | Exclusion Strategies 2 | ==================== 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. 6 | -------------------------------------------------------------------------------- /Resources/doc/event_system.rst: -------------------------------------------------------------------------------- 1 | Event System 2 | ============ 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/doc/handlers.rst: -------------------------------------------------------------------------------- 1 | Handlers 2 | ======== 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/doc/index.rst: -------------------------------------------------------------------------------- 1 | JMSSerializerBundle 2 | =================== 3 | 4 | Introduction 5 | ------------ 6 | JMSSerializerBundle allows you to serialize your data into a requested 7 | output format such as JSON, XML, or YAML, and vice versa. 8 | 9 | You can learn more in the `documentation `_ for the standalone library. 10 | 11 | Installation 12 | ------------ 13 | You can install this bundle using composer 14 | 15 | .. code-block :: bash 16 | 17 | composer require jms/serializer-bundle 18 | 19 | or add the package to your ``composer.json`` file directly. 20 | 21 | After you have installed the package, you just need to add the bundle to your ``AppKernel.php`` file:: 22 | 23 | // in AppKernel::registerBundles() 24 | $bundles = array( 25 | // ... 26 | new JMS\SerializerBundle\JMSSerializerBundle(), 27 | // ... 28 | ); 29 | 30 | Configuration 31 | ------------- 32 | JMSSerializerBundle requires no initial configuration to get you started. 33 | 34 | For all available configuration options, please see the :doc:`configuration reference `. 35 | 36 | Usage 37 | ----- 38 | The configured serializer is available as ``jms_serializer`` (or ``jms_serializer.instances.default``) service:: 39 | 40 | $serializer = $container->get('jms_serializer'); 41 | $serializer->serialize($data, $format); 42 | $data = $serializer->deserialize($inputStr, $typeName, $format); 43 | 44 | In templates, you may also use the ``serialize`` filter: 45 | 46 | .. code-block :: html+jinja 47 | 48 | {{ data | jms_serialize }} {# serializes to JSON #} 49 | {{ data | jms_serialize('json') }} 50 | {{ data | jms_serialize('xml') }} 51 | 52 | Learn more in the `documentation for the dedicated library `_. 53 | 54 | License 55 | ------- 56 | 57 | The code is released under the `MIT license`_. 58 | 59 | Documentation is subject to the `Attribution-NonCommercial-NoDerivs 3.0 Unported 60 | license`_. 61 | 62 | .. _MIT license: https://opensource.org/licenses/MIT 63 | .. _Attribution-NonCommercial-NoDerivs 3.0 Unported license: http://creativecommons.org/licenses/by-nc-nd/3.0/ 64 | 65 | -------------------------------------------------------------------------------- /Resources/doc/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Step 1: Download the Bundle 5 | --------------------------- 6 | 7 | Open a command console, enter your project directory and execute the 8 | following command to download the latest stable version of this bundle: 9 | 10 | .. code-block:: bash 11 | 12 | $ composer require jms/serializer-bundle 13 | 14 | This command requires you to have Composer installed globally, as explained 15 | in the `installation chapter `_ 16 | of the Composer documentation. 17 | 18 | Step 2: Enable the Bundle 19 | ------------------------- 20 | 21 | Symfony Flex Applications 22 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | 24 | For an application using Symfony Flex, the bundle should be automatically 25 | enabled. If it is not, you will need to add it to the ``config/bundles.php`` 26 | file in your project: 27 | 28 | .. code-block:: php 29 | 30 | ['all' => true], 36 | ]; 37 | 38 | 39 | Symfony Standard Applications 40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 41 | 42 | For an application based on the Symfony Standard structure, you will need to 43 | enable the bundle in your Kernel by adding the following line in the 44 | ``app/AppKernel.php`` file in your project: 45 | 46 | .. code-block:: php 47 | 48 | `_. -------------------------------------------------------------------------------- /Resources/doc/reference/annotations.rst: -------------------------------------------------------------------------------- 1 | Annotations 2 | ----------- 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/doc/reference/xml_reference.rst: -------------------------------------------------------------------------------- 1 | XML Reference 2 | ------------- 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/doc/reference/yml_reference.rst: -------------------------------------------------------------------------------- 1 | YAML Reference 2 | -------------- 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/doc/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | This document was moved to the standalone library, please see 5 | ``_. -------------------------------------------------------------------------------- /Resources/views/Collector/events.html.twig: -------------------------------------------------------------------------------- 1 | {%- import _self as helper -%} 2 | 3 |

Event Dispatcher

4 | 5 |
6 |
7 |
Triggered Listeners {{ collector.getNumListeners('called') }}
8 | 9 |
10 | {%- if 0 == collector.getNumListeners('called') -%} 11 |
12 |

No triggered listeners.

13 |
14 | {%- else -%} 15 | {{- helper.render_table(collector.triggeredListeners) -}} 16 | {%- endif -%} 17 |
18 |
19 | 20 |
21 |
Not Called Listeners {{ collector.getNumListeners('not_called') }}
22 |
23 | {{ helper.render_table_not_triggered_listeners(collector.notTriggeredListeners) }} 24 |
25 |
26 |
27 | 28 | {%- macro render_table_not_triggered_listeners(notCalledListenersPerEvent) -%} 29 | 30 | {%- for eventName, listeners in notCalledListenersPerEvent -%} 31 |

{{ eventName }}

32 | 33 | 34 | 35 | 36 | 37 | 38 | {%- for listener in listeners -%} 39 | 40 | 41 | 42 | {%- endfor -%} 43 |
Listener
{{ dump(listener) }}
44 | {%- endfor -%} 45 | {%- endmacro -%} 46 | 47 | 48 | {%- macro render_table(listeners) -%} 49 | 50 | {%- for eventName, callsPerlistener in listeners -%} 51 |

{{ eventName }}

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {%- for listener, callsPerClass in callsPerlistener -%} 61 | 62 | 63 | 64 | {%- for className, callsInfo in callsPerClass -%} 65 | 66 | 67 | 68 | 69 | 70 | 71 | {%- endfor -%} 72 | {%- endfor -%} 73 |
ClassCallsTotal duration (ms)
{{ dump(listener) }}
 {{ className }}{{ callsInfo.calls }}{{ callsInfo.duration ? (callsInfo.duration * 1000)|number_format(4) : '' }}
74 | {%- endfor -%} 75 | {%- endmacro -%} 76 | -------------------------------------------------------------------------------- /Resources/views/Collector/handlers.html.twig: -------------------------------------------------------------------------------- 1 | {%- import _self as helper -%} 2 | 3 |

Type Handlers

4 | 5 |
6 |
7 |

Triggered Handlers {{ collector.getNumHandlers('called') }}

8 | 9 |
10 | {%- if 0 == collector.getNumHandlers('called') -%} 11 |
12 |

No triggered handlers.

13 |
14 | {%- else -%} 15 | {{- helper.render_table_triggered_handlers(collector.triggeredHandlers) -}} 16 | {%- endif -%} 17 |
18 |
19 | 20 |
21 |
Not Triggered Handlers {{ collector.getNumHandlers('not_called') }}
22 |
23 | {{ helper.render_not_table_triggered_handlers(collector.notTriggeredHandlers) }} 24 |
25 |
26 |
27 | 28 | {%- macro render_not_table_triggered_handlers(handlers) -%} 29 | {%- for direction, callsByType in handlers -%} 30 |

31 | {%- if direction == constant('JMS\\Serializer\\GraphNavigatorInterface::DIRECTION_SERIALIZATION') -%} 32 | Serialization 33 | {%- else -%} 34 | Deserialization 35 | {%- endif -%} 36 |

37 | 38 | 39 | 40 | 41 | 42 | {%- if called|default(false) -%} 43 | 44 | 45 | {%- endif -%} 46 | 47 | 48 | {%- for type, handlers in callsByType -%} 49 | 50 | 51 | 56 | 57 | {%- endfor -%} 58 | 59 |
Date typeHandlerCallsTotal duration (ms)
{{ type }} 52 | {%- for handler in handlers -%} 53 | {{ dump(handler) }}
54 | {%- endfor -%} 55 |
60 | {%- endfor -%} 61 | {%- endmacro -%} 62 | 63 | {%- macro render_table_triggered_handlers(handlers) -%} 64 | {%- for direction, callsByType in handlers -%} 65 |

66 | {%- if direction == constant('JMS\\Serializer\\GraphNavigatorInterface::DIRECTION_SERIALIZATION') -%} 67 | Serialization 68 | {%- else -%} 69 | Deserialization 70 | {%- endif -%} 71 |

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {%- for type, calls in callsByType -%} 82 | {%- for call in calls -%} 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {%- endfor -%} 91 | {%- endfor -%} 92 | 93 |
Date typeHandlerCallsTotal duration (ms)
{{ type }}{{ dump(call.handler) }}{{ call.calls }}{{ call.duration ? (call.duration * 1000)|number_format(4) : '' }}
94 | {%- endfor -%} 95 | {%- endmacro -%} 96 | -------------------------------------------------------------------------------- /Resources/views/Collector/metadata.html.twig: -------------------------------------------------------------------------------- 1 |

Loaded metadata

2 | 3 | {%- if collector.loadedMetadata is empty -%} 4 |
5 |

No metadata have been loaded.

6 |
7 | {%- else -%} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {%- for class, files in collector.loadedMetadata -%} 17 | 18 | 19 | 24 | 25 | {%- endfor -%} 26 |
ClassFile
{{ class }} 20 | {%- for file in files -%} 21 | {{ file }}
22 | {% endfor %} 23 |
27 | {%- endif -%} 28 | 29 | 30 |

Attempted files

31 | {%- if collector.metadataFiles is empty -%} 32 |
33 |

No metadata files attempted (if this list is empty, probably all the data are cached as expected)

34 |
35 | {%- else -%} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {%- for class, files in collector.metadataFiles -%} 44 | 45 | 46 | 53 | 54 | {%- endfor -%} 55 |
ClassFiles
{{ class }} 47 | {%- for file, found in files -%} 48 | 49 | {{ file }} ({{ found ? 'found': 'not found' }})
50 |
51 | {% endfor %} 52 |
56 | {%- endif -%} 57 | 58 |

Configured metadata directories

59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {%- for prefix, path in collector.loadedDirs -%} 67 | 68 | 69 | 70 | 71 | {%- endfor -%} 72 |
PrefixPath
{{ prefix }}{{ path }}
73 | -------------------------------------------------------------------------------- /Resources/views/Collector/panel.html.twig: -------------------------------------------------------------------------------- 1 | {%- extends '@WebProfiler/Profiler/layout.html.twig' -%} 2 | {%- import _self as helper -%} 3 | {%- block toolbar -%} 4 | 5 | {%- set icon -%} 6 | {{- include('@JMSSerializer/icon.svg') }} 7 | S: 8 | {{- collector.runs(1)|length }} 9 | D: 10 | {{- collector.runs(2)|length }} 11 | {% if collector.instance != 'default' %} 12 | {{- collector.instance -}} 13 | {% endif %} 14 | {%- endset -%} 15 | 16 | {%- set text -%} 17 |
18 | Serializations 19 | {{ collector.runs(1)|length }} 20 |
21 |
22 | Deserializations 23 | {{ collector.runs(2)|length }} 24 |
25 |
26 | Listeners 27 | {{ collector.getNumListeners('called') }} 28 |
29 |
30 | Handlers 31 | {{ collector.getNumHandlers('called') }} 32 |
33 |
34 | Metadata 35 | {{ collector.loadedMetadata|length }} 36 |
37 | {%- endset -%} 38 | 39 | {%- include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } -%} 40 | {%- endblock -%} 41 | 42 | {%- block head -%} 43 | 46 | 59 | 62 | {{ parent() }} 63 | {%- endblock -%} 64 | 65 | {%- block menu -%} 66 | {# This left-hand menu appears when using the full-screen profiler. #} 67 | {% set total = (collector.runs(1)|length) + (collector.runs(2)|length) %} 68 | 69 | 70 | {{ include('@JMSSerializer/icon.svg') }} 71 | 72 | 73 | JMS Serializer 74 | {% if collector.instance != 'default' %} 75 |
({{- collector.instance -}})
76 | {% endif %} 77 |
78 | 79 | 80 | {{ (collector.runs(1)|length) }} + {{ (collector.runs(2)|length) }} 81 | 82 | 83 |
84 | {%- endblock -%} 85 | 86 | {%- block panel -%} 87 |

88 | JMS Serializer 89 | {% if collector.instance != 'default' %} 90 | ({{- collector.instance -}}) 91 | {% endif %} 92 |

93 | 94 |
95 |
96 | {{- collector.runs(1)|length }} 97 | Serializations 98 |
99 |
100 | {{- collector.runs(2)|length }} 101 | Deserializations 102 |
103 |
104 |
105 | {{- collector.triggeredEvents.count }} 106 | Triggered event listeners 107 |
108 |
109 | {{- collector.triggeredEvents.duration|round(2) }} ms 110 | Triggered event listeners (time) 111 |
112 |
113 | 114 | {{ helper.render_table_runs(collector.runs(1), 'Serializations') }} 115 | {{ helper.render_table_runs(collector.runs(2), 'Deserializations') }} 116 | 117 |
118 |
119 |

120 | Events 121 | 122 | {{ collector.triggeredListeners|length }} 123 | ({{ collector.getNumListeners('called') }} listeners) 124 | 125 | 126 | 127 |

128 | 129 |
130 |

Triggered event listeners

131 | 132 |
133 | {%- include '@JMSSerializer/Collector/events.html.twig' -%} 134 |
135 |
136 |
137 | 138 |
139 |

140 | Handlers 141 | 142 | {{ collector.triggeredHandlers|length }} 143 | ({{ collector.getNumHandlers('called') }} types) 144 | 145 |

146 | 147 |
148 |

Triggered event handlers

149 | 150 |
151 | {%- include '@JMSSerializer/Collector/handlers.html.twig' -%} 152 |
153 |
154 |
155 | 156 |
157 |

158 | Metadata 159 | 160 | {{- collector.loadedMetadata|length -}} 161 | 162 |

163 | 164 |
165 |

Loaded metadata

166 | 167 |
168 | {%- include '@JMSSerializer/Collector/metadata.html.twig' -%} 169 |
170 |
171 |
172 |
173 | {%- endblock -%} 174 | 175 | {%- macro render_table_runs(runs, mode) -%} 176 | 177 | {%- if (runs|length)>0 -%} 178 |

{{ mode }}

179 | 180 | 181 | 182 | 183 | 184 | 185 | {%- for run in runs -%} 186 | 187 | 188 | 189 | {%- endfor -%} 190 |
Type
{{ dump(run.type) }}
191 | {%- endif -%} 192 | {%- endmacro -%} 193 | -------------------------------------------------------------------------------- /Resources/views/Collector/script/jms.js.twig: -------------------------------------------------------------------------------- 1 | /** 2 | * Toggle visibility on elements. 3 | */ 4 | document.addEventListener("DOMContentLoaded", function() { 5 | Array.prototype.forEach.call(document.getElementsByClassName('jms-toggle'), function (source) { 6 | source.addEventListener('click', function() { 7 | Array.prototype.forEach.call(document.querySelectorAll(source.getAttribute('data-toggle')), function (target) { 8 | target.classList.toggle('jms-hidden'); 9 | }); 10 | }); 11 | }); 12 | }); 13 | 14 | /** 15 | * Copy as cURL. 16 | */ 17 | document.addEventListener("DOMContentLoaded", function () { 18 | Array.prototype.forEach.call(document.getElementsByClassName('jms-toolbar'), function (toolbar) { 19 | var button = toolbar.querySelector('.jms-copy-as-curl>button'); 20 | button.addEventListener('click', function() { 21 | var input = toolbar.querySelector('.jms-copy-as-curl>input'); 22 | input.select(); 23 | document.execCommand('copy'); 24 | input.setSelectionRange(0, 0); 25 | }); 26 | }); 27 | }) 28 | -------------------------------------------------------------------------------- /Resources/views/Collector/style/jms.css.twig: -------------------------------------------------------------------------------- 1 | .jms-push-right { 2 | float: right; 3 | } 4 | 5 | .jms-plugin-name { 6 | font-size: 130%; 7 | font-weight: bold; 8 | text-align: center; 9 | } 10 | 11 | .jms-hidden { 12 | display: none; 13 | } 14 | 15 | .jms-toggle { 16 | cursor: pointer; 17 | } 18 | 19 | .jms-center { 20 | text-align: center; 21 | } 22 | 23 | /** 24 | * Toolbar 25 | */ 26 | .jms-toolbar { 27 | display: flex; 28 | justify-content: space-between; 29 | } 30 | 31 | .jms-toolbar>*:not(:last-child) { 32 | margin-right:5px; 33 | } 34 | 35 | .jms-toolbar .jms-copy-as-curl { 36 | flex: 1; 37 | } 38 | 39 | .jms-copy-as-curl { 40 | font-size: 0; /*hide line return spacings*/ 41 | display: flex; 42 | } 43 | 44 | .jms-copy-as-curl>input { 45 | padding: .5em .75em; 46 | border-radius: 2px 0px 0px 2px; 47 | border: 0; 48 | line-height: inherit; 49 | background-color: #eee; 50 | opacity: 1; 51 | font-size: 14px; 52 | flex: 1; 53 | } 54 | 55 | .jms-copy-as-curl>button { 56 | font-size: 14px; 57 | border-radius: 0px 2px 2px 0px; 58 | } 59 | 60 | /** 61 | * Message 62 | */ 63 | .jms-message { 64 | box-sizing: border-box; 65 | padding: 5px; 66 | flex: 1; 67 | margin: 5px; 68 | overflow-x: auto; 69 | white-space: nowrap; 70 | } 71 | 72 | .jms-messages { 73 | clear: both; 74 | display: flex; 75 | } 76 | 77 | /** 78 | * Stack 79 | */ 80 | .jms-stack>.jms-stack { 81 | margin-left: 2.5em; 82 | } 83 | 84 | /** 85 | * Stack header 86 | */ 87 | .jms-stack-header { 88 | display: flex; 89 | justify-content: space-between; 90 | 91 | background: #FFF; 92 | border: 1px solid #E0E0E0; 93 | box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); 94 | margin: 1em 0; 95 | padding: 10px; 96 | } 97 | 98 | .jms-stack-failed { 99 | color:#B0413E; 100 | } 101 | 102 | .jms-stack-success { 103 | color: #4F805D; 104 | } 105 | 106 | .jms-stack-header .jms-stack-header-target { 107 | flex: 1; 108 | overflow: hidden; 109 | text-overflow: ellipsis; 110 | white-space: nowrap; 111 | background: white; 112 | color: black; 113 | font-size: 0; /*hide line return spacings*/ 114 | } 115 | 116 | .jms-scheme-http { 117 | display: none; 118 | } 119 | 120 | .jms-scheme-https { 121 | color: green; 122 | } 123 | 124 | .jms-target, .jms-scheme { 125 | font-weight: normal; 126 | } 127 | 128 | .jms-target, .jms-host, .jms-scheme { 129 | font-size: 12px; 130 | } 131 | 132 | .jms-duration { 133 | min-width: 6ch; 134 | text-align:center; 135 | } 136 | 137 | /** 138 | * HTTP method colors from swagger-ui. 139 | */ 140 | .jms-method.label { 141 | color: black; 142 | width: 9ch; 143 | text-align: center; 144 | } 145 | 146 | .jms-method-post.label { 147 | background: #49cc90; 148 | } 149 | 150 | .jms-method-get.label { 151 | background: #61affe; 152 | } 153 | 154 | .jms-method-put.label { 155 | background: #fca130; 156 | } 157 | 158 | .jms-method-delete.label { 159 | background: #f93e3e; 160 | } 161 | 162 | .jms-method-head.label { 163 | background: #9012fe; 164 | color: white; 165 | } 166 | 167 | .jms-method-patch.label { 168 | background: #50e3c2; 169 | } 170 | 171 | .jms-method-options.label { 172 | background: #0d5aa7; 173 | color: white; 174 | } 175 | 176 | .jms-method-connect.label { 177 | background: #ebebeb; 178 | } 179 | 180 | .jms-method-trace.label { 181 | background: #ebebeb; 182 | } 183 | -------------------------------------------------------------------------------- /Resources/views/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /Serializer/StopwatchEventSubscriber.php: -------------------------------------------------------------------------------- 1 | */ 12 | class StopwatchEventSubscriber implements EventSubscriberInterface 13 | { 14 | /** 15 | * {@inheritDoc} 16 | */ 17 | public static function getSubscribedEvents() 18 | { 19 | return [ 20 | ['event' => Events::PRE_SERIALIZE, 'method' => 'onPreSerialize', 'priority' => -1000], 21 | ['event' => Events::POST_SERIALIZE, 'method' => 'onPostSerialize', 'priority' => 1000], 22 | ]; 23 | } 24 | 25 | /** 26 | * A stopwatch object which exposes a start($name) and a stop($name) method. 27 | * 28 | * @var object 29 | */ 30 | private $stopwatch; 31 | 32 | /** @var string */ 33 | private $name; 34 | 35 | /** @var object */ 36 | private $rootObject; 37 | 38 | public function __construct($stopwatch, $name = 'jms_serializer') 39 | { 40 | $this->stopwatch = $stopwatch; 41 | $this->name = $name; 42 | } 43 | 44 | public function onPreSerialize(ObjectEvent $event) 45 | { 46 | if ($event->getContext()->getDepth() > 1 || null !== $this->rootObject) { 47 | return; 48 | } 49 | 50 | $this->stopwatch->start($this->name); 51 | $this->rootObject = $event->getObject(); 52 | } 53 | 54 | public function onPostSerialize(ObjectEvent $event) 55 | { 56 | if (null === $this->rootObject || $event->getObject() !== $this->rootObject) { 57 | return; 58 | } 59 | 60 | $this->stopwatch->stop($this->name); 61 | $this->rootObject = null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Templating/SerializerHelper.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class SerializerHelper extends Helper 20 | { 21 | protected $serializer; 22 | 23 | public function getName(): string 24 | { 25 | return 'jms_serializer'; 26 | } 27 | 28 | public function __construct(SerializerInterface $serializer) 29 | { 30 | $this->serializer = $serializer; 31 | } 32 | 33 | /** 34 | * @param mixed $object 35 | * @param string $type 36 | * 37 | * @return string Serialized data 38 | */ 39 | public function serialize($object, $type = 'json') 40 | { 41 | return $this->serializer->serialize($object, $type); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | This document details the changes that you need to make to your code 2 | when upgrading from one version to another. 3 | 4 | Upgrading From 4.x to 5.0 5 | ========================== 6 | 7 | The 5.0 release allows you to define multiple serializer instances (with different configurations and metadata). 8 | There are no expected breaking changes in the 5.0 release, 9 | but the dependency injection system has been heavily changed and because of it, 10 | the safest option was to release a new major version. 11 | 12 | Upgrading From 3.x to 4.0 13 | ========================== 14 | 15 | - The twig filter has been renamed from `serialize` to `jms_serialize`. 16 | 17 | Before: 18 | ```jinja 19 | {{ data | serialize }} 20 | ``` 21 | After: 22 | ```jinja 23 | {{ data | jms_serialize }} 24 | ``` 25 | 26 | - The services `jms_serializer.handler_registry` and `jms_serializer.event_dispatcher` now can be decorated using the Symfony 27 | decoration strategy. If you were doing something particular with such services, there could be a BC break. 28 | - If you are using `friendsofsymfony/rest-bundle`, the `jms_serializer.handler_registry` is not decorated anymore, 29 | it will not look anymore for handlers belonging to parent classes. 30 | 31 | Upgrading From 2.x to 3.0 32 | ========================== 33 | 34 | - The configuration options under `jms_serializer.visitors` previously were `json` and `xml`, 35 | now the options are direction specific as `json_serialization`, `json_deserialization` , 36 | `xml_serialization`, `xml_deserialization`. 37 | A complete list of options is available in the [configration reference](/Resources/doc/configuration.rst#extension-reference) 38 | - Defining the naming strategy by parameters does not work any longer. 39 | 40 | Before: 41 | ``` 42 | parameters: 43 | jms_serializer.serialized_name_annotation_strategy.class: JMS\Serializer\Naming\IdenticalPropertyNamingStrategy 44 | ``` 45 | 46 | After: 47 | ``` 48 | jms_serializer: 49 | property_naming: 50 | id: 'jms_serializer.identical_property_naming_strategy' # service id of the naming strategy 51 | ``` 52 | 53 | Upgrading From 1.x to 2.0 54 | ========================== 55 | 56 | - Removed `serializer` alias, to access the serializer use the alias `jms_serializer` [#558](https://github.com/schmittjoh/JMSSerializerBundle/issues/558) 57 | - Removed the `enable_short_alias` configuration option 58 | - Changed the default datetime format from `ISO8601` (`Y-m-d\TH:i:sO`) to `RFC3339` (`Y-m-d\TH:i:sP`) [#494](https://github.com/schmittjoh/JMSSerializerBundle/issues/494) 59 | - Defining not-existing metadata directories will trigger an exception [#517](https://github.com/schmittjoh/JMSSerializerBundle/issues/517) 60 | - The "key" (or `name` attribute) for the metadata directories definition is mandatory now [#531](https://github.com/schmittjoh/JMSSerializerBundle/pull/531) 61 | - The options `subscribers.doctrine_proxy.initialize_virtual_types`, `subscribers.doctrine_proxy.initialize_excluded` and `handlers.array_collection.initialize_excluded` now as default are `false` 62 | 63 | 64 | Upgrading From 0.11 to 1.0 65 | ========================== 66 | Nothing yet. 67 | 68 | Upgrading From 0.10 to 0.11 69 | =========================== 70 | 71 | - Namespace Changes 72 | 73 | The core library has been extracted to a dedicated repository ``schmittjoh/serializer`` 74 | to make it easier re-usable in any kind of PHP project, not only in Symfony2 projects. 75 | This results in several namespace changes. You can adjust your projects by performing 76 | these replacements (in order): 77 | 78 | - ``JMS\SerializerBundle\Serializer`` -> ``JMS\Serializer`` 79 | - ``JMS\SerializerBundle`` -> ``JMS\Serializer`` 80 | - ``JMS\Serializer\DependencyInjection`` -> ``JMS\SerializerBundle\DependencyInjection`` 81 | 82 | - Dependency Changes 83 | 84 | You might need to increase versions of jms/di-extra-bundle, and also jms/security-extra-bundle 85 | depending on your stability settings. Sometimes it is also necessary to run a composer update 86 | twice because of a bug in composer's solving algorithm. 87 | 88 | 89 | Upgrading From 0.9 to 0.10 90 | ========================== 91 | 92 | - Custom Handlers 93 | 94 | The interfaces ``SerializationHandlerInterface``, and ``DeserializationHandlerInterface`` 95 | have been removed. Instead, you can now use either an event listener, or the new handler 96 | concept. As a general rule, if your handler was registered for a specific type, you 97 | would use the new handler system, if you instead were handling an arbitrary number of 98 | possibly unknown types, you would use the event system. 99 | 100 | Please see the documentation for how to set-up one of these. 101 | 102 | - Objects implementing Traversable 103 | 104 | Objects that implement the Traversable interface are not automatically treated specially 105 | anymore, but are serialized just like any regular object. If you would like to restore the 106 | previous behavior, you can either add a custom handler, or force the serialization type 107 | to ``array`` using the ``@Type`` annotation (or its equivalent in XML/YML): 108 | 109 | ``` 110 | /** @Type("array") */ 111 | private $myTraversableObject; 112 | ``` 113 | 114 | - Configuration 115 | 116 | Most of the configuration under ``jms_serializer.handlers`` is gone. The order is not 117 | important anymore as a handler can only be registered for one specific type. 118 | 119 | You can still configure the built-in ``datetime`` handler though: 120 | 121 | ``` 122 | jms_serializer: 123 | handlers: 124 | datetime: 125 | default_format: DateTime::ISO8601 126 | default_timezone: UTC 127 | ``` 128 | 129 | This is not necessary anymore though as you can now specify the format each time when 130 | you use a DateTime by using the @Type annotation: 131 | 132 | ``` 133 | /** @Type("DateTime<'Y-m-d', 'UTC'>") */ 134 | private $createdAt; 135 | ``` 136 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jms/serializer-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Allows you to easily serialize, and deserialize data of any complexity", 5 | "keywords": [ 6 | "serialization", 7 | "deserialization", 8 | "json", 9 | "xml" 10 | ], 11 | "homepage": "http://jmsyst.com/bundles/JMSSerializerBundle", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Johannes M. Schmitt", 16 | "email": "schmittjoh@gmail.com" 17 | }, 18 | { 19 | "name": "Asmir Mustafic", 20 | "email": "goetas@gmail.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^7.4 || ^8.0", 25 | "jms/serializer": "^3.31", 26 | "jms/metadata": "^2.6", 27 | "symfony/config": "^5.4 || ^6.0 || ^7.0", 28 | "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", 29 | "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" 30 | }, 31 | "require-dev": { 32 | "doctrine/annotations": "^1.14 || ^2.0", 33 | "doctrine/coding-standard": "^12.0", 34 | "doctrine/orm": "^2.14", 35 | "phpunit/phpunit": "^8.0 || ^9.0", 36 | "symfony/expression-language": "^5.4 || ^6.0 || ^7.0", 37 | "symfony/finder": "^5.4 || ^6.0 || ^7.0", 38 | "symfony/form": "^5.4 || ^6.0 || ^7.0", 39 | "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", 40 | "symfony/templating": "^5.4 || ^6.0", 41 | "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", 42 | "symfony/uid": "^5.4 || ^6.0 || ^7.0", 43 | "symfony/validator": "^5.4 || ^6.0 || ^7.0", 44 | "symfony/yaml": "^5.4 || ^6.0 || ^7.0" 45 | }, 46 | "suggest": { 47 | "symfony/expression-language": "Required for opcache preloading ^5.4 || ^6.0 || ^7.0", 48 | "symfony/finder": "Required for cache warmup, supported versions ^5.4 || ^6.0 || ^7.0" 49 | }, 50 | "config": { 51 | "allow-plugins": { 52 | "dealerdirect/phpcodesniffer-composer-installer": true 53 | }, 54 | "sort-packages": true 55 | }, 56 | "extra": { 57 | "branch-alias": { 58 | "dev-master": "5.x-dev" 59 | } 60 | }, 61 | "autoload": { 62 | "psr-4": { 63 | "JMS\\SerializerBundle\\": "" 64 | }, 65 | "exclude-from-classmap": [ 66 | "/Tests/" 67 | ] 68 | }, 69 | "autoload-dev": { 70 | "psr-4": { 71 | "JMS\\SerializerBundle\\Tests\\": "Tests" 72 | } 73 | } 74 | } 75 | --------------------------------------------------------------------------------