Repository: pimcore/studio-backend-bundle
Pull request #1070: [Mercure]: Use custom URL service for Mercure
Author: login: lukmzig
Created: 2025-05-09T08:53:38Z | Merged: 2025-05-12T08:12:01Z

## Changes in this pull request
Part of pimcore/studio-ui-bundle#1479
Resolves pimcore/studio-backend-bundle#1069

## Additional info
- add possibility to make url host dynamic
- add possibility to make SameSite cookie attribute configurable


Diff:
diff --git a/config/mercure.yaml b/config/mercure.yaml
index 6497c93e2..47965eb4c 100644
--- a/config/mercure.yaml
+++ b/config/mercure.yaml
@@ -11,24 +11,27 @@ services:
         public: true
         tags: [ 'controller.service_arguments' ]
 
+    Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ServerTokenService: ~
+
+    Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ClientTokenService: ~
+
     Pimcore\Bundle\StudioBackendBundle\Mercure\Service\HubServiceInterface:
         class: Pimcore\Bundle\StudioBackendBundle\Mercure\Service\HubService
         arguments:
-            Symfony\Component\Mercure\HubInterface: '@mercure.hub.studio_client'
+            $tokenProvider: '@Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ClientTokenService'
+            $cookieLifetime: '%pimcore_studio_backend.mercure_settings.cookie_lifetime%'
+            $cookieSameSite: '%pimcore_studio_backend.mercure_settings.cookie_same_site%'
 
     Pimcore\Bundle\StudioBackendBundle\Mercure\Service\Loader\TopicLoaderInterface:
         class: Pimcore\Bundle\StudioBackendBundle\Mercure\Service\Loader\TaggedIteratorAdapter
 
-    Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ServerTokenService: ~
-
-    Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ClientTokenService: ~
-
     Pimcore\Bundle\StudioBackendBundle\Mercure\Service\PublishServiceInterface:
         class: Pimcore\Bundle\StudioBackendBundle\Mercure\Service\PublishService
-
-    Pimcore\Bundle\StudioBackendBundle\Mercure\Service\PublishService:
         arguments:
-            Symfony\Component\Mercure\HubInterface: '@mercure.hub.studio_server'
+            $tokenProvider: '@Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ServerTokenService'
+
+    Pimcore\Bundle\StudioBackendBundle\Mercure\Service\UrlServiceInterface:
+        class: Pimcore\Bundle\StudioBackendBundle\Mercure\Service\UrlService
 
     Symfony\Component\Mercure\Jwt\TokenFactoryInterface:
         class: Symfony\Component\Mercure\Jwt\LcobucciFactory
diff --git a/config/pimcore/config.yaml b/config/pimcore/config.yaml
index 3f7e22e8a..3971f845a 100644
--- a/config/pimcore/config.yaml
+++ b/config/pimcore/config.yaml
@@ -207,16 +207,4 @@ pimcore_studio_backend:
             - "fieldcollections"
 
     widget_types:
-        - 'element_tree'
-
-mercure:
-    hubs:
-        studio_server:
-            url: '%pimcore_studio_backend.mercure_settings.hub_url_server%'
-            jwt:
-                provider: 'Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ServerTokenService'
-
-        studio_client:
-            url: '%pimcore_studio_backend.mercure_settings.hub_url_client%'
-            jwt:
-                provider: 'Pimcore\Bundle\StudioBackendBundle\Mercure\Service\ClientTokenService'
\ No newline at end of file
+        - 'element_tree'
\ No newline at end of file
diff --git a/doc/00_Installation.md b/doc/00_Installation.md
index aec27071d..7bb2fe13d 100644
--- a/doc/00_Installation.md
+++ b/doc/00_Installation.md
@@ -95,22 +95,40 @@ Also make sure that the keys configured in pimcore match the keys in the (docker
 
 ### Configuration
 
+It is necessary to configure a JWT key for Mercure. This key is used to sign and verify the JWT tokens that are used for authentication.
+
+#### Optional Mercure configuration
+
 URLs for accessing Mercure server-side (for updating state information within application
-services) and client-side (for getting updates in Pimcore Studio UI) need to be configured via symfony configuration
-tree as follows:
+services) and client-side (for getting updates in Pimcore Studio UI) can be configured via a symfony configuration.
 
