Merge branch '4.4'
* 4.4: [HttpKernel][FrameworkBundle] Add alternative convention for bundle directories [DI] deprecate support for non-object services [Translation] XliffLintCommand: allow .xliff file extension [Serializer] Encode empty objects as objects, not arrays
This commit is contained in:
commit
b64fdb7c0a
@ -113,9 +113,33 @@ HttpFoundation
|
||||
HttpKernel
|
||||
----------
|
||||
|
||||
* Implementing the `BundleInterface` without implementing the `getPublicDir()` method is deprecated.
|
||||
This method will be added to the interface in 5.0.
|
||||
* The `DebugHandlersListener` class has been marked as `final`
|
||||
* Added new Bundle directory convention consistent with standard skeletons:
|
||||
|
||||
```
|
||||
└── MyBundle/
|
||||
├── config/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── MyBundle.php
|
||||
├── templates/
|
||||
└── translations/
|
||||
```
|
||||
|
||||
To make this work properly, it is necessary to change the root path of the bundle:
|
||||
|
||||
```php
|
||||
class MyBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return \dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As many bundles must be compatible with a range of Symfony versions, the current
|
||||
directory convention is not deprecated yet, but it will be in the future.
|
||||
|
||||
Lock
|
||||
----
|
||||
|
@ -291,7 +291,6 @@ HttpFoundation
|
||||
HttpKernel
|
||||
----------
|
||||
|
||||
* The `getPublicDir()` method has been added to the `BundleInterface`.
|
||||
* Removed `Client`, use `HttpKernelBrowser` instead
|
||||
* The `Kernel::getRootDir()` and the `kernel.root_dir` parameter have been removed
|
||||
* The `KernelInterface::getName()` and the `kernel.name` parameter have been removed
|
||||
@ -308,6 +307,32 @@ HttpKernel
|
||||
* Removed `TranslatorListener` in favor of `LocaleAwareListener`
|
||||
* The `DebugHandlersListener` class has been made `final`
|
||||
* Removed `SaveSessionListener` in favor of `AbstractSessionListener`
|
||||
* Added new Bundle directory convention consistent with standard skeletons:
|
||||
|
||||
```
|
||||
└── MyBundle/
|
||||
├── config/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── MyBundle.php
|
||||
├── templates/
|
||||
└── translations/
|
||||
```
|
||||
|
||||
To make this work properly, it is necessary to change the root path of the bundle:
|
||||
|
||||
```php
|
||||
class MyBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return \dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As many bundles must be compatible with a range of Symfony versions, the current
|
||||
directory convention is not deprecated yet, but it will be in the future.
|
||||
|
||||
Intl
|
||||
----
|
||||
|
@ -48,11 +48,10 @@ class DoctrineValidationPass implements CompilerPassInterface
|
||||
}
|
||||
|
||||
$files = $container->getParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files');
|
||||
$validationPath = 'Resources/config/validation.'.$this->managerType.'.'.$extension;
|
||||
$validationPath = '/config/validation.'.$this->managerType.'.'.$extension;
|
||||
|
||||
foreach ($container->getParameter('kernel.bundles') as $bundle) {
|
||||
$reflection = new \ReflectionClass($bundle);
|
||||
if ($container->fileExists($file = \dirname($reflection->getFileName()).'/'.$validationPath)) {
|
||||
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
|
||||
if ($container->fileExists($file = $bundle['path'].'/Resources'.$validationPath) || $container->fileExists($file = $bundle['path'].$validationPath)) {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ EOT
|
||||
$validAssetDirs = [];
|
||||
/** @var BundleInterface $bundle */
|
||||
foreach ($kernel->getBundles() as $bundle) {
|
||||
if (!is_dir($originDir = $bundle->getPath().\DIRECTORY_SEPARATOR.ltrim($bundle->getPublicDir(), '\\/'))) {
|
||||
if (!is_dir($originDir = $bundle->getPath().'/Resources/public') && !is_dir($originDir = $bundle->getPath().'/public')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -140,11 +140,12 @@ EOF
|
||||
if (null !== $input->getArgument('bundle')) {
|
||||
try {
|
||||
$bundle = $kernel->getBundle($input->getArgument('bundle'));
|
||||
$transPaths = [$bundle->getPath().'/Resources/translations'];
|
||||
$bundleDir = $bundle->getPath();
|
||||
$transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations'];
|
||||
$viewsPaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates'];
|
||||
if ($this->defaultTransPath) {
|
||||
$transPaths[] = $this->defaultTransPath;
|
||||
}
|
||||
$viewsPaths = [$bundle->getPath().'/Resources/views'];
|
||||
if ($this->defaultViewsPath) {
|
||||
$viewsPaths[] = $this->defaultViewsPath;
|
||||
}
|
||||
@ -161,8 +162,9 @@ EOF
|
||||
}
|
||||
} elseif ($input->getOption('all')) {
|
||||
foreach ($kernel->getBundles() as $bundle) {
|
||||
$transPaths[] = $bundle->getPath().'/Resources/translations';
|
||||
$viewsPaths[] = $bundle->getPath().'/Resources/views';
|
||||
$bundleDir = $bundle->getPath();
|
||||
$transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
|
||||
$viewsPaths[] = is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundle->getPath().'/templates';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,11 +139,12 @@ EOF
|
||||
if (null !== $input->getArgument('bundle')) {
|
||||
try {
|
||||
$foundBundle = $kernel->getBundle($input->getArgument('bundle'));
|
||||
$transPaths = [$foundBundle->getPath().'/Resources/translations'];
|
||||
$bundleDir = $foundBundle->getPath();
|
||||
$transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations'];
|
||||
$viewsPaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates'];
|
||||
if ($this->defaultTransPath) {
|
||||
$transPaths[] = $this->defaultTransPath;
|
||||
}
|
||||
$viewsPaths = [$foundBundle->getPath().'/Resources/views'];
|
||||
if ($this->defaultViewsPath) {
|
||||
$viewsPaths[] = $this->defaultViewsPath;
|
||||
}
|
||||
|
@ -1027,7 +1027,7 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
$defaultDir = $container->getParameterBag()->resolveValue($config['default_path']);
|
||||
foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
|
||||
if ($container->fileExists($dir = $bundle['path'].'/Resources/translations')) {
|
||||
if ($container->fileExists($dir = $bundle['path'].'/Resources/translations') || $container->fileExists($dir = $bundle['path'].'/translations')) {
|
||||
$dirs[] = $dir;
|
||||
} else {
|
||||
$nonExistingDirs[] = $dir;
|
||||
@ -1167,20 +1167,20 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
|
||||
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
|
||||
$dirname = $bundle['path'];
|
||||
$configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config';
|
||||
|
||||
if (
|
||||
$container->fileExists($file = $dirname.'/Resources/config/validation.yaml', false) ||
|
||||
$container->fileExists($file = $dirname.'/Resources/config/validation.yml', false)
|
||||
$container->fileExists($file = $configDir.'/validation.yaml', false) ||
|
||||
$container->fileExists($file = $configDir.'/validation.yml', false)
|
||||
) {
|
||||
$fileRecorder('yml', $file);
|
||||
}
|
||||
|
||||
if ($container->fileExists($file = $dirname.'/Resources/config/validation.xml', false)) {
|
||||
if ($container->fileExists($file = $configDir.'/validation.xml', false)) {
|
||||
$fileRecorder('xml', $file);
|
||||
}
|
||||
|
||||
if ($container->fileExists($dir = $dirname.'/Resources/config/validation', '/^$/')) {
|
||||
if ($container->fileExists($dir = $configDir.'/validation', '/^$/')) {
|
||||
$this->registerMappingFilesFromDir($dir, $fileRecorder);
|
||||
}
|
||||
}
|
||||
@ -1352,20 +1352,20 @@ class FrameworkExtension extends Extension
|
||||
};
|
||||
|
||||
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
|
||||
$dirname = $bundle['path'];
|
||||
$configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config';
|
||||
|
||||
if ($container->fileExists($file = $dirname.'/Resources/config/serialization.xml', false)) {
|
||||
if ($container->fileExists($file = $configDir.'/serialization.xml', false)) {
|
||||
$fileRecorder('xml', $file);
|
||||
}
|
||||
|
||||
if (
|
||||
$container->fileExists($file = $dirname.'/Resources/config/serialization.yaml', false) ||
|
||||
$container->fileExists($file = $dirname.'/Resources/config/serialization.yml', false)
|
||||
$container->fileExists($file = $configDir.'/serialization.yaml', false) ||
|
||||
$container->fileExists($file = $configDir.'/serialization.yml', false)
|
||||
) {
|
||||
$fileRecorder('yml', $file);
|
||||
}
|
||||
|
||||
if ($container->fileExists($dir = $dirname.'/Resources/config/serialization', '/^$/')) {
|
||||
if ($container->fileExists($dir = $configDir.'/serialization', '/^$/')) {
|
||||
$this->registerMappingFilesFromDir($dir, $fileRecorder);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\LegacyBundle\Entity;
|
||||
|
||||
class LegacyPerson
|
||||
{
|
||||
public $name;
|
||||
public $age;
|
||||
|
||||
public function __construct(string $name, string $age)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->age = $age;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\LegacyBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class LegacyBundle extends Bundle
|
||||
{
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\LegacyBundle\Entity\LegacyPerson:
|
||||
attributes:
|
||||
name:
|
||||
serialized_name: 'full_name'
|
@ -0,0 +1,5 @@
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\LegacyBundle\Entity\LegacyPerson:
|
||||
properties:
|
||||
age:
|
||||
- GreaterThan:
|
||||
value: 18
|
@ -0,0 +1 @@
|
||||
ok_label: OK
|
@ -0,0 +1 @@
|
||||
OK
|
@ -0,0 +1,4 @@
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ModernBundle\src\Entity\ModernPerson:
|
||||
attributes:
|
||||
name:
|
||||
serialized_name: 'full_name'
|
@ -0,0 +1,5 @@
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ModernBundle\src\Entity\ModernPerson:
|
||||
properties:
|
||||
age:
|
||||
- GreaterThan:
|
||||
value: 18
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ModernBundle\src\Entity;
|
||||
|
||||
class ModernPerson
|
||||
{
|
||||
public $name;
|
||||
public $age;
|
||||
|
||||
public function __construct(string $name, string $age)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->age = $age;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ModernBundle\src;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ModernBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return \dirname(__DIR__);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
OK
|
@ -0,0 +1 @@
|
||||
ok_label: OK
|
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\LegacyBundle\Entity\LegacyPerson;
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ModernBundle\src\Entity\ModernPerson;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class BundlePathsTest extends AbstractWebTestCase
|
||||
{
|
||||
public function testBundlePublicDir()
|
||||
{
|
||||
$kernel = static::bootKernel(['test_case' => 'BundlePaths']);
|
||||
$projectDir = sys_get_temp_dir().'/'.uniqid('sf_bundle_paths', true);
|
||||
|
||||
$fs = new Filesystem();
|
||||
$fs->mkdir($projectDir.'/public');
|
||||
$command = (new Application($kernel))->add(new AssetsInstallCommand($fs, $projectDir));
|
||||
$exitCode = (new CommandTester($command))->execute(['target' => $projectDir.'/public']);
|
||||
|
||||
$this->assertSame(0, $exitCode);
|
||||
$this->assertFileExists($projectDir.'/public/bundles/modern/modern.css');
|
||||
$this->assertFileExists($projectDir.'/public/bundles/legacy/legacy.css');
|
||||
|
||||
$fs->remove($projectDir);
|
||||
}
|
||||
|
||||
public function testBundleTwigTemplatesDir()
|
||||
{
|
||||
static::bootKernel(['test_case' => 'BundlePaths']);
|
||||
$twig = static::$container->get('twig');
|
||||
$bundlesMetadata = static::$container->getParameter('kernel.bundles_metadata');
|
||||
|
||||
$this->assertSame([$bundlesMetadata['LegacyBundle']['path'].'/Resources/views'], $twig->getLoader()->getPaths('Legacy'));
|
||||
$this->assertSame("OK\n", $twig->render('@Legacy/index.html.twig'));
|
||||
|
||||
$this->assertSame([$bundlesMetadata['ModernBundle']['path'].'/templates'], $twig->getLoader()->getPaths('Modern'));
|
||||
$this->assertSame("OK\n", $twig->render('@Modern/index.html.twig'));
|
||||
}
|
||||
|
||||
public function testBundleTranslationsDir()
|
||||
{
|
||||
static::bootKernel(['test_case' => 'BundlePaths']);
|
||||
$translator = static::$container->get('translator');
|
||||
|
||||
$this->assertSame('OK', $translator->trans('ok_label', [], 'legacy'));
|
||||
$this->assertSame('OK', $translator->trans('ok_label', [], 'modern'));
|
||||
}
|
||||
|
||||
public function testBundleValidationConfigDir()
|
||||
{
|
||||
static::bootKernel(['test_case' => 'BundlePaths']);
|
||||
$validator = static::$container->get('validator');
|
||||
|
||||
$this->assertTrue($validator->hasMetadataFor(LegacyPerson::class));
|
||||
$this->assertCount(1, $constraintViolationList = $validator->validate(new LegacyPerson('john', 5)));
|
||||
$this->assertSame('This value should be greater than 18.', $constraintViolationList->get(0)->getMessage());
|
||||
|
||||
$this->assertTrue($validator->hasMetadataFor(ModernPerson::class));
|
||||
$this->assertCount(1, $constraintViolationList = $validator->validate(new ModernPerson('john', 5)));
|
||||
$this->assertSame('This value should be greater than 18.', $constraintViolationList->get(0)->getMessage());
|
||||
}
|
||||
|
||||
public function testBundleSerializationConfigDir()
|
||||
{
|
||||
static::bootKernel(['test_case' => 'BundlePaths']);
|
||||
$serializer = static::$container->get('serializer');
|
||||
|
||||
$this->assertEquals(['full_name' => 'john', 'age' => 5], $serializer->normalize(new LegacyPerson('john', 5), 'json'));
|
||||
$this->assertEquals(['full_name' => 'john', 'age' => 5], $serializer->normalize(new ModernPerson('john', 5), 'json'));
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\LegacyBundle\LegacyBundle;
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ModernBundle\src\ModernBundle;
|
||||
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||
|
||||
return [
|
||||
new FrameworkBundle(),
|
||||
new TwigBundle(),
|
||||
new ModernBundle(),
|
||||
new LegacyBundle(),
|
||||
];
|
@ -0,0 +1,11 @@
|
||||
imports:
|
||||
- { resource: ../config/default.yml }
|
||||
|
||||
framework:
|
||||
translator: true
|
||||
validation: true
|
||||
serializer: true
|
||||
|
||||
twig:
|
||||
strict_variables: '%kernel.debug%'
|
||||
exception_controller: ~
|
@ -161,7 +161,7 @@ class TwigExtension extends Extension
|
||||
}
|
||||
$container->addResource(new FileExistenceResource($defaultOverrideBundlePath));
|
||||
|
||||
if (file_exists($dir = $bundle['path'].'/Resources/views')) {
|
||||
if (file_exists($dir = $bundle['path'].'/Resources/views') || file_exists($dir = $bundle['path'].'/templates')) {
|
||||
$bundleHierarchy[$name][] = $dir;
|
||||
}
|
||||
$container->addResource(new FileExistenceResource($dir));
|
||||
|
@ -56,9 +56,11 @@ class TemplateIterator implements \IteratorAggregate
|
||||
$name = substr($name, 0, -6);
|
||||
}
|
||||
|
||||
$bundleTemplatesDir = is_dir($bundle->getPath().'/Resources/views') ? $bundle->getPath().'/Resources/views' : $bundle->getPath().'/templates';
|
||||
|
||||
$templates = array_merge(
|
||||
$templates,
|
||||
$this->findTemplatesInDirectory($bundle->getPath().'/Resources/views', $name),
|
||||
$this->findTemplatesInDirectory($bundleTemplatesDir, $name),
|
||||
null !== $this->defaultPath ? $this->findTemplatesInDirectory($this->defaultPath.'/bundles/'.$bundle->getName(), $name) : []
|
||||
);
|
||||
}
|
||||
|
@ -140,10 +140,8 @@ class Container implements ContainerInterface, ResetInterface
|
||||
*
|
||||
* Setting a synthetic service to null resets it: has() returns false and get()
|
||||
* behaves in the same way as if the service was never created.
|
||||
*
|
||||
* @param object $service The service instance
|
||||
*/
|
||||
public function set(string $id, $service)
|
||||
public function set(string $id, ?object $service)
|
||||
{
|
||||
// Runs the internal initializer; used by the dumped container to include always-needed files
|
||||
if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) {
|
||||
@ -209,7 +207,7 @@ class Container implements ContainerInterface, ResetInterface
|
||||
* @param string $id The service identifier
|
||||
* @param int $invalidBehavior The behavior when the service does not exist
|
||||
*
|
||||
* @return object The associated service
|
||||
* @return object|null The associated service
|
||||
*
|
||||
* @throws ServiceCircularReferenceException When a circular reference is detected
|
||||
* @throws ServiceNotFoundException When the service is not defined
|
||||
|
@ -479,11 +479,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
/**
|
||||
* Sets a service.
|
||||
*
|
||||
* @param object $service The service instance
|
||||
*
|
||||
* @throws BadMethodCallException When this ContainerBuilder is compiled
|
||||
*/
|
||||
public function set(string $id, $service)
|
||||
public function set(string $id, ?object $service)
|
||||
{
|
||||
if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) {
|
||||
// setting a synthetic service on a compiled container is alright
|
||||
@ -526,7 +524,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
* @param string $id The service identifier
|
||||
* @param int $invalidBehavior The behavior when the service does not exist
|
||||
*
|
||||
* @return object The associated service
|
||||
* @return object|null The associated service
|
||||
*
|
||||
* @throws InvalidArgumentException when no definitions are available
|
||||
* @throws ServiceCircularReferenceException When a circular reference is detected
|
||||
|
@ -32,10 +32,8 @@ interface ContainerInterface extends PsrContainerInterface
|
||||
|
||||
/**
|
||||
* Sets a service.
|
||||
*
|
||||
* @param object $service The service instance
|
||||
*/
|
||||
public function set(string $id, $service);
|
||||
public function set(string $id, ?object $service);
|
||||
|
||||
/**
|
||||
* Gets a service.
|
||||
|
@ -1573,16 +1573,17 @@ class ContainerBuilderTest extends TestCase
|
||||
|
||||
public function testScalarService()
|
||||
{
|
||||
$c = new ContainerBuilder();
|
||||
$c->register('foo', 'string')
|
||||
->setPublic(true)
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'string')
|
||||
->setFactory([ScalarFactory::class, 'getSomeValue'])
|
||||
;
|
||||
$container->register('bar', 'stdClass')
|
||||
->setProperty('foo', new Reference('foo'))
|
||||
->setPublic(true)
|
||||
;
|
||||
$container->compile();
|
||||
|
||||
$c->compile();
|
||||
|
||||
$this->assertTrue($c->has('foo'));
|
||||
$this->assertSame('some value', $c->get('foo'));
|
||||
$this->assertSame('some value', $container->get('bar')->foo);
|
||||
}
|
||||
|
||||
public function testWither()
|
||||
|
@ -288,16 +288,6 @@ class ContainerTest extends TestCase
|
||||
$this->assertTrue($sc->has('foo.baz'), '->has() returns true if a get*Method() is defined');
|
||||
}
|
||||
|
||||
public function testScalarService()
|
||||
{
|
||||
$c = new Container();
|
||||
|
||||
$c->set('foo', 'some value');
|
||||
|
||||
$this->assertTrue($c->has('foo'));
|
||||
$this->assertSame('some value', $c->get('foo'));
|
||||
}
|
||||
|
||||
public function testInitialized()
|
||||
{
|
||||
$sc = new ProjectServiceContainer();
|
||||
|
@ -1282,10 +1282,12 @@ class PhpDumperTest extends TestCase
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'string')
|
||||
->setPublic(true)
|
||||
->setFactory([ScalarFactory::class, 'getSomeValue'])
|
||||
;
|
||||
|
||||
$container->register('bar', 'stdClass')
|
||||
->setProperty('foo', new Reference('foo'))
|
||||
->setPublic(true)
|
||||
;
|
||||
$container->compile();
|
||||
|
||||
$dumper = new PhpDumper($container);
|
||||
@ -1293,8 +1295,7 @@ class PhpDumperTest extends TestCase
|
||||
|
||||
$container = new \Symfony_DI_PhpDumper_Test_Scalar_Service();
|
||||
|
||||
$this->assertTrue($container->has('foo'));
|
||||
$this->assertSame('some value', $container->get('foo'));
|
||||
$this->assertSame('some value', $container->get('bar')->foo);
|
||||
}
|
||||
|
||||
public function testWither()
|
||||
|
@ -133,11 +133,6 @@ abstract class Bundle implements BundleInterface
|
||||
{
|
||||
}
|
||||
|
||||
public function getPublicDir(): string
|
||||
{
|
||||
return 'Resources/public';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundle's container extension class.
|
||||
*
|
||||
|
@ -68,9 +68,4 @@ interface BundleInterface extends ContainerAwareInterface
|
||||
* @return string The Bundle absolute path
|
||||
*/
|
||||
public function getPath();
|
||||
|
||||
/**
|
||||
* Returns relative path to the public assets directory.
|
||||
*/
|
||||
public function getPublicDir(): string;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ CHANGELOG
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* added the `getPublicDir()` method to `BundleInterface`.
|
||||
* removed the first and second constructor argument of `ConfigDataCollector`
|
||||
* removed `ConfigDataCollector::getApplicationName()`
|
||||
* removed `ConfigDataCollector::getApplicationVersion()`
|
||||
@ -27,9 +26,8 @@ CHANGELOG
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* Implementing the `BundleInterface` without implementing the `getPublicDir()` method is deprecated.
|
||||
This method will be added to the interface in 5.0.
|
||||
* The `DebugHandlersListener` class has been marked as `final`
|
||||
* Added new Bundle directory convention consistent with standard skeletons
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
@ -306,7 +306,7 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase
|
||||
public function testBindScalarValueToControllerArgument($bindingKey)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$resolver = $container->register('argument_resolver.service')->addArgument([]);
|
||||
$resolver = $container->register('argument_resolver.service', 'stdClass')->addArgument([]);
|
||||
|
||||
$container->register('foo', ArgumentWithoutTypeController::class)
|
||||
->setBindings([$bindingKey => '%foo%'])
|
||||
@ -317,19 +317,13 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase
|
||||
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||
$pass->process($container);
|
||||
|
||||
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
|
||||
$locatorId = (string) $resolver->getArgument(0);
|
||||
$container->getDefinition($locatorId)->setPublic(true);
|
||||
|
||||
$locator = $container->getDefinition((string) $locator['foo::fooAction']->getValues()[0]);
|
||||
$container->compile();
|
||||
|
||||
// assert the locator has a someArg key
|
||||
$arguments = $locator->getArgument(0);
|
||||
$this->assertArrayHasKey('someArg', $arguments);
|
||||
$this->assertInstanceOf(ServiceClosureArgument::class, $arguments['someArg']);
|
||||
// get the Reference that someArg points to
|
||||
$reference = $arguments['someArg']->getValues()[0];
|
||||
// make sure this service *does* exist and returns the correct value
|
||||
$this->assertTrue($container->has((string) $reference));
|
||||
$this->assertSame('foo_val', $container->get((string) $reference));
|
||||
$locator = $container->get($locatorId);
|
||||
$this->assertSame('foo_val', $locator->get('foo::fooAction')->get('someArg'));
|
||||
}
|
||||
|
||||
public function provideBindScalarValueToControllerArgument()
|
||||
|
@ -584,7 +584,7 @@ EOF;
|
||||
{
|
||||
$bundle = $this
|
||||
->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')
|
||||
->setMethods(['getPath', 'getPublicDir', 'getParent', 'getName'])
|
||||
->setMethods(['getPath', 'getParent', 'getName'])
|
||||
->disableOriginalConstructor()
|
||||
;
|
||||
|
||||
@ -606,12 +606,6 @@ EOF;
|
||||
->willReturn($dir)
|
||||
;
|
||||
|
||||
$bundle
|
||||
->expects($this->any())
|
||||
->method('getPublicDir')
|
||||
->willReturn('Resources/public')
|
||||
;
|
||||
|
||||
$bundle
|
||||
->expects($this->any())
|
||||
->method('getParent')
|
||||
|
@ -55,7 +55,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
||||
{
|
||||
$handle = fopen('php://temp,', 'w+');
|
||||
|
||||
if (!\is_array($data)) {
|
||||
if (!is_iterable($data)) {
|
||||
$data = [[$data]];
|
||||
} elseif (empty($data)) {
|
||||
$data = [[]];
|
||||
@ -192,10 +192,10 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
||||
/**
|
||||
* Flattens an array and generates keys including the path.
|
||||
*/
|
||||
private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
|
||||
private function flatten(iterable $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
if (is_iterable($value)) {
|
||||
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator, $escapeFormulas);
|
||||
} else {
|
||||
if ($escapeFormulas && \in_array(substr((string) $value, 0, 1), $this->formulasStartCharacters, true)) {
|
||||
@ -228,7 +228,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function extractHeaders(array $data): array
|
||||
private function extractHeaders(iterable $data): array
|
||||
{
|
||||
$headers = [];
|
||||
$flippedHeaders = [];
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\Serializer\Encoder;
|
||||
use Symfony\Component\Serializer\Exception\RuntimeException;
|
||||
use Symfony\Component\Yaml\Dumper;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Encodes YAML data.
|
||||
@ -25,6 +26,8 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
|
||||
const FORMAT = 'yaml';
|
||||
private const ALTERNATIVE_FORMAT = 'yml';
|
||||
|
||||
public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects';
|
||||
|
||||
private $dumper;
|
||||
private $parser;
|
||||
private $defaultContext = ['yaml_inline' => 0, 'yaml_indent' => 0, 'yaml_flags' => 0];
|
||||
@ -47,6 +50,10 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
|
||||
{
|
||||
$context = array_merge($this->defaultContext, $context);
|
||||
|
||||
if (isset($context[self::PRESERVE_EMPTY_OBJECTS])) {
|
||||
$context['yaml_flags'] |= Yaml::DUMP_OBJECT_AS_MAP;
|
||||
}
|
||||
|
||||
return $this->dumper->dump($data, $context['yaml_inline'], $context['yaml_indent'], $context['yaml_flags']);
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,8 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
*/
|
||||
public const DEEP_OBJECT_TO_POPULATE = 'deep_object_to_populate';
|
||||
|
||||
public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects';
|
||||
|
||||
private $propertyTypeExtractor;
|
||||
private $typesCache = [];
|
||||
private $attributesCache = [];
|
||||
@ -199,6 +201,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute, $format)), $class, $format, $context);
|
||||
}
|
||||
|
||||
if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) {
|
||||
return new \ArrayObject();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ interface NormalizerInterface
|
||||
* @param string $format Format the normalization result will be encoded as
|
||||
* @param array $context Context options for the normalizer
|
||||
*
|
||||
* @return array|string|int|float|bool
|
||||
* @return array|string|int|float|bool|\ArrayObject \ArrayObject is used to make sure an empty object is encoded as an object not an array
|
||||
*
|
||||
* @throws InvalidArgumentException Occurs when the object given is not an attempted type for the normalizer
|
||||
* @throws CircularReferenceException Occurs when the normalizer detects a circular reference when no circular
|
||||
|
@ -311,6 +311,43 @@ CSV
|
||||
]));
|
||||
}
|
||||
|
||||
public function testEncodeArrayObject()
|
||||
{
|
||||
$value = new \ArrayObject(['foo' => 'hello', 'bar' => 'hey ho']);
|
||||
|
||||
$this->assertEquals(<<<'CSV'
|
||||
foo,bar
|
||||
hello,"hey ho"
|
||||
|
||||
CSV
|
||||
, $this->encoder->encode($value, 'csv'));
|
||||
|
||||
$value = new \ArrayObject();
|
||||
|
||||
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
|
||||
}
|
||||
|
||||
public function testEncodeNestedArrayObject()
|
||||
{
|
||||
$value = new \ArrayObject(['foo' => new \ArrayObject(['nested' => 'value']), 'bar' => new \ArrayObject(['another' => 'word'])]);
|
||||
|
||||
$this->assertEquals(<<<'CSV'
|
||||
foo.nested,bar.another
|
||||
value,word
|
||||
|
||||
CSV
|
||||
, $this->encoder->encode($value, 'csv'));
|
||||
}
|
||||
|
||||
public function testEncodeEmptyArrayObject()
|
||||
{
|
||||
$value = new \ArrayObject();
|
||||
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
|
||||
|
||||
$value = ['foo' => new \ArrayObject()];
|
||||
$this->assertEquals("\n\n", $this->encoder->encode($value, 'csv'));
|
||||
}
|
||||
|
||||
public function testSupportsDecoding()
|
||||
{
|
||||
$this->assertTrue($this->encoder->supportsDecoding('csv'));
|
||||
|
@ -46,6 +46,8 @@ class JsonEncodeTest extends TestCase
|
||||
return [
|
||||
[[], '[]', []],
|
||||
[[], '{}', ['json_encode_options' => JSON_FORCE_OBJECT]],
|
||||
[new \ArrayObject(), '{}', []],
|
||||
[new \ArrayObject(['foo' => 'bar']), '{"foo":"bar"}', []],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,26 @@ class XmlEncoderTest extends TestCase
|
||||
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
|
||||
}
|
||||
|
||||
public function testEncodeArrayObject()
|
||||
{
|
||||
$obj = new \ArrayObject(['foo' => 'bar']);
|
||||
|
||||
$expected = '<?xml version="1.0"?>'."\n".
|
||||
'<response><foo>bar</foo></response>'."\n";
|
||||
|
||||
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
|
||||
}
|
||||
|
||||
public function testEncodeEmptyArrayObject()
|
||||
{
|
||||
$obj = new \ArrayObject();
|
||||
|
||||
$expected = '<?xml version="1.0"?>'."\n".
|
||||
'<response/>'."\n";
|
||||
|
||||
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
|
||||
}
|
||||
|
||||
public function testDocTypeIsNotAllowed()
|
||||
{
|
||||
$this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');
|
||||
|
@ -28,6 +28,8 @@ class YamlEncoderTest extends TestCase
|
||||
|
||||
$this->assertEquals('foo', $encoder->encode('foo', 'yaml'));
|
||||
$this->assertEquals('{ foo: 1 }', $encoder->encode(['foo' => 1], 'yaml'));
|
||||
$this->assertEquals('null', $encoder->encode(new \ArrayObject(['foo' => 1]), 'yaml'));
|
||||
$this->assertEquals('{ foo: 1 }', $encoder->encode(new \ArrayObject(['foo' => 1]), 'yaml', ['preserve_empty_objects' => true]));
|
||||
}
|
||||
|
||||
public function testSupportsEncoding()
|
||||
|
@ -198,12 +198,25 @@ class AbstractObjectNormalizerTest extends TestCase
|
||||
'allow_extra_attributes' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testNormalizeEmptyObject()
|
||||
{
|
||||
$normalizer = new AbstractObjectNormalizerDummy();
|
||||
|
||||
// This results in objects turning into arrays in some encoders
|
||||
$normalizedData = $normalizer->normalize(new EmptyDummy());
|
||||
$this->assertEquals([], $normalizedData);
|
||||
|
||||
$normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', ['preserve_empty_objects' => true]);
|
||||
$this->assertEquals(new \ArrayObject(), $normalizedData);
|
||||
}
|
||||
}
|
||||
|
||||
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
|
||||
{
|
||||
protected function extractAttributes($object, $format = null, array $context = [])
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getAttributeValue($object, $attribute, $format = null, array $context = [])
|
||||
@ -233,6 +246,10 @@ class Dummy
|
||||
public $baz;
|
||||
}
|
||||
|
||||
class EmptyDummy
|
||||
{
|
||||
}
|
||||
|
||||
class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
|
||||
{
|
||||
public function __construct()
|
||||
|
@ -192,6 +192,19 @@ class SerializerTest extends TestCase
|
||||
$this->assertEquals(json_encode($data), $result);
|
||||
}
|
||||
|
||||
public function testSerializeEmpty()
|
||||
{
|
||||
$serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]);
|
||||
$data = ['foo' => new \stdClass()];
|
||||
|
||||
//Old buggy behaviour
|
||||
$result = $serializer->serialize($data, 'json');
|
||||
$this->assertEquals('{"foo":[]}', $result);
|
||||
|
||||
$result = $serializer->serialize($data, 'json', ['preserve_empty_objects' => true]);
|
||||
$this->assertEquals('{"foo":{}}', $result);
|
||||
}
|
||||
|
||||
public function testSerializeNoEncoder()
|
||||
{
|
||||
$this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');
|
||||
|
@ -126,7 +126,7 @@ EOF
|
||||
// otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed
|
||||
// also, the regexp matching must be case-insensitive, as defined for 'target-language' values
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language
|
||||
$expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.xlf/', $normalizedLocale) : sprintf('/^(.*\.(?i:%s)\.xlf|(?i:%s)\..*\.xlf)/', $normalizedLocale, $normalizedLocale);
|
||||
$expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocale) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocale, $normalizedLocale);
|
||||
|
||||
if (0 === preg_match($expectedFilenamePattern, basename($file))) {
|
||||
$errors[] = [
|
||||
|
Reference in New Issue
Block a user