Skip to content

Commit 660a224

Browse files
Merge branch '7.4' into 8.0
* 7.4: [Routing] Fix case sensitivity for static host matching in compiled routes [Routing] Fix localized prefix updates breaking aliases [Routing] Fix addNamePrefix breaking aliases to external routes [Workflow] Fix MethodMarkingStore crash with inherited uninitialized properties [AssetMapper] Fix entrypoint status lost during update [ObjectMapper] map to embedded object with property access [FrameworkBundle] Make `APP_*_DIR` relative to the project directory [Console] Fix completion for global options values
2 parents c9bda39 + a76ccea commit 660a224

File tree

19 files changed

+309
-22
lines changed

19 files changed

+309
-22
lines changed

src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,30 +108,30 @@ private function getBundlesPath(): string
108108

109109
public function getCacheDir(): string
110110
{
111-
if (isset($_SERVER['APP_CACHE_DIR'])) {
112-
return $_SERVER['APP_CACHE_DIR'].'/'.$this->environment;
111+
if (null !== $dir = $_SERVER['APP_CACHE_DIR'] ?? null) {
112+
return $this->getEnvDir($dir);
113113
}
114114

115115
return parent::getCacheDir();
116116
}
117117

118118
public function getBuildDir(): string
119119
{
120-
if (isset($_SERVER['APP_BUILD_DIR'])) {
121-
return $_SERVER['APP_BUILD_DIR'].'/'.$this->environment;
120+
if (null !== $dir = $_SERVER['APP_BUILD_DIR'] ?? null) {
121+
return $this->getEnvDir($dir);
122122
}
123123

124124
return parent::getBuildDir();
125125
}
126126

127127
public function getShareDir(): ?string
128128
{
129-
if (isset($_SERVER['APP_SHARE_DIR'])) {
130-
if (false === $dir = filter_var($_SERVER['APP_SHARE_DIR'], \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE) ?? $_SERVER['APP_SHARE_DIR']) {
129+
if (null !== $dir = $_SERVER['APP_SHARE_DIR'] ?? null) {
130+
if (false === $dir = filter_var($dir, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE) ?? $dir) {
131131
return null;
132132
}
133133
if (\is_string($dir)) {
134-
return $dir.'/'.$this->environment;
134+
return $this->getEnvDir($dir);
135135
}
136136
}
137137

@@ -267,4 +267,16 @@ protected function getKernelParameters(): array
267267

268268
return $parameters;
269269
}
270+
271+
private function getEnvDir(string $dir): string
272+
{
273+
if ('' !== $dir && \in_array($dir[0], ['/', '\\'], true)) {
274+
return $dir.'/'.$this->environment;
275+
}
276+
if ('\\' === \DIRECTORY_SEPARATOR && ':' === ($dir[1] ?? '') && 65 <= \ord($dir[0]) && \ord($dir[0]) <= 122 && !\in_array($dir[0], ['[', ']', '^', '_', '`'], true)) {
277+
return $dir.'/'.$this->environment;
278+
}
279+
280+
return $this->getProjectDir().'/'.$dir.'/'.$this->environment;
281+
}
270282
}

src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHPUnit\Framework\TestCase;
1616
use Psr\Log\NullLogger;
1717
use Symfony\Bundle\FrameworkBundle\Console\Application;
18+
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
1819
use Symfony\Component\Console\Attribute\AsCommand;
1920
use Symfony\Component\Console\Input\ArrayInput;
2021
use Symfony\Component\Console\Output\BufferedOutput;
@@ -239,4 +240,33 @@ public function testGetKernelParametersWithBundlesFile()
239240
'TestBundle' => ['test' => true, 'dev' => true],
240241
], $parameters['.kernel.bundles_definition']);
241242
}
243+
244+
public function testRelativeEnvDirsAreResolvedFromProjectDir()
245+
{
246+
$_SERVER['APP_CACHE_DIR'] = 'var/custom-cache';
247+
$_SERVER['APP_BUILD_DIR'] = 'var/custom-build';
248+
$_SERVER['APP_SHARE_DIR'] = 'var/custom-share';
249+
250+
$projectDir = sys_get_temp_dir().'/sf_env_dir_kernel';
251+
$kernel = new EnvDirKernel($projectDir);
252+
253+
$this->assertSame($projectDir.'/var/custom-cache/test', $kernel->getCacheDir());
254+
$this->assertSame($projectDir.'/var/custom-build/test', $kernel->getBuildDir());
255+
$this->assertSame($projectDir.'/var/custom-share/test', $kernel->getShareDir());
256+
}
257+
}
258+
259+
class EnvDirKernel extends Kernel
260+
{
261+
use MicroKernelTrait;
262+
263+
public function __construct(private readonly string $projectDir)
264+
{
265+
parent::__construct('test', false);
266+
}
267+
268+
public function getProjectDir(): string
269+
{
270+
return $this->projectDir;
271+
}
242272
}

