diff --git a/UPGRADE-3.1.md b/UPGRADE-3.1.md index d5e68b2d41711..4355a80939657 100644 --- a/UPGRADE-3.1.md +++ b/UPGRADE-3.1.md @@ -93,8 +93,8 @@ FrameworkBundle cache service. If you are using `serializer.mapping.cache.apc`, use `serializer.mapping.cache.doctrine.apc` instead. - * The `framework.serializer.cache` option has been deprecated. Configure a cache pool - called `serializer` under `framework.cache.pools` instead. + * The `framework.serializer.cache` option has been deprecated. Configure the + `cache.serializer` service under `framework.cache.pools` instead. Before: @@ -110,7 +110,7 @@ FrameworkBundle framework: cache: pools: - serializer: + cache.serializer: adapter: cache.adapter.apcu HttpKernel diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index ccd4c7ae00bd3..1f5a00bbf9306 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -80,8 +80,8 @@ FrameworkBundle * The service `serializer.mapping.cache.apc` has been removed; use `serializer.mapping.cache.doctrine.apc` instead. - * The `framework.serializer.cache` option has been removed. Configure a cache pool - called `serializer` under `framework.cache.pools` instead. + * The `framework.serializer.cache` option has been removed. Configure the + `cache.serializer` service under `framework.cache.pools` instead. Before: @@ -97,7 +97,7 @@ FrameworkBundle framework: cache: pools: - serializer: + cache.serializer: adapter: cache.adapter.apcu ``` diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index 4b8e0afb7bb3b..49e97b73c6402 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -11,8 +11,10 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; +use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; @@ -44,13 +46,15 @@ public function process(ContainerBuilder $container) if ($pool->isAbstract()) { continue; } - $tags[0]['namespace'] = $this->getNamespace($namespaceSuffix, isset($tags[0]['namespace']) ? $tags[0]['namespace'] : $id); while ($adapter instanceof DefinitionDecorator) { $adapter = $container->findDefinition($adapter->getParent()); if ($t = $adapter->getTag('cache.pool')) { $tags[0] += $t[0]; } } + if (!isset($tags[0]['namespace'])) { + $tags[0]['namespace'] = $this->getNamespace($namespaceSuffix, $id); + } if (isset($tags[0]['clearer'])) { $clearer = $container->getDefinition($tags[0]['clearer']); } else { @@ -58,8 +62,8 @@ public function process(ContainerBuilder $container) } unset($tags[0]['clearer']); - if (isset($tags[0]['provider']) && is_string($tags[0]['provider'])) { - $tags[0]['provider'] = new Reference($tags[0]['provider']); + if (isset($tags[0]['provider'])) { + $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); } $i = 0; foreach ($attributes as $attr) { @@ -82,4 +86,24 @@ private function getNamespace($namespaceSuffix, $id) { return substr(str_replace('/', '-', base64_encode(md5($id.$namespaceSuffix, true))), 0, 10); } + + /** + * @internal + */ + public static function getServiceProvider(ContainerBuilder $container, $name) + { + if (0 === strpos($name, 'redis://')) { + $dsn = $name; + + if (!$container->hasDefinition($name = md5($dsn))) { + $definition = new Definition(\Redis::class); + $definition->setPublic(false); + $definition->setFactory(array(RedisAdapter::class, 'createConnection')); + $definition->setArguments(array($dsn)); + $container->setDefinition($name, $definition); + } + } + + return $name; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 7d8bf5f0a03e1..ed56b03911dcf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -558,25 +558,35 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->addDefaultsIfNotSet() ->fixXmlConfig('pool') ->children() + ->scalarNode('app') + ->info('App related cache pools configuration') + ->defaultValue('cache.adapter.filesystem') + ->end() + ->scalarNode('system') + ->info('System related cache pools configuration') + ->defaultValue('cache.adapter.filesystem') + ->end() + ->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools')->end() + ->scalarNode('default_doctrine_provider')->end() + ->scalarNode('default_psr6_provider')->end() + ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') ->children() - ->scalarNode('adapter') - ->info('The cache pool adapter service to use as template definition.') - ->defaultValue('cache.adapter.shared') - ->end() + ->scalarNode('adapter')->defaultValue('cache.app')->end() ->booleanNode('public')->defaultFalse()->end() ->integerNode('default_lifetime')->end() ->scalarNode('provider') ->info('The service name to use as provider when the specified adapter needs one.') ->end() - ->scalarNode('namespace') - ->info('The namespace where cached items are stored. Auto-generated by default. Set to false to disable namespacing.') - ->end() - ->scalarNode('clearer')->defaultValue('cache.default_pools_clearer')->end() + ->scalarNode('clearer')->defaultValue('cache.default_clearer')->end() ->end() ->end() + ->validate() + ->ifTrue(function ($v) { return isset($v['cache.app']) || isset($v['cache.system']); }) + ->thenInvalid('"cache.app" and "cache.system" are reserved names') + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 3f1845dd8f7db..3f15d2069942f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -73,7 +73,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('property_access.xml'); // Load Cache configuration first as it is used by other components - $loader->load('cache_pools.xml'); + $loader->load('cache.xml'); $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); @@ -984,7 +984,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $chainLoader->replaceArgument(0, $serializerLoaders); if (isset($config['cache']) && $config['cache']) { - @trigger_error('The "framework.serializer.cache" option is deprecated since Symfony 3.1 and will be removed in 4.0. You can configure a cache pool called "serializer" under "framework.cache.pools" instead.', E_USER_DEPRECATED); + @trigger_error('The "framework.serializer.cache" option is deprecated since Symfony 3.1 and will be removed in 4.0. Configure the "cache.serializer" service under "framework.cache.pools" instead.', E_USER_DEPRECATED); $container->setParameter( 'serializer.mapping.cache.prefix', @@ -999,7 +999,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder CacheClassMetadataFactory::class, array( new Reference('serializer.mapping.cache_class_metadata_factory.inner'), - new Reference('cache.pool.serializer'), + new Reference('cache.serializer'), ) ); $cacheMetadataFactory->setPublic(false); @@ -1037,13 +1037,26 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild private function registerCacheConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - foreach ($config['pools'] as $name => $poolConfig) { - $poolDefinition = new DefinitionDecorator($poolConfig['adapter']); - $poolDefinition->setPublic($poolConfig['public']); - unset($poolConfig['adapter'], $poolConfig['public']); + $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); - $poolDefinition->addTag('cache.pool', $poolConfig); - $container->setDefinition('cache.pool.'.$name, $poolDefinition); + foreach (array('doctrine', 'psr6', 'redis') as $name) { + if (isset($config[$name = 'default_'.$name.'_provider'])) { + $container->setAlias('cache.'.$name, Compiler\CachePoolPass::getServiceProvider($container, $config[$name])); + } + } + foreach (array('app', 'system') as $name) { + $config['pools']['cache.'.$name] = array( + 'adapter' => $config[$name], + 'public' => true, + ); + } + foreach ($config['pools'] as $name => $pool) { + $definition = new DefinitionDecorator($pool['adapter']); + $definition->setPublic($pool['public']); + unset($pool['adapter'], $pool['public']); + + $definition->addTag('cache.pool', $pool); + $container->setDefinition($name, $definition); } $this->addClassesToCompile(array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache_pools.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml similarity index 69% rename from src/Symfony/Bundle/FrameworkBundle/Resources/config/cache_pools.xml rename to src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index 6929b2299348e..6a2e1474838bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache_pools.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -6,30 +6,24 @@ - - - - - - - - - + + - - + + - - + + - - + + + @@ -39,6 +33,7 @@ + @@ -49,6 +44,7 @@ + @@ -59,14 +55,16 @@ + + - + @@ -74,5 +72,9 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index aa2c057d356ec..830702213f9e7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -205,9 +205,15 @@ - - - + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index aa35244a5d891..ce2221cd6166e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -29,7 +29,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index b41d568c64fba..b948d22f11f76 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -268,6 +268,10 @@ protected static function getBundleDefaultConfig() ), 'cache' => array( 'pools' => array(), + 'app' => 'cache.adapter.filesystem', + 'system' => 'cache.adapter.filesystem', + 'directory' => '%kernel.cache_dir%/pools', + 'default_redis_provider' => 'redis://localhost', ), ); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php index 7cf634b92e394..ef7a1be190468 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php @@ -3,25 +3,25 @@ $container->loadFromExtension('framework', array( 'cache' => array( 'pools' => array( - 'foo' => array( + 'cache.foo' => array( 'adapter' => 'cache.adapter.apcu', 'default_lifetime' => 30, ), - 'bar' => array( + 'cache.bar' => array( 'adapter' => 'cache.adapter.doctrine', 'default_lifetime' => 5, 'provider' => 'app.doctrine_cache_provider', ), - 'baz' => array( + 'cache.baz' => array( 'adapter' => 'cache.adapter.filesystem', 'default_lifetime' => 7, ), - 'foobar' => array( + 'cache.foobar' => array( 'adapter' => 'cache.adapter.psr6', 'default_lifetime' => 10, 'provider' => 'app.cache_pool', ), - 'def' => array( + 'cache.def' => array( 'default_lifetime' => 11, ), ), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml index d6f472716ff89..79ee2a4b37421 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml index 395009f18ad18..514e782e6e148 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml @@ -1,19 +1,19 @@ framework: cache: pools: - foo: + cache.foo: adapter: cache.adapter.apcu default_lifetime: 30 - bar: + cache.bar: adapter: cache.adapter.doctrine default_lifetime: 5 provider: app.doctrine_cache_provider - baz: + cache.baz: adapter: cache.adapter.filesystem default_lifetime: 7 - foobar: + cache.foobar: adapter: cache.adapter.psr6 default_lifetime: 10 provider: app.cache_pool - def: + cache.def: default_lifetime: 11 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 6abee616bbe43..b6662a05a957f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -16,6 +16,8 @@ use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Adapter\ProxyAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; @@ -619,11 +621,11 @@ public function testCachePoolServices() { $container = $this->createContainerFromFile('cache'); - $this->assertCachePoolServiceDefinitionIsCreated($container, 'foo', 'cache.adapter.apcu', 30); - $this->assertCachePoolServiceDefinitionIsCreated($container, 'bar', 'cache.adapter.doctrine', 5); - $this->assertCachePoolServiceDefinitionIsCreated($container, 'baz', 'cache.adapter.filesystem', 7); - $this->assertCachePoolServiceDefinitionIsCreated($container, 'foobar', 'cache.adapter.psr6', 10); - $this->assertCachePoolServiceDefinitionIsCreated($container, 'def', 'cache.adapter.filesystem', 11); + $this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.foo', 'cache.adapter.apcu', 30); + $this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.bar', 'cache.adapter.doctrine', 5); + $this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.baz', 'cache.adapter.filesystem', 7); + $this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.foobar', 'cache.adapter.psr6', 10); + $this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.def', 'cache.app', 11); } protected function createContainer(array $data = array()) @@ -697,15 +699,13 @@ private function assertVersionStrategy(ContainerBuilder $container, Reference $r } } - private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $container, $name, $adapter, $defaultLifetime) + private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $container, $id, $adapter, $defaultLifetime) { - $id = 'cache.pool.'.$name; - $this->assertTrue($container->has($id), sprintf('Service definition "%s" for cache pool of type "%s" is registered', $id, $adapter)); $poolDefinition = $container->getDefinition($id); - $this->assertInstanceOf(DefinitionDecorator::class, $poolDefinition, sprintf('Cache pool "%s" is based on an abstract cache pool.', $name)); + $this->assertInstanceOf(DefinitionDecorator::class, $poolDefinition, sprintf('Cache pool "%s" is based on an abstract cache pool.', $id)); $this->assertTrue($poolDefinition->hasTag('cache.pool'), sprintf('Service definition "%s" is tagged with the "cache.pool" tag.', $id)); $this->assertFalse($poolDefinition->isAbstract(), sprintf('Service definition "%s" is not abstract.', $id)); @@ -714,21 +714,31 @@ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $con $this->assertTrue(isset($tag[0]['default_lifetime']), 'The default lifetime is stored as an attribute of the "cache.pool" tag.'); $this->assertSame($defaultLifetime, $tag[0]['default_lifetime'], 'The default lifetime is stored as an attribute of the "cache.pool" tag.'); - $adapterId = $poolDefinition->getParent(); - $adapterDefinition = $container->findDefinition($adapterId); + $parentDefinition = $poolDefinition; + do { + $parentId = $parentDefinition->getParent(); + $parentDefinition = $container->findDefinition($parentId); + } while ($parentDefinition instanceof DefinitionDecorator); switch ($adapter) { case 'cache.adapter.apcu': - $this->assertSame(ApcuAdapter::class, $adapterDefinition->getClass()); + $this->assertSame(ApcuAdapter::class, $parentDefinition->getClass()); break; case 'cache.adapter.doctrine': - $this->assertSame(DoctrineAdapter::class, $adapterDefinition->getClass()); + $this->assertSame(DoctrineAdapter::class, $parentDefinition->getClass()); break; + case 'cache.app': case 'cache.adapter.filesystem': - $this->assertSame(FilesystemAdapter::class, $adapterDefinition->getClass()); + $this->assertSame(FilesystemAdapter::class, $parentDefinition->getClass()); + break; + case 'cache.adapter.psr6': + $this->assertSame(ProxyAdapter::class, $parentDefinition->getClass()); break; + case 'cache.adapter.redis': + $this->assertSame(RedisAdapter::class, $parentDefinition->getClass()); + break; + default: + $this->fail('Unresolved adapter: '.$adapter); } - - $this->assertTrue($adapterDefinition->isAbstract(), sprintf('Service definition "%s" is abstract.', $adapterId)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php index b35eebdb317f2..5724a0afccb95 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php @@ -36,12 +36,27 @@ public function testRedisCachePools() } } + /** + * @requires extension redis + */ + public function testRedisCustomCachePools() + { + try { + $this->doTestCachePools(array('root_config' => 'redis_custom_config.yml', 'environment' => 'custom_redis_cache'), RedisAdapter::class); + } catch (\PHPUnit_Framework_Error_Warning $e) { + if (0 !== strpos($e->getMessage(), 'unable to connect to 127.0.0.1')) { + throw $e; + } + $this->markTestSkipped($e->getMessage()); + } + } + public function doTestCachePools($options, $adapterClass) { static::bootKernel($options); $container = static::$kernel->getContainer(); - $pool = $container->get('cache.pool.test'); + $pool = $container->get('cache.test'); $this->assertInstanceOf($adapterClass, $pool); $key = 'foobar'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml index 25aff9cbcbe15..20d966780c918 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml @@ -4,5 +4,5 @@ imports: framework: cache: pools: - test: + cache.test: public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml index fb2510b6fa0d2..ae1fbf62a6ff3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml @@ -1,22 +1,9 @@ imports: - { resource: ../config/default.yml } -services: - cache.adapter.redis.connection: - public: false - class: Redis - calls: - - [connect, [127.0.0.1]] - - cache.adapter.shared: - abstract: true - parent: cache.adapter.redis - tags: - - name: cache.pool - provider: cache.adapter.redis.connection - framework: cache: + app: cache.adapter.redis pools: - test: + cache.test: public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml new file mode 100644 index 0000000000000..3c99f05ac75cc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml @@ -0,0 +1,21 @@ +imports: + - { resource: ../config/default.yml } + +services: + cache.test_redis_connection: + public: false + class: Redis + calls: + - [connect, [127.0.0.1]] + + cache.app: + parent: cache.adapter.redis + tags: + - name: cache.pool + provider: cache.test_redis_connection + +framework: + cache: + pools: + cache.test: + public: true