-Additionally, you can configure the cookie lifetime for the JWT token in seconds. The default value is 3600 seconds.
+Additionally, you can configure the cookie parameters like:
+- lifetime for the JWT token in seconds (default is 3600 seconds)
+- SameSite attribute (default is `Strict`, possible values are `Lax`, `Strict` or `None`)
 
 ```yaml
 pimcore_studio_backend:
     mercure_settings:
         jwt_key: '<your-256-bit-secret-min-32-chars>'
+        # Optional configuration
+
+        # The url to the mercure hub for the (frontend) client.
+        # If it is not set, the default will be set to "http(s)://<PIMCORE_HOST>/hub/.well-known/mercure".
+        # It is possible to use "<PIMCORE_SCHEMA_HOST>" as a placeholder for the current schema and host.
         hub_url_client: 'https://your-app-domain.com/hub'
+
+        # The url to the mercure hub for the server. 
+        # This can also be the docker container name (e.g., http://mercure/.well-known/mercure).
+        # If it is not set, the default will be set to "http(s)://<PIMCORE_HOST>/hub/.well-known/mercure".
         hub_url_server: 'http://mercure/.well-known/mercure'
-        # Optional configuration
         cookie_lifetime: 3600
+        cookie_same_site: 'Strict'
 ```
 
+You need to configure the full URL including protocol, port and path to Mercure here.
+For client side url it is also possible to use `<PIMCORE_SCHEMA_HOST>` for the current schema and host of client. 
+If these settings are not set, URLs will be generated based on Pimcore host and default paths.
+
 ## Changing the prefix of the Studio Backend
 It is possible to change the route where you can reach the API. By default, the route is `/pimcore-studio/api/`.  
 If you want to change the prefix, you can do so by changing the configuration like the following:
diff --git a/doc/02_Mercure_Setup.md b/doc/02_Mercure_Setup.md
index e7e3822be..3271d0c5d 100644
--- a/doc/02_Mercure_Setup.md
+++ b/doc/02_Mercure_Setup.md
@@ -87,6 +87,12 @@ pimcore_studio_backend:
         hub_url_server: 'http://mercure/.well-known/mercure'
 ```
 
+If you would like to retrieve the URLs from within the application, you can use the following methods:
+- `getClientSideUrl()` to get the client URL
+- `getServerSideUrl()` to get the server URL
+
+These methods are available in the `Pimcore\Bundle\StudioBackendBundle\Mercure\Service\UrlServiceInterface` class interface.
+
 ## Check if Mercure is running
 To see if Mercure is up and available, call ``https://your-app-domain.com/hub`` (or `client_side_url`
 you configured in configuration). This request should return the text
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 6f69d032b..9c8e19727 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -24,6 +24,7 @@
 use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
 use Symfony\Component\Config\Definition\Builder\TreeBuilder;
 use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\HttpFoundation\Cookie;
 use function is_array;
 use function is_int;
 use function is_null;
@@ -47,6 +48,12 @@ class Configuration implements ConfigurationInterface
 
     private const string PERMISSION_ARRAY_VALUE_ERROR = 'Each permission value must be a boolean.';
 
+    private const array ALLOWED_COOKIE_SAME_SITE_VALUES = [
+        Cookie::SAMESITE_LAX,
+        Cookie::SAMESITE_NONE,
+        Cookie::SAMESITE_STRICT,
+    ];
+
     /**
      * {@inheritdoc}
      */
@@ -273,15 +280,20 @@ private function addMercureConfiguration(ArrayNodeDefinition $node): void
                 ->children()
                     ->scalarNode('hub_url_server')
                         ->info(
-                            'The url to the mercure hub for the server. This can also be the docker container name.'.
-                            ' (e.g. http://mercure/.well-known/mercure)'
+                            'The url to the mercure hub for the server.' .
+                            'This can also be the docker container name '.
+                            '(e.g., http://mercure/.well-known/mercure). ' .
+                            'If it is not set, default will be set to ' .
+                            '"http(s)://<PIMCORE_HOST>/hub/.well-known/mercure".'
                         )
-                        ->defaultValue('http://mercure/.well-known/mercure')
+                        ->defaultNull()
                     ->end()
                     ->scalarNode('hub_url_client')
-                        ->info(
-                            'The url to the mercure hub for the (frontend) client.'.
-                            ' If not set, default will be set to "http(s)://<PIMCORE_HOST>/hub".'
+                        ->info('The url to the mercure hub for the (frontend) client. ' .
+                            'If it is not set, the default will be set to ' .
+                            '"http(s)://<PIMCORE_HOST>/hub/.well-known/mercure". ' .
+                            'It is possible to use "<PIMCORE_SCHEMA_HOST>" ' .
+                            'as a placeholder for the current schema and host.'
                         )
                         ->defaultNull()
                     ->end()
@@ -294,6 +306,15 @@ private function addMercureConfiguration(ArrayNodeDefinition $node): void
                         ->info('Lifetime of the mercure cookie in seconds. Default is one hour.')
                         ->defaultValue(3600)
                     ->end()
+                     ->enumNode('cookie_same_site')
+                        ->info('Same site setting for the mercure cookie. Default is "' .
+                            Cookie::SAMESITE_STRICT .'". ' .
+                            'Possible values are: ' .
+                            implode(',', self::ALLOWED_COOKIE_SAME_SITE_VALUES) .'".'
+                        )
+                        ->values(self::ALLOWED_COOKIE_SAME_SITE_VALUES)
+                        ->defaultValue(Cookie::SAMESITE_STRICT)
+                    ->end()
                 ->end()
             ->end();
     }
diff --git a/src/DependencyInjection/PimcoreStudioBackendExtension.php b/src/DependencyInjection/PimcoreStudioBackendExtension.php
index 12b3b4533..44985f6d8 100644
--- a/src/DependencyInjection/PimcoreStudioBackendExtension.php
+++ b/src/DependencyInjection/PimcoreStudioBackendExtension.php
@@ -27,7 +27,7 @@
 use Pimcore\Bundle\StudioBackendBundle\Export\Service\XlsxExportService;
 use Pimcore\Bundle\StudioBackendBundle\Grid\Column\Collector\DataObject\AdvancedColumnCollector;
 use Pimcore\Bundle\StudioBackendBundle\Grid\Service\ConfigurationServiceInterface;
-use Pimcore\Bundle\StudioBackendBundle\Mercure\Service\HubServiceInterface;
+use Pimcore\Bundle\StudioBackendBundle\Mercure\Service\UrlServiceInterface;
 use Pimcore\Bundle\StudioBackendBundle\Metadata\Service\DataAdapterServiceInterface as MetadataAdapterServiceInterface;
 use Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteServiceInterface;
 use Pimcore\Bundle\StudioBackendBundle\OpenApi\Service\OpenApiServiceInterface;
@@ -36,7 +36,6 @@
 use Pimcore\Bundle\StudioBackendBundle\Perspective\Service\WidgetServiceInterface;
 use Pimcore\Bundle\StudioBackendBundle\Perspective\Service\WidgetValidationServiceInterface;
 use Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingServiceInterface;
-use Pimcore\Tool;
 use Symfony\Component\Config\FileLocator;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@@ -97,9 +96,6 @@ public function load(array $configs, ContainerBuilder $container): void
         $definition = $container->getDefinition(ElementDeleteServiceInterface::class);
         $definition->setArgument('$recycleBinThreshold', $config['element_recycle_bin_threshold']);
 
-        $definition = $container->getDefinition(HubServiceInterface::class);
-        $definition->setArgument('$cookieLifetime', $config['mercure_settings']['cookie_lifetime']);
-
         $definition = $container->getDefinition(ZipServiceInterface::class);
         $definition->setArgument('$downloadLimits', $config['asset_download_settings']);
 
@@ -168,6 +164,12 @@ public function load(array $configs, ContainerBuilder $container): void
 
         $definition = $container->getDefinition(MetadataAdapterServiceInterface::class);
         $definition->setArgument('$studioAdapters', $config['asset_metadata_adapter_mapping']);
+
+        $definition = $container->getDefinition(UrlServiceInterface::class);
+        $definition->setArguments([
+            '$serverSideUrl' => $config['mercure_settings']['hub_url_server'],
+            '$clientSideUrl' => $config['mercure_settings']['hub_url_client'],
+        ]);
     }
 
     /**
@@ -198,10 +200,6 @@ public function prepend(ContainerBuilder $container): void
                 continue;
             }
 
-            if ($key === 'hub_url_client' && $containerConfig['mercure_settings'][$key] === null) {
-                $containerConfig['mercure_settings'][$key] = Tool::getHostUrl() . '/hub';
-            }
-
             $container->setParameter(
                 'pimcore_studio_backend.mercure_settings.' . $key,
                 $containerConfig['mercure_settings'][$key]
diff --git a/src/Mercure/Service/HubService.php b/src/Mercure/Service/HubService.php
index d4b55bada..8f43cc89e 100644
--- a/src/Mercure/Service/HubService.php
+++ b/src/Mercure/Service/HubService.php
@@ -15,7 +15,7 @@
 
 use Pimcore\Bundle\StudioBackendBundle\Mercure\Util\Constant\Mercure;
 use Symfony\Component\HttpFoundation\Cookie;
-use Symfony\Component\Mercure\HubInterface;
+use Symfony\Component\Mercure\Jwt\TokenProviderInterface;
 
 /**
  * @internal
@@ -23,25 +23,27 @@
 final readonly class HubService implements HubServiceInterface
 {
     public function __construct(
-        private HubInterface $clientHub,
-        private int $cookieLifetime = 3600
+        private TokenProviderInterface $tokenProvider,
+        private UrlServiceInterface $urlService,
+        private int $cookieLifetime = 3600,
+        private string $cookieSameSite = Cookie::SAMESITE_STRICT,
     ) {
     }
 
     public function createCookie(): Cookie
     {
-        $urlParts = parse_url($this->clientHub->getPublicUrl());
+        $urlParts = parse_url($this->urlService->getClientSideUrl());
 
         return new Cookie(
             Mercure::AUTHORIZATION_COOKIE_NAME->value,
-            $this->clientHub->getProvider()->getJwt(),
+            $this->tokenProvider->getJwt(),
             time() + $this->cookieLifetime,
             $urlParts[Mercure::URL_PATH->value] ?? '/',
             $urlParts[Mercure::URL_HOST->value] ?? '',
             $urlParts[Mercure::URL_SCHEME->value] === Mercure::URL_SCHEME_HTTPS->value,
             true,
             false,
-            Cookie::SAMESITE_STRICT
+            $this->cookieSameSite
         );
     }
 }
diff --git a/src/Mercure/Service/PublishService.php b/src/Mercure/Service/PublishService.php
index 1acb788b6..5286f687e 100644
--- a/src/Mercure/Service/PublishService.php
+++ b/src/Mercure/Service/PublishService.php
@@ -14,18 +14,23 @@
 namespace Pimcore\Bundle\StudioBackendBundle\Mercure\Service;
 
 use Psr\Log\LoggerInterface;
-use Symfony\Component\Mercure\HubInterface;
+use Symfony\Component\Mercure\Hub;
+use Symfony\Component\Mercure\Jwt\StaticTokenProvider;
+use Symfony\Component\Mercure\Jwt\TokenProviderInterface;
 use Symfony\Component\Mercure\Update;
 use Symfony\Component\Serializer\SerializerInterface;
 use function is_string;
 use function sprintf;
 
-final readonly class PublishService implements PublishServiceInterface
+final class PublishService implements PublishServiceInterface
 {
+    private ?Hub $publisher = null;
+
     public function __construct(
-        private HubInterface $serverHub,
-        private LoggerInterface $logger,
-        private SerializerInterface $serializer
+        private readonly LoggerInterface $logger,
+        private readonly SerializerInterface $serializer,
+        private readonly TokenProviderInterface $tokenProvider,
+        private readonly UrlServiceInterface $urlService,
     ) {
     }
 
@@ -58,11 +63,23 @@ public function publish(
             )
         );
 
-        $this->serverHub->publish(new Update($topics, $jsonData, $private, $id, $type, $retry));
+        $this->getServerHub()->publish(new Update($topics, $jsonData, $private, $id, $type, $retry));
     }
 
     public function getJsonData(mixed $data): string
     {
         return $this->serializer->serialize($data, 'json');
     }
+
+    private function getServerHub(): Hub
+    {
+        if ($this->publisher === null) {
+            $this->publisher = new Hub(
+                $this->urlService->getServerSideUrl(),
+                new StaticTokenProvider($this->tokenProvider->getJwt()),
+            );
+        }
+
+        return $this->publisher;
+    }
 }
diff --git a/src/Mercure/Service/ServerTokenService.php b/src/Mercure/Service/ServerTokenService.php
index 56d4e131a..b87b4a97a 100644
--- a/src/Mercure/Service/ServerTokenService.php
+++ b/src/Mercure/Service/ServerTokenService.php
@@ -29,11 +29,6 @@ public function __construct(
     ) {
     }
 
-    private function getTopicCollection(): TopicCollection
-    {
-        return $this->topicLoader->loadTopics();
-    }
-
     public function getJwt(): string
     {
         return $this->tokenFactory->create(
@@ -41,4 +36,9 @@ public function getJwt(): string
             $this->getTopicCollection()->getServerPublishableTopics(),
         );
     }
+
+    private function getTopicCollection(): TopicCollection
+    {
+        return $this->topicLoader->loadTopics();
+    }
 }
diff --git a/src/Mercure/Service/UrlService.php b/src/Mercure/Service/UrlService.php
new file mode 100644
index 000000000..bdcf8c2f2
--- /dev/null
+++ b/src/Mercure/Service/UrlService.php
@@ -0,0 +1,53 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * This source file is available under the terms of the
+ * Pimcore Open Core License (POCL)
+ * Full copyright and license information is available in
+ * LICENSE.md which is distributed with this source code.
+ *
+ *  @copyright  Copyright (c) Pimcore GmbH (https://www.pimcore.com)
+ *  @license    Pimcore Open Core License (POCL)
+ */
+
+namespace Pimcore\Bundle\StudioBackendBundle\Mercure\Service;
+
+use Pimcore\Bundle\StaticResolverBundle\Lib\ToolResolverInterface;
+use Pimcore\Bundle\StudioBackendBundle\Mercure\Util\Constant\Mercure;
+
+/**
+ * @internal
+ */
+final readonly class UrlService implements UrlServiceInterface
+{
+    public function __construct(
+        private ?string $serverSideUrl,
+        private ?string $clientSideUrl,
+        private ToolResolverInterface $toolResolver,
+    ) {
+    }
+
+    public function getServerSideUrl(): string
+    {
+        if (empty($this->serverSideUrl)) {
+            return $this->getDefaultUrl();
+        }
+
+        return $this->serverSideUrl;
+    }
+
+    public function getClientSideUrl(): string
+    {
+        if (empty($this->clientSideUrl)) {
+            return $this->getDefaultUrl();
+        }
+
+        return str_replace(Mercure::HOST_PLACEHOLDER->value, $this->toolResolver->getHostUrl(), $this->clientSideUrl);
+    }
+
+    private function getDefaultUrl(): string
+    {
+        return $this->toolResolver->getHostUrl() . '/hub';
+    }
+}
diff --git a/src/Mercure/Service/UrlServiceInterface.php b/src/Mercure/Service/UrlServiceInterface.php
new file mode 100644
index 000000000..56fdeb279
--- /dev/null
+++ b/src/Mercure/Service/UrlServiceInterface.php
@@ -0,0 +1,21 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * This source file is available under the terms of the
+ * Pimcore Open Core License (POCL)
+ * Full copyright and license information is available in
+ * LICENSE.md which is distributed with this source code.
+ *
+ *  @copyright  Copyright (c) Pimcore GmbH (https://www.pimcore.com)
+ *  @license    Pimcore Open Core License (POCL)
+ */
+
+namespace Pimcore\Bundle\StudioBackendBundle\Mercure\Service;
+
+interface UrlServiceInterface
+{
+    public function getServerSideUrl(): string;
+
+    public function getClientSideUrl(): string;
+}
diff --git a/src/Mercure/Util/Constant/Mercure.php b/src/Mercure/Util/Constant/Mercure.php
index 40c063f7b..a2085debc 100644
--- a/src/Mercure/Util/Constant/Mercure.php
+++ b/src/Mercure/Util/Constant/Mercure.php
@@ -19,6 +19,7 @@
 enum Mercure: string
 {
     case AUTHORIZATION_COOKIE_NAME = 'mercureAuthorization';
+    case HOST_PLACEHOLDER = '<PIMCORE_SCHEMA_HOST>';
     case URL_PATH = 'path';
     case URL_HOST = 'host';
     case URL_SCHEME = 'scheme';


Reviews:
user: login: martineiber
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T10:43:37Z
user: login: lukmzig
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T06:15:49Z
user: login: martineiber
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:04:42Z

Review comments:
diff_hunk: @@ -0,0 +1,21 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * This source file is available under the terms of the
+ * Pimcore Open Core License (POCL)
+ * Full copyright and license information is available in
+ * LICENSE.md which is distributed with this source code.
+ *
+ *  @copyright  Copyright (c) Pimcore GmbH (https://www.pimcore.com)
+ *  @license    Pimcore Open Core License (POCL)
+ */
+
+namespace Pimcore\Bundle\StudioBackendBundle\Mercure\Service;
+
+interface UrlServiceInterface
path: src/Mercure/Service/UrlServiceInterface.php
user: login: martineiber
body: on purpose not Internal? When yes, then we should add a short Docs how and for what to use it. 
created_at: 2025-05-09T10:43:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * This source file is available under the terms of the
+ * Pimcore Open Core License (POCL)
+ * Full copyright and license information is available in
+ * LICENSE.md which is distributed with this source code.
+ *
+ *  @copyright  Copyright (c) Pimcore GmbH (https://www.pimcore.com)
+ *  @license    Pimcore Open Core License (POCL)
+ */
+
+namespace Pimcore\Bundle\StudioBackendBundle\Mercure\Service;
+
+interface UrlServiceInterface
path: src/Mercure/Service/UrlServiceInterface.php
user: login: lukmzig
body: Good point, :) I updated the docs here: https://github.com/pimcore/studio-backend-bundle/pull/1070/commits/d20b5b00f83b95a481e28a55c87e260f1ddbc4ad
created_at: 2025-05-12T06:15:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-12T06:15:58Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=pimcore_studio-backend-bundle&pullRequest=1070) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=pimcore_studio-backend-bundle&pullRequest=1070&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=pimcore_studio-backend-bundle&pullRequest=1070&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=pimcore_studio-backend-bundle&pullRequest=1070&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=pimcore_studio-backend-bundle&pullRequest=1070&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=pimcore_studio-backend-bundle&pullRequest=1070&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=pimcore_studio-backend-bundle&pullRequest=1070)


author_association: NONE