src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ private function updateImportMapConfig(bool $update, array $packagesToRequire, a
112112
$entry->packageModuleSpecifier,
113113
null,
114114
$importName,
115+
null,
116+
$entry->isEntrypoint,
115117
);
116118

117119
// remove it: then it will be re-added

src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,34 @@ public static function getPackageNameTests(): iterable
363363
];
364364
}
365365

366+
public function testUpdatePreservesEntrypointStatus()
367+
{
368+
$manager = $this->createImportMapManager();
369+
370+
$this->mockImportMap([
371+
self::createRemoteEntry('my_entrypoint', version: '1.0.0', isEntrypoint: true),
372+
self::createRemoteEntry('standard_lib', version: '2.0.0', isEntrypoint: false),
373+
]);
374+
375+
$this->packageResolver->expects($this->once())
376+
->method('resolvePackages')
377+
->willReturn([
378+
self::resolvedPackage('my_entrypoint', '1.0.1', isEntrypoint: true),
379+
self::resolvedPackage('standard_lib', '2.1.0'),
380+
]);
381+
382+
$this->configReader->expects($this->once())
383+
->method('writeEntries')
384+
->with($this->callback(function (ImportMapEntries $entries) {
385+
$this->assertTrue($entries->get('my_entrypoint')->isEntrypoint, 'Entrypoint status lost!');
386+
$this->assertFalse($entries->get('standard_lib')->isEntrypoint);
387+
388+
return true;
389+
}));
390+
391+
$manager->update();
392+
}
393+
366394
private function createImportMapManager(): ImportMapManager
367395
{
368396
$this->assetMapper = $this->createMock(AssetMapperInterface::class);
@@ -387,10 +415,10 @@ private function createImportMapManager(): ImportMapManager
387415
);
388416
}
389417

390-
private static function resolvedPackage(string $packageName, string $version, ImportMapType $type = ImportMapType::JS)
418+
private static function resolvedPackage(string $packageName, string $version, ImportMapType $type = ImportMapType::JS, bool $isEntrypoint = false)
391419
{
392420
return new ResolvedImportMapPackage(
393-
new PackageRequireOptions($packageName),
421+
new PackageRequireOptions($packageName, entrypoint: $isEntrypoint),
394422
$version,
395423
$type,
396424
);
@@ -414,11 +442,11 @@ private static function createLocalEntry(string $importName, string $path, Impor
414442
return ImportMapEntry::createLocal($importName, $type, path: $path, isEntrypoint: $isEntrypoint);
415443
}
416444

417-
private static function createRemoteEntry(string $importName, string $version, ?string $path = null, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null): ImportMapEntry
445+
private static function createRemoteEntry(string $importName, string $version, ?string $path = null, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null, bool $isEntrypoint = false): ImportMapEntry
418446
{
419447
$packageSpecifier ??= $importName;
420448
$path ??= '/vendor/any-path.js';
421449

422-
return ImportMapEntry::createRemote($importName, $type, path: $path, version: $version, packageModuleSpecifier: $packageSpecifier, isEntrypoint: false);
450+
return ImportMapEntry::createRemote($importName, $type, path: $path, version: $version, packageModuleSpecifier: $packageSpecifier, isEntrypoint: $isEntrypoint);
423451
}
424452
}

src/Symfony/Component/Console/Application.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,15 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
421421
if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
422422
$suggestions->suggestOptions($this->getDefinition()->getOptions());
423423
}
424+
425+
if (
426+
CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType()
427+
&& ($definition = $this->getDefinition())->hasOption($input->getCompletionName())
428+
) {
429+
$definition->getOption($input->getCompletionName())->complete($input, $suggestions);
430+
431+
return;
432+
}
424433
}
425434

