From 148411743085b808af9582f64d83565b38760bbf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 22 Jun 2018 12:02:59 +0200 Subject: [PATCH] [FrameworkBundle][Cache] Allow configuring PDO-based cache pools, with table auto-creation on first use --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 1 + .../FrameworkExtension.php | 2 +- .../Resources/config/cache.xml | 13 +++++++++ .../DependencyInjection/ConfigurationTest.php | 1 + src/Symfony/Component/Cache/CHANGELOG.md | 1 + .../Tests/Adapter/PdoDbalAdapterTest.php | 1 - .../Component/Cache/Traits/PdoTrait.php | 28 +++++++++++++++---- src/Symfony/Component/Cache/composer.json | 3 +- 9 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 5fc271c0512d..e285ebf8a13c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) + * Allowed configuring PDO-based cache pools via a new `cache.adapter.pdo` abstract service * Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead * Deprecated processing of services tagged `security.expression_language_provider` in favor of a new `AddExpressionLanguageProvidersPass` in SecurityBundle. diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 98e0e0e4b844..131d7dc66c61 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -866,6 +866,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() + ->scalarNode('default_pdo_provider')->defaultValue('doctrine.dbal.default_connection')->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index b5f4b4d9c366..1ab8e9c4d858 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1547,7 +1547,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con // Inline any env vars referenced in the parameter $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true)); } - foreach (array('doctrine', 'psr6', 'redis', 'memcached') as $name) { + foreach (array('doctrine', 'psr6', 'redis', 'memcached', 'pdo') as $name) { if (isset($config[$name = 'default_'.$name.'_provider'])) { $container->setAlias('cache.'.$name, new Alias(Compiler\CachePoolPass::getServiceProvider($container, $config[$name]), false)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index d31be8db3fc9..a4d082e4e4ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -109,6 +109,19 @@ + + + + + + 0 + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 58f7e564c97f..c148e87162c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -266,6 +266,7 @@ protected static function getBundleDefaultConfig() 'directory' => '%kernel.cache_dir%/pools', 'default_redis_provider' => 'redis://localhost', 'default_memcached_provider' => 'memcached://localhost', + 'default_pdo_provider' => 'doctrine.dbal.default_connection', ), 'workflows' => array( 'enabled' => false, diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 98cf028026a6..13884de90533 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache * added sub-second expiry accuracy for backends that support it * added support for phpredis 4 `compression` and `tcp_keepalive` options + * added automatic table creation when using Doctrine DBAL with PDO-based backends * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead * deprecated the `AbstractAdapter::createSystemCache()` method diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php index 1e8c6155bdd3..f585c35524c4 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php @@ -33,7 +33,6 @@ public static function setupBeforeClass() self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); $pool = new PdoAdapter(DriverManager::getConnection(array('driver' => 'pdo_sqlite', 'path' => self::$dbFile))); - $pool->createTable(); } public static function tearDownAfterClass() diff --git a/src/Symfony/Component/Cache/Traits/PdoTrait.php b/src/Symfony/Component/Cache/Traits/PdoTrait.php index a88099ecb183..19a94c9acda7 100644 --- a/src/Symfony/Component/Cache/Traits/PdoTrait.php +++ b/src/Symfony/Component/Cache/Traits/PdoTrait.php @@ -14,6 +14,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Schema\Schema; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -150,7 +151,11 @@ public function prune() $deleteSql .= " AND $this->idCol LIKE :namespace"; } - $delete = $this->getConnection()->prepare($deleteSql); + try { + $delete = $this->getConnection()->prepare($deleteSql); + } catch (TableNotFoundException $e) { + return true; + } $delete->bindValue(':time', time(), \PDO::PARAM_INT); if ('' !== $this->namespace) { @@ -229,7 +234,10 @@ protected function doClear($namespace) $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; } - $conn->exec($sql); + try { + $conn->exec($sql); + } catch (TableNotFoundException $e) { + } return true; } @@ -241,8 +249,11 @@ protected function doDelete(array $ids) { $sql = str_pad('', (count($ids) << 1) - 1, '?,'); $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)"; - $stmt = $this->getConnection()->prepare($sql); - $stmt->execute(array_values($ids)); + try { + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($ids)); + } catch (TableNotFoundException $e) { + } return true; } @@ -302,7 +313,14 @@ protected function doSave(array $values, $lifetime) $now = time(); $lifetime = $lifetime ?: null; - $stmt = $conn->prepare($sql); + try { + $stmt = $conn->prepare($sql); + } catch (TableNotFoundException $e) { + if (!$conn->isTransactionActive() || \in_array($this->driver, array('pgsql', 'sqlite', 'sqlsrv'), true)) { + $this->createTable(); + } + $stmt = $conn->prepare($sql); + } if ('sqlsrv' === $driver || 'oci' === $driver) { $stmt->bindParam(1, $id); diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index e76644186efa..b043aeb58b47 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -28,11 +28,12 @@ "require-dev": { "cache/integration-tests": "dev-master", "doctrine/cache": "~1.6", - "doctrine/dbal": "~2.4", + "doctrine/dbal": "~2.5", "predis/predis": "~1.0", "symfony/var-dumper": "^4.1.1" }, "conflict": { + "doctrine/dbal": "<2.5", "symfony/var-dumper": "<3.4" }, "autoload": {