From 4fa804621b9b6356171f66fbf5906f82b5f50d14 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Sat, 24 Jul 2021 15:12:44 +0200 Subject: [PATCH] proof of concept: profiler --- Debug/DataCollector.php | 91 +++++++++ Debug/TraceableDriver.php | 60 ++++++ Debug/TraceableEventDispatcher.php | 107 ++++++++++ Debug/TraceableHandlerRegistry.php | 100 ++++++++++ .../Compiler/CustomHandlersPass.php | 16 +- ...gisterEventListenersAndSubscribersPass.php | 37 ++-- .../JMSSerializerExtension.php | 7 +- JMSSerializerBundle.php | 12 +- Resources/config/debug.xml | 55 ++++++ Resources/config/services.xml | 6 +- Resources/views/Collector/events.html.twig | 75 ++++++++ Resources/views/Collector/handlers.html.twig | 63 ++++++ Resources/views/Collector/metadata.html.twig | 33 ++++ Resources/views/Collector/panel.html.twig | 121 ++++++++++++ Resources/views/Collector/script/jms.js.twig | 27 +++ Resources/views/Collector/style/jms.css.twig | 182 ++++++++++++++++++ Resources/views/icon.svg | 14 ++ 17 files changed, 977 insertions(+), 29 deletions(-) create mode 100644 Debug/DataCollector.php create mode 100644 Debug/TraceableDriver.php create mode 100644 Debug/TraceableEventDispatcher.php create mode 100644 Debug/TraceableHandlerRegistry.php create mode 100644 Resources/config/debug.xml create mode 100644 Resources/views/Collector/events.html.twig create mode 100644 Resources/views/Collector/handlers.html.twig create mode 100644 Resources/views/Collector/metadata.html.twig create mode 100644 Resources/views/Collector/panel.html.twig create mode 100644 Resources/views/Collector/script/jms.js.twig create mode 100644 Resources/views/Collector/style/jms.css.twig create mode 100644 Resources/views/icon.svg diff --git a/Debug/DataCollector.php b/Debug/DataCollector.php new file mode 100644 index 00000000..9261e576 --- /dev/null +++ b/Debug/DataCollector.php @@ -0,0 +1,91 @@ +eventDispatcher = $eventDispatcher; + $this->handler = $handler; + $this->metadataDriver = $metadataDriver; + + $this->reset(); + } + + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + } + + public function reset(): void + { + $this->data['handlers'] = []; + $this->data['metadata'] = []; + $this->data['listeners'] = []; + } + + public function getName(): string + { + return 'jms_serializer'; + } + + public function addTriggeredEvent(array $call): void + { + $this->data['listeners'][] = $call; + } + + public function getTriggeredListeners(): array + { + return $this->data['listeners']['called']; + } + + public function getNotTriggeredListeners(): array + { + return $this->data['listeners']['not_called']; + } + + public function getTriggeredHandlers(): array + { + return $this->data['handlers']['called']; + } + + public function getNotTriggeredHandlers(): array + { + return $this->data['handlers']['not_called']; + } + + public function getLoadedMetadata(): array + { + return $this->data['metadata']; + } + + public function lateCollect(): void + { + $this->data['listeners'] = [ + 'called' => $this->eventDispatcher->getTriggeredListeners(), + 'not_called' => $this->eventDispatcher->getNotTriggeredListeners(), + ]; + + + $this->data['handlers'] = [ + 'called' => $this->handler->getTriggeredHandlers(), + 'not_called' => $this->handler->getNotTriggeredHandlers(), + ]; + + $this->data['metadata'] = $this->metadataDriver->getLoadedMetadata(); + } +} diff --git a/Debug/TraceableDriver.php b/Debug/TraceableDriver.php new file mode 100644 index 00000000..c1548c97 --- /dev/null +++ b/Debug/TraceableDriver.php @@ -0,0 +1,60 @@ +driver = $driver; + } + + public function getLoadedMetadata() + { + return $this->storage; + } + + public function load($class): ?ClassMetadata + { + try{ + return $metadata = $this->driver->load($class); + } finally { + if ($metadata){ + $this->trackMetadata($metadata); + } + } + + } + + private function trackMetadata(ClassMetadata $metadata): void + { + $class = $metadata->name; + $this->storage[$class] = array_merge( + $this->storage[$class] ?? [], $metadata->fileResources + ); + $this->storage[$class] = array_unique($this->storage[$class]); + } + + public function put(ClassMetadata $metadata): void + { + $this->driver->put($metadata); + $this->trackMetadata($metadata); + } + + public function evict(string $class): void + { + $this->driver->evict($class); + } +} diff --git a/Debug/TraceableEventDispatcher.php b/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..f2bae871 --- /dev/null +++ b/Debug/TraceableEventDispatcher.php @@ -0,0 +1,107 @@ +storage as $eventName => $calledOnTypes) { + foreach ($calledOnTypes as $type => $calls) { + foreach ($calls as $call) { + $listener = $this->findNameForListener($call['listener']); + $resultsByListener[$eventName][$listener][$type][] = $call; + } + } + } + + foreach ($resultsByListener as $eventName => $calledOnListeners) { + foreach ($calledOnListeners as $listener => $calledOnTypes) { + foreach ($calledOnTypes as $type => $calls) { + $resultsByListener[$eventName][$listener][$type] = [ + 'calls' => count($calls), + 'duration' => $this->calculateTotalDuration($calls) + ]; + } + } + } + + return $resultsByListener; + } + + private function findNameForListener($listener): string + { + if (is_array($listener)) { + return (is_string($listener[0]) ? $listener[0] : get_class($listener[0]) ).'::'.$listener[1]; + } + return 'unknown'; + } + + public function getNotTriggeredListeners(): array + { + $result = []; + + foreach ($this->getListeners() as $event => $listeners) { + foreach ($listeners as $listener) { + foreach ($this->storage[$event] ?? [] as $calls) { + foreach ($calls as $call) { + if ($call['listener'] == $listener[0]) { + continue 3; + } + } + } + $listenerName = $this->findNameForListener($listener[0]); + $result[$event][$listenerName] = $listenerName; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function initializeListeners(string $eventName, string $loweredClass, string $format): array + { + $listeners = parent::initializeListeners($eventName, $loweredClass, $format); + $this->storage = []; + foreach ($listeners as &$listener) { + $listener[0] = $f = function (...$args) use ($listener, &$f) { + $t = microtime(true); + call_user_func_array($listener[0], $args); + + // $args = [$event, $eventName, $class, $format, $dispatcher] + // $listener = [$callable, $class, $format, $interface] + $this->storage[$args[1]][$args[2]][] = [ + 'listener' => $listener[0], + 'format' => $args[3], + 'type' => $args[0]->getType(), + 'duration' => microtime(true) - $t + ]; + }; + } + + return $listeners; + } + + private function calculateTotalDuration(array $calls): float + { + return array_sum(array_column($calls, 'duration')); + } +} diff --git a/Debug/TraceableHandlerRegistry.php b/Debug/TraceableHandlerRegistry.php new file mode 100644 index 00000000..c36e0094 --- /dev/null +++ b/Debug/TraceableHandlerRegistry.php @@ -0,0 +1,100 @@ +registry = $registry; + } + + public function registerSubscribingHandler(SubscribingHandlerInterface $handler): void + { + $this->registerSubscribingHandler($handler); + } + + public function registerHandler(int $direction, string $typeName, string $format, $handler): void + { + $this->registry->registerHandler($direction, $typeName, $format, $handler); + } + + public function getHandler(int $direction, string $typeName, string $format) + { + $handler = $this->registry->getHandler($direction, $typeName, $format); + if ($handler=== null) { + return null; + } + return function (...$args) use($handler, $direction, $typeName, $format) { + try{ + $t = microtime(true); + return call_user_func_array($handler, $args); + } finally { + $this->storage[$direction][$typeName][]= [ + 'handler' => $handler, + 'format' => $format, + 'duration' => microtime(true) - $t + ]; + } + + }; + } + + private function findNameForListener($listener): string + { + if (is_array($listener)) { + return (is_string($listener[0]) ? $listener[0] : get_class($listener[0]) ).'::'.$listener[1]; + } + return 'unknown'; + } + + public function getTriggeredHandlers(): array + { + $result = []; + + foreach ($this->storage as $direction => $handlersByType) { + foreach ($handlersByType as $type => $calls) { + foreach ($calls as $call) { + $handlerName = $this->findNameForListener($call['handler']); + if (!isset($result[$direction][$type][$handlerName])){ + $result[$direction][$type][$handlerName] = [ + 'calls' => 0, + 'duration' => 0, + ]; + } + $result[$direction][$type][$handlerName] = [ + 'handler' => $handlerName, + 'calls' => $result[$direction][$type][$handlerName]['calls']+1, + 'duration' => $result[$direction][$type][$handlerName]['duration']+$call['duration'], + ]; + } + } + } + + return $result; + } + + public function getNotTriggeredHandlers(): array + { + return []; + $result = []; + + foreach ($this->getTraceableHandlers() as $info) { + if (!$info['calls']) { + $result[$info['direction']][] = $info; + } + } + + return $result; + } + +} diff --git a/DependencyInjection/Compiler/CustomHandlersPass.php b/DependencyInjection/Compiler/CustomHandlersPass.php index bf645a12..9481f217 100644 --- a/DependencyInjection/Compiler/CustomHandlersPass.php +++ b/DependencyInjection/Compiler/CustomHandlersPass.php @@ -6,6 +6,7 @@ use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\Handler\HandlerRegistry; +use JMS\SerializerBundle\Debug\Handler\TraceableHandler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -80,14 +81,21 @@ public function process(ContainerBuilder $container) } } - $handlers = $this->sortAndFlattenHandlersList($handlers); + $handlersByDirection = $this->sortAndFlattenHandlersList($handlers); - $container->findDefinition('jms_serializer.handler_registry') - ->addArgument($handlers); + $hdef = $container->findDefinition('jms_serializer.handler_registry'); + + foreach ($handlersByDirection as $direction => $handlersByType) { + foreach ($handlersByType as $type => $handlersByFormat) { + foreach ($handlersByFormat as $format => $handlerCallable) { + $hdef->addMethodCall('registerHandler', [$direction, $type, $format, $handlerCallable]); + } + } + } if (class_exists(ServiceLocatorTagPass::class)) { $serviceLocator = ServiceLocatorTagPass::register($container, $handlerServices); - $container->findDefinition('jms_serializer.handler_registry')->replaceArgument(0, $serviceLocator); + $hdef->replaceArgument(0, $serviceLocator); } } diff --git a/DependencyInjection/Compiler/RegisterEventListenersAndSubscribersPass.php b/DependencyInjection/Compiler/RegisterEventListenersAndSubscribersPass.php index 048d0bac..af312915 100644 --- a/DependencyInjection/Compiler/RegisterEventListenersAndSubscribersPass.php +++ b/DependencyInjection/Compiler/RegisterEventListenersAndSubscribersPass.php @@ -5,6 +5,7 @@ namespace JMS\SerializerBundle\DependencyInjection\Compiler; use JMS\Serializer\EventDispatcher\EventDispatcher; +use JMS\Serializer\EventDispatcher\LazyEventDispatcher; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -16,6 +17,8 @@ public function process(ContainerBuilder $container) { $listeners = []; $listenerServices = []; + $dispatcherDef = $container->findDefinition('jms_serializer.event_dispatcher'); + foreach ($container->findTaggedServiceIds('jms_serializer.event_listener') as $id => $tags) { foreach ($tags as $attributes) { if (!isset($attributes['event'])) { @@ -30,12 +33,8 @@ public function process(ContainerBuilder $container) $method = $attributes['method'] ?? EventDispatcher::getDefaultMethodName($attributes['event']); $priority = isset($attributes['priority']) ? (int) $attributes['priority'] : 0; - if (class_exists(ServiceLocatorTagPass::class) || $container->getDefinition($id)->isPublic()) { - $listenerServices[$id] = new Reference($id); - $listeners[$attributes['event']][$priority][] = [[$id, $method], $class, $format]; - } else { - $listeners[$attributes['event']][$priority][] = [[new Reference($id), $method], $class, $format]; - } + + $listeners[$attributes['event']][$priority][] = [[new Reference($id), $method], $class, $format]; } } @@ -59,15 +58,12 @@ public function process(ContainerBuilder $container) $priority = isset($eventData['priority']) ? (int) $eventData['priority'] : 0; $interface = $eventData['interface'] ?? null; - if (class_exists(ServiceLocatorTagPass::class) || $container->getDefinition($id)->isPublic()) { - $listenerServices[$id] = new Reference($id); - $listeners[$eventData['event']][$priority][] = [[$id, $method], $class, $format, $interface]; - } else { - $listeners[$eventData['event']][$priority][] = [[new Reference($id), $method], $class, $format, $interface]; - } + $listeners[$eventData['event']][$priority][] = [[new Reference($id), $method], $class, $format, $interface]; + } } + $listenerServices = []; if ($listeners) { array_walk($listeners, static function (&$value, $key) { ksort($value); @@ -77,13 +73,22 @@ public function process(ContainerBuilder $container) $events = call_user_func_array('array_merge', $events); } - $container->findDefinition('jms_serializer.event_dispatcher') - ->addMethodCall('setListeners', [$listeners]); + + foreach ($listeners as $event => $listenersPerEvent) { + foreach ($listenersPerEvent as $singleListener) { + $id = (string)$singleListener[0][0]; + if (is_a($dispatcherDef->getClass(), LazyEventDispatcher::class, true) && (class_exists(ServiceLocatorTagPass::class) || $container->getDefinition($id)->isPublic())) { + $listenerServices[$id] = new Reference($id); + $singleListener[0][0] = $id; + } + $dispatcherDef->addMethodCall('addListener', array_merge([$event], $singleListener)); + } + } } - if (class_exists(ServiceLocatorTagPass::class)) { + if (class_exists(ServiceLocatorTagPass::class) && $listenerServices) { $serviceLocator = ServiceLocatorTagPass::register($container, $listenerServices); - $container->getDefinition('jms_serializer.event_dispatcher')->replaceArgument(0, $serviceLocator); + $dispatcherDef->replaceArgument(0, $serviceLocator); } } } diff --git a/DependencyInjection/JMSSerializerExtension.php b/DependencyInjection/JMSSerializerExtension.php index d3855b94..f994a6f8 100644 --- a/DependencyInjection/JMSSerializerExtension.php +++ b/DependencyInjection/JMSSerializerExtension.php @@ -34,6 +34,10 @@ public function loadInternal(array $config, ContainerBuilder $container) $loader = new XmlFileLoader($container, new FileLocator([__DIR__ . '/../Resources/config/'])); $loader->load('services.xml'); + if (true) { + $loader->load('debug.xml'); + } + // Built-in handlers. $container->getDefinition('jms_serializer.datetime_handler') ->addArgument($config['handlers']['datetime']['default_format']) @@ -113,8 +117,7 @@ public function loadInternal(array $config, ContainerBuilder $container) if ($config['metadata']['infer_types_from_doc_block'] && class_exists(DocBlockDriver::class)) { $container->getDefinition('jms_serializer.metadata.doc_block_driver') ->setDecoratedService('jms_serializer.metadata_driver') - ->setPublic(false) - ; + ->setPublic(false); } if (PHP_VERSION_ID >= 70400 && class_exists(TypedPropertiesDriver::class)) { diff --git a/JMSSerializerBundle.php b/JMSSerializerBundle.php index 9f3aac29..80aed1e9 100644 --- a/JMSSerializerBundle.php +++ b/JMSSerializerBundle.php @@ -30,12 +30,12 @@ function (ContainerBuilder $container, $def) { } )); - $builder->addCompilerPass(new FormErrorHandlerTranslationDomainPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); - $builder->addCompilerPass(new TwigExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); - $builder->addCompilerPass(new ExpressionFunctionProviderPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); - $builder->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_REMOVING); - $builder->addCompilerPass(new CustomHandlersPass(), PassConfig::TYPE_BEFORE_REMOVING); - $builder->addCompilerPass(new DoctrinePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); + $builder->addCompilerPass(new FormErrorHandlerTranslationDomainPass()); + $builder->addCompilerPass(new TwigExtensionPass()); + $builder->addCompilerPass(new ExpressionFunctionProviderPass()); + $builder->addCompilerPass(new RegisterEventListenersAndSubscribersPass()); + $builder->addCompilerPass(new CustomHandlersPass()); + $builder->addCompilerPass(new DoctrinePass()); } private function getServiceMapPass($tagName, $keyAttributeName, $callable) diff --git a/Resources/config/debug.xml b/Resources/config/debug.xml new file mode 100644 index 00000000..1463df1e --- /dev/null +++ b/Resources/config/debug.xml @@ -0,0 +1,55 @@ + + + + + + + JMS\SerializerBundle\Debug\TraceableEventDispatcher + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 8a79d053..42284761 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -4,9 +4,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + JMS\Serializer\EventDispatcher\LazyEventDispatcher + + - + diff --git a/Resources/views/Collector/events.html.twig b/Resources/views/Collector/events.html.twig new file mode 100644 index 00000000..3b4a18e1 --- /dev/null +++ b/Resources/views/Collector/events.html.twig @@ -0,0 +1,75 @@ +{%- import _self as helper -%} + +

Event Dispatcher

+ +
+
+
Triggered Listeners {{ collector.triggeredListeners|length }}
+ +
+ {%- if 0 == collector.triggeredListeners|length -%} +
+

No triggered listeners.

+
+ {%- else -%} + {{- helper.render_table(collector.triggeredListeners) -}} + {%- endif -%} +
+
+ +
+
Not Called Listeners {{ collector.notTriggeredListeners|length }}
+
+ {{ helper.render_table_not_triggered_listeners(collector.notTriggeredListeners) }} +
+
+
+ +{%- macro render_table_not_triggered_listeners(notCalledListenersPerEvent) -%} + + {%- for eventName, listeners in notCalledListenersPerEvent -%} +

{{ eventName }}

+ + + + + + + {%- for listener in listeners -%} + + + + {%- endfor -%} +
Listener
{{ dump(listener) }}
+ {%- endfor -%} +{%- endmacro -%} + + +{%- macro render_table(listeners) -%} + + {%- for eventName, callsPerlistener in listeners -%} +

{{ eventName }}

+ + + + + + + + + {%- for listener, callsPerClass in callsPerlistener -%} + + + + {%- for className, callsInfo in callsPerClass -%} + + + + + + + {%- endfor -%} + {%- endfor -%} +
ClassCallsTotal duration (ms)
{{ dump(listener) }}
 {{ className }}{{ callsInfo.calls }}{{ callsInfo.duration ? (callsInfo.duration * 1000)|number_format(4) : '' }}
+ {%- endfor -%} +{%- endmacro -%} diff --git a/Resources/views/Collector/handlers.html.twig b/Resources/views/Collector/handlers.html.twig new file mode 100644 index 00000000..74763f40 --- /dev/null +++ b/Resources/views/Collector/handlers.html.twig @@ -0,0 +1,63 @@ +{%- import _self as helper -%} + +

Type Handlers

+ +
+
+

Triggered Handlers {{ collector.triggeredHandlers|length }}

+ +
+ {%- if 0 == collector.triggeredHandlers|length -%} +
+

No triggered handlers.

+
+ {%- else -%} + {{- helper.render_table_triggered_handlers(collector.triggeredHandlers, true) -}} + {%- endif -%} +
+
+ +
+
Not Triggered Handlers {{ collector.notTriggeredHandlers|length }}
+
+ {{ helper.render_table_triggered_handlers(collector.notTriggeredHandlers, false) }} +
+
+
+ +{%- macro render_table_triggered_handlers(handlers, called) -%} + {%- for direction, callsByType in handlers -%} +

+ {%- if direction == constant('JMS\\Serializer\\GraphNavigatorInterface::DIRECTION_SERIALIZATION') -%} + Serialization + {%- else -%} + Deserialization + {%- endif -%} +

+ + + + + + {%- if called|default(false) -%} + + + {%- endif -%} + + + {%- for type, calls in callsByType -%} + {%- for call in calls -%} + + + + {%- if called|default(false) -%} + + + {%- endif -%} + + {%- endfor -%} + {%- endfor -%} + +
Date typeHandlerCallsTotal duration (ms)
{{ type }}{{ call.handler }}{{ call.calls }}{{ call.duration ? (call.duration * 1000)|number_format(4) : '' }}
+ {%- endfor -%} +{%- endmacro -%} diff --git a/Resources/views/Collector/metadata.html.twig b/Resources/views/Collector/metadata.html.twig new file mode 100644 index 00000000..6bc7f4c4 --- /dev/null +++ b/Resources/views/Collector/metadata.html.twig @@ -0,0 +1,33 @@ +

Loaded metadata

+ +{%- if collector.loadedMetadata is empty -%} +
+

No metadata have been loaded.

+
+{%- else -%} +{#
#} +{#
#} +{# Runs#} +{# {{ collector.loadedMetadata|length }}#} +{#
#} +{#
#} + + + + + + + + + {%- for class, files in collector.loadedMetadata -%} + + + + + {%- endfor -%} +
ClassFile
{{ class }} + {%- for file in files -%} + {{ file }}
+ {% endfor %} +
+{%- endif -%} diff --git a/Resources/views/Collector/panel.html.twig b/Resources/views/Collector/panel.html.twig new file mode 100644 index 00000000..ce494357 --- /dev/null +++ b/Resources/views/Collector/panel.html.twig @@ -0,0 +1,121 @@ +{%- extends '@WebProfiler/Profiler/layout.html.twig' -%} + +{%- block toolbar -%} + + {%- set icon -%} + {{- include('@JMSSerializer/icon.svg') }} + E:{{- collector.triggeredListeners|length }} + H:{{- collector.triggeredHandlers|length }} + M:{{- collector.loadedMetadata|length }} + {%- endset -%} + + {%- set text -%} +
+ Events + {{ collector.triggeredListeners|length }} +
+
+ Handlers + {{ collector.triggeredHandlers|length }} +
+
+ Metadata + {{ collector.loadedMetadata|length }} +
+ {%- endset -%} + + {%- include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } -%} +{%- endblock -%} + +{%- block head -%} + + + + {{ parent() }} +{%- endblock -%} + +{%- block menu -%} + {# This left-hand menu appears when using the full-screen profiler. #} + {% set total = collector.triggeredListeners|length + collector.triggeredHandlers|length + collector.loadedMetadata|length %} + + + {{ include('@JMSSerializer/icon.svg') }} + + JMS Serializer + {%- if collector.loadedMetadata|length > 0 -%} + + {{ collector.loadedMetadata|length }} + + {%- endif -%} + +{%- endblock -%} + +{%- block panel -%} +

JMS Serializer

+ +
+ +
+

+ Events + {{ collector.triggeredListeners|length }} +

+ +
+

Triggered event listeners

+ +
+ {%- include '@JMSSerializer/Collector/events.html.twig' -%} +
+
+
+ +
+

+ Handlers + {{ collector.triggeredHandlers|length }} +

+ +
+

Triggered event handlers

+ +
+ {%- include '@JMSSerializer/Collector/handlers.html.twig' -%} +
+
+
+ +
+

+ Metadata + + {{- collector.loadedMetadata|length -}} + +

+ +
+

Loaded metadata

+ +
+ {%- include '@JMSSerializer/Collector/metadata.html.twig' -%} +
+
+
+
+{%- endblock -%} diff --git a/Resources/views/Collector/script/jms.js.twig b/Resources/views/Collector/script/jms.js.twig new file mode 100644 index 00000000..4c3d9b2f --- /dev/null +++ b/Resources/views/Collector/script/jms.js.twig @@ -0,0 +1,27 @@ +/** + * Toggle visibility on elements. + */ +document.addEventListener("DOMContentLoaded", function() { + Array.prototype.forEach.call(document.getElementsByClassName('jms-toggle'), function (source) { + source.addEventListener('click', function() { + Array.prototype.forEach.call(document.querySelectorAll(source.getAttribute('data-toggle')), function (target) { + target.classList.toggle('jms-hidden'); + }); + }); + }); +}); + +/** + * Copy as cURL. + */ +document.addEventListener("DOMContentLoaded", function () { + Array.prototype.forEach.call(document.getElementsByClassName('jms-toolbar'), function (toolbar) { + var button = toolbar.querySelector('.jms-copy-as-curl>button'); + button.addEventListener('click', function() { + var input = toolbar.querySelector('.jms-copy-as-curl>input'); + input.select(); + document.execCommand('copy'); + input.setSelectionRange(0, 0); + }); + }); +}) diff --git a/Resources/views/Collector/style/jms.css.twig b/Resources/views/Collector/style/jms.css.twig new file mode 100644 index 00000000..66ce777c --- /dev/null +++ b/Resources/views/Collector/style/jms.css.twig @@ -0,0 +1,182 @@ +.jms-push-right { + float: right; +} + +.jms-plugin-name { + font-size: 130%; + font-weight: bold; + text-align: center; +} + +.jms-hidden { + display: none; +} + +.jms-toggle { + cursor: pointer; +} + +.jms-center { + text-align: center; +} + +/** + * Toolbar + */ +.jms-toolbar { + display: flex; + justify-content: space-between; +} + +.jms-toolbar>*:not(:last-child) { + margin-right:5px; +} + +.jms-toolbar .jms-copy-as-curl { + flex: 1; +} + +.jms-copy-as-curl { + font-size: 0; /*hide line return spacings*/ + display: flex; +} + +.jms-copy-as-curl>input { + padding: .5em .75em; + border-radius: 2px 0px 0px 2px; + border: 0; + line-height: inherit; + background-color: #eee; + opacity: 1; + font-size: 14px; + flex: 1; +} + +.jms-copy-as-curl>button { + font-size: 14px; + border-radius: 0px 2px 2px 0px; +} + +/** + * Message + */ +.jms-message { + box-sizing: border-box; + padding: 5px; + flex: 1; + margin: 5px; + overflow-x: auto; + white-space: nowrap; +} + +.jms-messages { + clear: both; + display: flex; +} + +/** + * Stack + */ +.jms-stack>.jms-stack { + margin-left: 2.5em; +} + +/** + * Stack header + */ +.jms-stack-header { + display: flex; + justify-content: space-between; + + background: #FFF; + border: 1px solid #E0E0E0; + box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); + margin: 1em 0; + padding: 10px; +} + +.jms-stack-failed { + color:#B0413E; +} + +.jms-stack-success { + color: #4F805D; +} + +.jms-stack-header .jms-stack-header-target { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + background: white; + color: black; + font-size: 0; /*hide line return spacings*/ +} + +.jms-scheme-http { + display: none; +} + +.jms-scheme-https { + color: green; +} + +.jms-target, .jms-scheme { + font-weight: normal; +} + +.jms-target, .jms-host, .jms-scheme { + font-size: 12px; +} + +.jms-duration { + min-width: 6ch; + text-align:center; +} + +/** + * HTTP method colors from swagger-ui. + */ +.jms-method.label { + color: black; + width: 9ch; + text-align: center; +} + +.jms-method-post.label { + background: #49cc90; +} + +.jms-method-get.label { + background: #61affe; +} + +.jms-method-put.label { + background: #fca130; +} + +.jms-method-delete.label { + background: #f93e3e; +} + +.jms-method-head.label { + background: #9012fe; + color: white; +} + +.jms-method-patch.label { + background: #50e3c2; +} + +.jms-method-options.label { + background: #0d5aa7; + color: white; +} + +.jms-method-connect.label { + background: #ebebeb; +} + +.jms-method-trace.label { + background: #ebebeb; +} diff --git a/Resources/views/icon.svg b/Resources/views/icon.svg new file mode 100644 index 00000000..c0d4328c --- /dev/null +++ b/Resources/views/icon.svg @@ -0,0 +1,14 @@ + + + + +