426435
/**

src/Symfony/Component/Console/Tests/Command/CompleteCommandTest.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Console\Completion\CompletionSuggestions;
2121
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
2222
use Symfony\Component\Console\Input\InputArgument;
23+
use Symfony\Component\Console\Input\InputOption;
2324
use Symfony\Component\Console\Output\OutputInterface;
2425
use Symfony\Component\Console\Tester\CommandTester;
2526

@@ -35,6 +36,8 @@ protected function setUp(): void
3536

3637
$this->application = new Application();
3738
$this->application->addCommand(new CompleteCommandTest_HelloCommand());
39+
$this->application->getDefinition()
40+
->addOption(new InputOption('global-option', null, InputOption::VALUE_REQUIRED, suggestedValues: ['foo', 'bar', 'baz']));
3841

3942
$this->command->setApplication($this->application);
4043
$this->tester = new CommandTester($this->command);
@@ -114,10 +117,12 @@ public function testCompleteCommandInputDefinition(array $input, array $suggesti
114117

115118
public static function provideCompleteCommandInputDefinitionInputs()
116119
{
117-
yield 'definition' => [['bin/console', 'hello', '-'], ['--help', '--silent', '--quiet', '--verbose', '--version', '--ansi', '--no-ansi', '--no-interaction']];
120+
yield 'definition' => [['bin/console', 'hello', '-'], ['--help', '--silent', '--quiet', '--verbose', '--version', '--ansi', '--no-ansi', '--no-interaction', '--global-option']];
118121
yield 'custom' => [['bin/console', 'hello'], ['Fabien', 'Robin', 'Wouter']];
119-
yield 'definition-aliased' => [['bin/console', 'ahoy', '-'], ['--help', '--silent', '--quiet', '--verbose', '--version', '--ansi', '--no-ansi', '--no-interaction']];
122+
yield 'definition-aliased' => [['bin/console', 'ahoy', '-'], ['--help', '--silent', '--quiet', '--verbose', '--version', '--ansi', '--no-ansi', '--no-interaction', '--global-option']];
120123
yield 'custom-aliased' => [['bin/console', 'ahoy'], ['Fabien', 'Robin', 'Wouter']];
124+
yield 'global-option-values' => [['bin/console', '--global-option'], ['foo', 'bar', 'baz']];
125+
yield 'global-option-with-command-values' => [['bin/console', 'ahoy', '--global-option'], ['foo', 'bar', 'baz']];
121126
}
122127

123128
private function execute(array $input)
@@ -142,6 +147,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
142147
{
143148
if ($input->mustSuggestArgumentValuesFor('name')) {
144149
$suggestions->suggestValues(['Fabien', 'Robin', 'Wouter']);
150+
151+
return;
145152
}
153+
154+
parent::complete($input, $suggestions);
146155
}
147156
}

src/Symfony/Component/ObjectMapper/ObjectMapper.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,6 @@ private function doMap(object $source, object|string|null $target, \WeakMap $obj
150150
}
151151

152152
$targetPropertyName = $mapping->target ?? $propertyName;
153-
if (!$targetRefl->hasProperty($targetPropertyName)) {
154-
continue;
155-
}
156-
157153
$value = $this->getSourceValue($source, $mappedTarget, $value, $objectMap, $mapping);
158154
$this->storeValue($targetPropertyName, $mapToProperties, $ctorArguments, $value);
159155
}
@@ -186,7 +182,19 @@ private function doMap(object $source, object|string|null $target, \WeakMap $obj
186182
}
187183

188184
foreach ($mapToProperties as $property => $value) {
189-
$this->propertyAccessor ? $this->propertyAccessor->setValue($mappedTarget, $property, $value) : ($mappedTarget->{$property} = $value);
185+
if ($this->propertyAccessor) {
186+
if ($this->propertyAccessor->isWritable($mappedTarget, $property)) {
187+
$this->propertyAccessor->setValue($mappedTarget, $property, $value);
188+
}
189+
190+
continue;
191+
}
192+
193+
if (!$targetRefl->hasProperty($property)) {
194+
continue;
195+
}
196+
197+
$mappedTarget->{$property} = $value;
190198
}
191199

192200
return $mappedTarget;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EmbeddedMapping;
4+
5+
class Address
6+
{
7+
public string $zipcode;
8+
public string $city;
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EmbeddedMapping;
4+
5+
class User
6+
{
7+
public string $name;
8+
public Address $address;
9+
10+
public function __construct()
11+
{
12+
$this->address = new Address();
13+
}
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\EmbeddedMapping;
4+
5+
use Symfony\Component\ObjectMapper\Attribute\Map;
6+
7+
class UserDto
8+
{
9+
public function __construct(
10+
#[Map(target: 'address.zipcode')]
11+
public string $userAddressZipcode,
12+
#[Map(target: 'address.city')]
13+
public string $userAddressCity,
14+
public string $name,
15+
) {
16+
}
17+
}

0 commit comments

Comments
 (0)