diff --git a/.travis.yml b/.travis.yml index 4d1461291..f092cbef6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ php: - 5.6 - 7.0 - 7.1 + - 7.2 - nightly matrix: @@ -18,12 +19,15 @@ matrix: - php: 5.5 env: deps=low SYMFONY_DEPRECATIONS_HELPER=weak - php: 5.6 - env: SYMFONY_VERSION="2.8.*@dev" - - php: 5.6 - env: deps=dev + env: SYMFONY_VERSION="2.8.*" + - php: 7.0 + env: SYMFONY_VERSION="3.2.*" + - php: 7.1 + env: stability=RC SYMFONY_DEPRECATIONS_HELPER=weak SYMFONY_VERSION="3.4.*" + - php: 7.2 + env: stability=alpha allow_failures: - php: nightly - - env: deps=dev env: global: @@ -31,8 +35,8 @@ env: before_install: - composer self-update - - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/symfony:${SYMFONY_VERSION}; fi; - - if [ "$deps" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi; + - if [ "$SYMFONY_VERSION" != "" ]; then jq "(.require, .\"require-dev\")|=(with_entries(if .key|test(\"^symfony/\") then .value|=\"${SYMFONY_VERSION}\" else . end))" composer.json|ex -sc 'wq!composer.json' /dev/stdin; fi; + - if [ ! -z "$stability" ]; then perl -pi -e "s/^}$/,\"minimum-stability\":\"$stability\"}/" composer.json; fi; install: - if [ "$deps" = "low" ]; then composer --prefer-lowest --prefer-stable update; else composer update; fi; diff --git a/DataCollector/DoctrineDataCollector.php b/DataCollector/DoctrineDataCollector.php index 85197aa67..489b0eca7 100644 --- a/DataCollector/DoctrineDataCollector.php +++ b/DataCollector/DoctrineDataCollector.php @@ -20,6 +20,7 @@ use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector as BaseCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; /** * DoctrineDataCollector. @@ -136,6 +137,17 @@ public function collect(Request $request, Response $response, \Exception $except } } + // HttpKernel < 3.2 compatibility layer + if (method_exists($this, 'cloneVar')) { + // Might be good idea to replicate this block in doctrine bridge so we can drop this from here after some time. + // This code is compatible with such change, because cloneVar is supposed to check if input is already cloned. + foreach ($this->data['queries'] as &$queries) { + foreach ($queries as &$query) { + $query['params'] = $this->cloneVar($query['params']); + } + } + } + $this->data['entities'] = $entities; $this->data['errors'] = $errors; $this->data['caches'] = $caches; diff --git a/DependencyInjection/Compiler/EntityListenerPass.php b/DependencyInjection/Compiler/EntityListenerPass.php index f25538d52..ca3ac3f4f 100644 --- a/DependencyInjection/Compiler/EntityListenerPass.php +++ b/DependencyInjection/Compiler/EntityListenerPass.php @@ -47,6 +47,7 @@ public function process(ContainerBuilder $container) } $resolver = $container->findDefinition($resolverId); + $resolver->setPublic(true); if (isset($attributes['entity']) && isset($attributes['event'])) { $this->attachToListener($container, $name, $id, $attributes); diff --git a/Resources/views/Collector/db.html.twig b/Resources/views/Collector/db.html.twig index c25db4a47..4cfb9a012 100644 --- a/Resources/views/Collector/db.html.twig +++ b/Resources/views/Collector/db.html.twig @@ -1,4 +1,6 @@ -{% extends app.request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %} +{% extends request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as helper %} {% block toolbar %} {% if collector.querycount > 0 or collector.invalidEntityCount > 0 %} @@ -113,8 +115,8 @@ {{ render(controller('DoctrineBundle:Profiler:explain', { token: token, panel: 'db', - connectionName: app.request.query.get('connection'), - query: app.request.query.get('query') + connectionName: request.query.get('connection'), + query: request.query.get('query') })) }} {% else %} {{ block('queries') }} @@ -238,7 +240,7 @@ {{ query.sql|doctrine_pretty_query(highlight_only = true) }}
- Parameters: {{ profiler_dump(query.params) }} + Parameters: {{ profiler_dump(query.params, 2) }}
@@ -280,7 +282,7 @@

There are no configured database connections.

{% else %} - {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.connections, labels: ['Name', 'Service'] }, with_context = false ) }} + {{ helper.render_simple_table('Name', 'Service', collector.connections) }} {% endif %}

Entity Managers

@@ -290,7 +292,7 @@

There are no configured entity managers.

{% else %} - {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.managers, labels: ['Name', 'Service'] }, with_context = false ) }} + {{ helper.render_simple_table('Name', 'Service', collector.managers) }} {% endif %}

Second Level Cache

@@ -306,7 +308,7 @@ {% else %} {% if profiler_markup_version == 1 %} - {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.cacheCounts }, with_context = false) }} + {{ helper.render_simple_table('Key', 'Value', collector.cacheCounts) }} {% else %}
@@ -328,17 +330,17 @@ {% if collector.cacheRegions.hits %}

Number of cache hits

- {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.cacheRegions.hits }, with_context = false) }} + {{ helper.render_simple_table('Region', 'Hits', collector.cacheRegions.hits) }} {% endif %} {% if collector.cacheRegions.misses %}

Number of cache misses

- {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.cacheRegions.misses }, with_context = false) }} + {{ helper.render_simple_table('Region', 'Misses', collector.cacheRegions.misses) }} {% endif %} {% if collector.cacheRegions.puts %}

Number of cache puts

- {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.cacheRegions.puts }, with_context = false) }} + {{ helper.render_simple_table('Region', 'Puts', collector.cacheRegions.puts) }} {% endif %} {% endif %} {% endif %} @@ -463,3 +465,22 @@ //]]> {% endblock %} + +{% macro render_simple_table(label1, label2, data) %} + + + + + + + + + {% for key, value in data %} + + + + + {% endfor %} + +
{{ label1 }}{{ label2 }}
{{ key }}{{ value }}
+{% endmacro %} diff --git a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php index 47719f752..01e3559b0 100644 --- a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -348,13 +349,13 @@ public function testLoadMultipleConnections() $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $arguments[1]); $this->assertEquals('doctrine.orm.em2_configuration', (string) $arguments[1]); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.em1_metadata_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em1_metadata_cache')); $this->assertEquals('%doctrine_cache.xcache.class%', $definition->getClass()); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.em1_query_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em1_query_cache')); $this->assertEquals('%doctrine_cache.array.class%', $definition->getClass()); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.em1_result_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em1_result_cache')); $this->assertEquals('%doctrine_cache.array.class%', $definition->getClass()); } @@ -376,10 +377,10 @@ public function testEntityManagerMetadataCacheDriverConfiguration() { $container = $this->loadContainer('orm_service_multiple_entity_managers'); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.em1_metadata_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em1_metadata_cache')); $this->assertDICDefinitionClass($definition, '%doctrine_cache.xcache.class%'); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.em2_metadata_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.em2_metadata_cache')); $this->assertDICDefinitionClass($definition, '%doctrine_cache.apc.class%'); } @@ -387,7 +388,7 @@ public function testEntityManagerMemcacheMetadataCacheDriverConfiguration() { $container = $this->loadContainer('orm_service_simple_single_entity_manager'); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.default_metadata_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_metadata_cache')); $this->assertDICDefinitionClass($definition, '%doctrine_cache.memcache.class%'); $this->assertDICDefinitionMethodCallOnce($definition, 'setMemcache', array(new Reference('doctrine_cache.services.doctrine.orm.default_metadata_cache.connection')) @@ -404,7 +405,7 @@ public function testEntityManagerRedisMetadataCacheDriverConfigurationWithDataba { $container = $this->loadContainer('orm_service_simple_single_entity_manager_redis'); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.default_metadata_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_metadata_cache')); $this->assertDICDefinitionClass($definition, '%doctrine_cache.redis.class%'); $this->assertDICDefinitionMethodCallOnce($definition, 'setRedis', array(new Reference('doctrine_cache.services.doctrine.orm.default_metadata_cache_redis.connection')) @@ -420,7 +421,7 @@ public function testDependencyInjectionImportsOverrideDefaults() { $container = $this->loadContainer('orm_imports'); - $cacheDefinition = $container->getDefinition($container->getAlias('doctrine.orm.default_metadata_cache')); + $cacheDefinition = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_metadata_cache')); $this->assertEquals('%doctrine_cache.apc.class%', $cacheDefinition->getClass()); $configDefinition = $container->getDefinition('doctrine.orm.default_configuration'); @@ -604,7 +605,7 @@ public function testSecondLevelCache() $loggerChainDef = $container->getDefinition('doctrine.orm.default_second_level_cache.logger_chain'); $loggerStatisticsDef = $container->getDefinition('doctrine.orm.default_second_level_cache.logger_statistics'); $myQueryRegionDef = $container->getDefinition('doctrine.orm.default_second_level_cache.region.my_query_region_filelock'); - $cacheDriverDef = $container->getDefinition($container->getAlias('doctrine.orm.default_second_level_cache.region_cache_driver')); + $cacheDriverDef = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_second_level_cache.region_cache_driver')); $configDef = $container->getDefinition('doctrine.orm.default_configuration'); $myEntityRegionArgs = $myEntityRegionDef->getArguments(); $myQueryRegionArgs = $myQueryRegionDef->getArguments(); @@ -1072,7 +1073,7 @@ private function assertDICDefinitionNoMethodCall(Definition $definition, $method private function compileContainer(ContainerBuilder $container) { - $container->getCompilerPassConfig()->setOptimizationPasses(array(new ResolveDefinitionTemplatesPass())); + $container->getCompilerPassConfig()->setOptimizationPasses(array(class_exists(ResolveChildDefinitionsPass::class) ? new ResolveChildDefinitionsPass() : new ResolveDefinitionTemplatesPass())); $container->getCompilerPassConfig()->setRemovingPasses(array()); $container->compile(); } diff --git a/Tests/DependencyInjection/DoctrineExtensionTest.php b/Tests/DependencyInjection/DoctrineExtensionTest.php index 95b9ba405..c7fed524d 100644 --- a/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -24,6 +24,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Version; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -356,13 +357,13 @@ public function testDependencyInjectionConfigurationDefaults() $this->assertEquals('doctrine.orm.default_entity_listener_resolver', (string) $calls[12][1][0]); } - $definition = $container->getDefinition($container->getAlias('doctrine.orm.default_metadata_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_metadata_cache')); $this->assertEquals('%doctrine_cache.array.class%', $definition->getClass()); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.default_query_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_query_cache')); $this->assertEquals('%doctrine_cache.array.class%', $definition->getClass()); - $definition = $container->getDefinition($container->getAlias('doctrine.orm.default_result_cache')); + $definition = $container->getDefinition((string) $container->getAlias('doctrine.orm.default_result_cache')); $this->assertEquals('%doctrine_cache.array.class%', $definition->getClass()); } @@ -817,7 +818,7 @@ private function assertDICDefinitionMethodCallOnce(Definition $definition, $meth private function compileContainer(ContainerBuilder $container) { - $container->getCompilerPassConfig()->setOptimizationPasses(array(new ResolveDefinitionTemplatesPass())); + $container->getCompilerPassConfig()->setOptimizationPasses(array(class_exists(ResolveChildDefinitionsPass::class) ? new ResolveChildDefinitionsPass() : new ResolveDefinitionTemplatesPass())); $container->getCompilerPassConfig()->setRemovingPasses(array()); $container->compile(); } diff --git a/Tests/ProfilerTest.php b/Tests/ProfilerTest.php new file mode 100644 index 000000000..4f8436410 --- /dev/null +++ b/Tests/ProfilerTest.php @@ -0,0 +1,86 @@ +logger = new DebugStack(); + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); + $registry->expects($this->once())->method('getManagers')->willReturn([]); + $this->collector = new DoctrineDataCollector($registry); + $this->collector->addLogger('foo', $this->logger); + + $twigLoaderFilesystem = new \Twig_Loader_Filesystem(__DIR__.'/../Resources/views/Collector'); + $twigLoaderFilesystem->addPath(__DIR__.'/../vendor/symfony/web-profiler-bundle/Resources/views', 'WebProfiler'); + $this->twig = new \Twig_Environment($twigLoaderFilesystem, ['debug' => true, 'strict_variables' => true]); + + $this->twig->addExtension(new CodeExtension('', '', '')); + $this->twig->addExtension(new RoutingExtension($this->getMockBuilder(UrlGeneratorInterface::class)->getMock())); + $this->twig->addExtension(new HttpKernelExtension($this->getMockBuilder(FragmentHandler::class)->disableOriginalConstructor()->getMock())); + $this->twig->addExtension(new WebProfilerExtension()); + $this->twig->addExtension(new DoctrineExtension()); + + $loader = $this->getMockBuilder(\Twig_RuntimeLoaderInterface::class)->getMock(); + $loader->method('load')->willReturn($this->getMockBuilder(HttpKernelRuntime::class)->disableOriginalConstructor()->getMock()); + $this->twig->addRuntimeLoader($loader); + } + + public function testRender() + { + $this->logger->queries = [ + [ + 'sql' => 'SELECT * FROM foo WHERE bar IN (?, ?)', + 'params' => ['foo', 'bar'], + 'executionMS' => 1, + ], + ]; + + $this->collector->collect($request = new Request(['group' => '0']), $response = new Response()); + + $profile = new Profile('foo'); + + // This is only needed for WebProfilerBundle=3.2, remove when support for it is dropped + $requestCollector = new RequestDataCollector(); + $requestCollector->collect($request, $response); + $profile->addCollector($requestCollector); + + $output = $this->twig->render('db.html.twig', [ + 'request' => $request, + 'token' => 'foo', + 'page' => 'foo', + 'profile' => $profile, + 'collector' => $this->collector, + 'queries' => $this->logger->queries, + ]); + + $output = str_replace(["\e[37m", "\e[0m", "\e[32;1m", "\e[34;1m"], "", $output); + $this->assertContains("SELECT * FROM foo WHERE bar IN ('foo', 'bar');", $output); + } +} diff --git a/Tests/ServiceRepositoryTest.php b/Tests/ServiceRepositoryTest.php index 1c518995c..827a12b0e 100644 --- a/Tests/ServiceRepositoryTest.php +++ b/Tests/ServiceRepositoryTest.php @@ -112,8 +112,7 @@ public function testRepositoryServiceWiring() $this->expectExceptionMessageRegExp($message); } else { // PHPUnit 4 compat - $this->setExpectedException(\RuntimeException::class); - $this->setExpectedExceptionMessage($message); + $this->setExpectedExceptionRegExp(\RuntimeException::class, $message); } } diff --git a/Tests/TestCase.php b/Tests/TestCase.php index 9c707040c..292b8a813 100644 --- a/Tests/TestCase.php +++ b/Tests/TestCase.php @@ -18,6 +18,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension; use PHPUnit\Framework\TestCase as BaseTestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -77,7 +78,7 @@ public function createYamlBundleTestContainer() $container->setDefinition('my.platform', new Definition('Doctrine\DBAL\Platforms\MySqlPlatform')); - $container->getCompilerPassConfig()->setOptimizationPasses(array(new ResolveDefinitionTemplatesPass())); + $container->getCompilerPassConfig()->setOptimizationPasses(array(class_exists(ResolveChildDefinitionsPass::class) ? new ResolveChildDefinitionsPass() : new ResolveDefinitionTemplatesPass())); $container->getCompilerPassConfig()->setRemovingPasses(array()); $container->compile(); diff --git a/Twig/DoctrineExtension.php b/Twig/DoctrineExtension.php index 5ae506a7e..44f5dde6f 100644 --- a/Twig/DoctrineExtension.php +++ b/Twig/DoctrineExtension.php @@ -14,6 +14,8 @@ namespace Doctrine\Bundle\DoctrineBundle\Twig; +use Symfony\Component\VarDumper\Cloner\Data; + /** * This class contains the needed functions in order to do the query highlighting * @@ -283,13 +285,18 @@ public static function escapeFunction($parameter) /** * Return a query with the parameters replaced * - * @param string $query - * @param array $parameters + * @param string $query + * @param array|Data $parameters * * @return string */ - public function replaceQueryParameters($query, array $parameters) + public function replaceQueryParameters($query, $parameters) { + if ($parameters instanceof Data) { + // VarDumper < 3.3 compatibility layer + $parameters = method_exists($parameters, 'getValue') ? $parameters->getValue(true) : $parameters->getRawData(); + } + $i = 0; if (!array_key_exists(0, $parameters) && array_key_exists(1, $parameters)) { diff --git a/composer.json b/composer.json index 7ecfab275..ae8059550 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/console": "~2.7|~3.0|~4.0", "symfony/dependency-injection": "~2.7|~3.0|~4.0", "doctrine/dbal": "^2.5.12", - "jdorn/sql-formatter": "~1.1", + "jdorn/sql-formatter": "^1.2.16", "symfony/doctrine-bridge": "~2.7|~3.0|~4.0", "doctrine/doctrine-cache-bundle": "~1.2" }, @@ -39,9 +39,13 @@ "symfony/validator": "~2.7|~3.0|~4.0", "symfony/property-info": "~2.8|~3.0|~4.0", "symfony/phpunit-bridge": "~2.7|~3.0|~4.0", - "twig/twig": "~1.12|~2.0", + "twig/twig": "~1.26|~2.0", "satooshi/php-coveralls": "^1.0", - "phpunit/phpunit": "^4.8.36|^5.7|^6.4" + "phpunit/phpunit": "^4.8.36|^5.7|^6.4", + "symfony/web-profiler-bundle": "~2.7|~3.0|~4.0" + }, + "conflict": { + "symfony/http-foundation": "<2.6" }, "suggest": { "symfony/web-profiler-bundle": "To use the data collector.",