-
- {%- if message.textCharset() %}
- {{- message.textBody()|convert_encoding('UTF-8', message.textCharset()) }}
- {%- else %}
- {{- message.textBody() }}
- {%- endif -%}
-
+ {% endif %}
+ {% set textBody = message.textBody() %}
+ {% if textBody is not null %}
+
+
Text Content
+
+
+ {%- if message.textCharset() %}
+ {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
+ {%- else %}
+ {{- textBody }}
+ {%- endif -%}
+
+
-
+ {% endif %}
{% for attachment in message.attachments %}
Attachment #{{ loop.index }}
diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
index d62d5d144d4ce..fb7e0de2c2d16 100644
--- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
@@ -74,7 +74,7 @@ static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime)
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
- } elseif (0 === $item->expiry) {
+ } elseif (!$item->expiry) {
$ttl = 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
diff --git a/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php
index 476cec9d15a49..f66c8370d5b91 100644
--- a/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php
@@ -79,7 +79,7 @@ static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime)
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
- } elseif (0 === $item->expiry) {
+ } elseif (!$item->expiry) {
$ttl = 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php
index 76e92983153b0..b1c09b5fe54ec 100644
--- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php
@@ -182,14 +182,14 @@ public function save(CacheItemInterface $item): bool
$now = microtime(true);
- if (0 === $expiry) {
- $expiry = \PHP_INT_MAX;
- }
-
- if (null !== $expiry && $expiry <= $now) {
- $this->deleteItem($key);
+ if (null !== $expiry) {
+ if (!$expiry) {
+ $expiry = \PHP_INT_MAX;
+ } elseif ($expiry <= $now) {
+ $this->deleteItem($key);
- return true;
+ return true;
+ }
}
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
index c90b80e4cbd51..23434cd4714c9 100644
--- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
@@ -92,7 +92,7 @@ static function (CacheItemInterface $innerItem, array $item) {
$item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
}
$innerItem->set($item["\0*\0value"]);
- $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', 0 === $item["\0*\0expiry"] ? \PHP_INT_MAX : $item["\0*\0expiry"])) : null);
+ $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null);
},
null,
CacheItem::class
diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php
index 1ec5a8cc30846..cb1c638c5c0db 100644
--- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php
@@ -55,7 +55,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
private const DEFAULT_CACHE_TTL = 8640000;
/**
- * detected eviction policy used on Redis server
+ * detected eviction policy used on Redis server.
*/
private string $redisEvictionPolicy;
diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md
index a1dc4617e5b13..1f54db7b8bd80 100644
--- a/src/Symfony/Component/Cache/CHANGELOG.md
+++ b/src/Symfony/Component/Cache/CHANGELOG.md
@@ -10,7 +10,6 @@ CHANGELOG
5.4
---
- * Make `LockRegistry` use semaphores when possible
* Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package
* Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL
* Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL
diff --git a/src/Symfony/Component/Cache/LockRegistry.php b/src/Symfony/Component/Cache/LockRegistry.php
index 5803bccc7688d..69838c417af33 100644
--- a/src/Symfony/Component/Cache/LockRegistry.php
+++ b/src/Symfony/Component/Cache/LockRegistry.php
@@ -27,7 +27,7 @@
final class LockRegistry
{
private static $openedFiles = [];
- private static $lockedKeys;
+ private static $lockedFiles;
/**
* The number of items in this list controls the max number of concurrent processes.
@@ -76,25 +76,21 @@ public static function setFiles(array $files): array
fclose($file);
}
}
- self::$openedFiles = self::$lockedKeys = [];
+ self::$openedFiles = self::$lockedFiles = [];
return $previousFiles;
}
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
{
- if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedKeys) {
+ if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) {
// disable locking on Windows by default
- self::$files = self::$lockedKeys = [];
+ self::$files = self::$lockedFiles = [];
}
- $key = unpack('i', md5($item->getKey(), true))[1];
+ $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
- if (!\function_exists('sem_get')) {
- $key = self::$files ? abs($key) % \count(self::$files) : null;
- }
-
- if (null === $key || (self::$lockedKeys[$key] ?? false) || !$lock = self::open($key)) {
+ if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) {
return $callback($item, $save);
}
@@ -102,15 +98,11 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s
try {
$locked = false;
// race to get the lock in non-blocking mode
- if ($wouldBlock = \function_exists('sem_get')) {
- $locked = @sem_acquire($lock, true);
- } else {
- $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
- }
+ $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
if ($locked || !$wouldBlock) {
$logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
- self::$lockedKeys[$key] = true;
+ self::$lockedFiles[$key] = true;
$value = $callback($item, $save);
@@ -125,25 +117,12 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s
return $value;
}
-
// if we failed the race, retry locking in blocking mode to wait for the winner
$logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
-
- if (\function_exists('sem_get')) {
- $lock = sem_get($key);
- @sem_acquire($lock);
- } else {
- flock($lock, \LOCK_SH);
- }
+ flock($lock, \LOCK_SH);
} finally {
- if ($locked) {
- if (\function_exists('sem_get')) {
- sem_remove($lock);
- } else {
- flock($lock, \LOCK_UN);
- }
- }
- unset(self::$lockedKeys[$key]);
+ flock($lock, \LOCK_UN);
+ unset(self::$lockedFiles[$key]);
}
static $signalingException, $signalingCallback;
$signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
@@ -168,10 +147,6 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s
private static function open(int $key)
{
- if (\function_exists('sem_get')) {
- return sem_get($key);
- }
-
if (null !== $h = self::$openedFiles[$key] ?? null) {
return $h;
}
diff --git a/src/Symfony/Component/Cache/Psr16Cache.php b/src/Symfony/Component/Cache/Psr16Cache.php
index 98f3994b3dde4..5c1618ddc32c3 100644
--- a/src/Symfony/Component/Cache/Psr16Cache.php
+++ b/src/Symfony/Component/Cache/Psr16Cache.php
@@ -30,7 +30,7 @@ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterf
private const METADATA_EXPIRY_OFFSET = 1527506807;
- private \Closure $createCacheItem;
+ private ?\Closure $createCacheItem = null;
private $cacheItemPrototype = null;
public function __construct(CacheItemPoolInterface $pool)
diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php
index f7c5b83124970..ab0f7da134f90 100644
--- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php
+++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php
@@ -128,7 +128,7 @@ public function testGetMetadata()
$metadata = $item->getMetadata();
$this->assertArrayHasKey(CacheItem::METADATA_CTIME, $metadata);
- $this->assertEqualsWithDelta(999, $metadata[CacheItem::METADATA_CTIME], 10);
+ $this->assertEqualsWithDelta(999, $metadata[CacheItem::METADATA_CTIME], 150);
$this->assertArrayHasKey(CacheItem::METADATA_EXPIRY, $metadata);
$this->assertEqualsWithDelta(9 + time(), $metadata[CacheItem::METADATA_EXPIRY], 1);
}
@@ -306,6 +306,15 @@ public function testWeirdDataMatchingMetadataWrappedValues()
$this->assertTrue($cache->hasItem('foobar'));
}
+
+ public function testNullByteInKey()
+ {
+ $cache = $this->createCachePool(0, __FUNCTION__);
+
+ $cache->save($cache->getItem("a\0b")->set(123));
+
+ $this->assertSame(123, $cache->getItem("a\0b")->get());
+ }
}
class NotUnserializable
diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php
new file mode 100644
index 0000000000000..46516e0095e6e
--- /dev/null
+++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Tests\Adapter;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\ProxyAdapter;
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @group integration
+ */
+class ProxyAdapterAndRedisAdapterTest extends AbstractRedisAdapterTest
+{
+ protected $skippedTests = [
+ 'testPrune' => 'RedisAdapter does not implement PruneableInterface.',
+ ];
+
+ public static function setUpBeforeClass(): void
+ {
+ parent::setUpBeforeClass();
+ self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'));
+ }
+
+ public function createCachePool($defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface
+ {
+ return new ProxyAdapter(new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), 100), 'ProxyNS', $defaultLifetime);
+ }
+
+ public function testSaveItemPermanently()
+ {
+ $setCacheItemExpiry = \Closure::bind(
+ static function (CacheItem $item, $expiry) {
+ $item->expiry = $expiry;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+
+ $cache = $this->createCachePool(1);
+ $value = rand();
+ $item = $cache->getItem('foo');
+ $setCacheItemExpiry($item, 0);
+ $cache->save($item->set($value));
+ $item = $cache->getItem('bar');
+ $setCacheItemExpiry($item, 0.0);
+ $cache->save($item->set($value));
+ $item = $cache->getItem('baz');
+ $cache->save($item->set($value));
+
+ $this->assertSame($value, $this->cache->getItem('foo')->get());
+ $this->assertSame($value, $this->cache->getItem('bar')->get());
+ $this->assertSame($value, $this->cache->getItem('baz')->get());
+
+ sleep(1);
+ $this->assertSame($value, $this->cache->getItem('foo')->get());
+ $this->assertSame($value, $this->cache->getItem('bar')->get());
+ $this->assertFalse($this->cache->getItem('baz')->isHit());
+ }
+}
diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php
index 54ce7822e06af..81e943c144f56 100644
--- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php
+++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php
@@ -14,12 +14,10 @@
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
-use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
-use Symfony\Component\Cache\LockRegistry;
use Symfony\Component\Cache\Tests\Fixtures\PrunableAdapter;
use Symfony\Component\Filesystem\Filesystem;
@@ -181,24 +179,6 @@ public function testGetItemReturnsCacheMissWhenPoolDoesNotHaveItemAndOnlyHasTags
$this->assertFalse($item->isHit());
}
- public function testLog()
- {
- $lockFiles = LockRegistry::setFiles([__FILE__]);
-
- $logger = $this->createMock(LoggerInterface::class);
- $logger
- ->expects($this->atLeastOnce())
- ->method($this->anything());
-
- $cache = new TagAwareAdapter(new ArrayAdapter());
- $cache->setLogger($logger);
-
- // Computing will produce at least one log
- $cache->get('foo', static function (): string { return 'ccc'; });
-
- LockRegistry::setFiles($lockFiles);
- }
-
/**
* @return MockObject&PruneableCacheInterface
*/
diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php b/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php
index f945c4637edab..4bc238ed341bd 100644
--- a/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php
+++ b/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php
@@ -44,7 +44,7 @@ public function testNativeUnserialize()
public function testIgbinaryUnserialize()
{
if (version_compare('3.1.6', phpversion('igbinary'), '>')) {
- $this->markTestSkipped('igbinary is not compatible with PHP 7.4.');
+ $this->markTestSkipped('igbinary needs to be v3.1.6 or higher.');
}
$marshaller = new DefaultMarshaller();
@@ -68,7 +68,7 @@ public function testNativeUnserializeNotFoundClass()
public function testIgbinaryUnserializeNotFoundClass()
{
if (version_compare('3.1.6', phpversion('igbinary'), '>')) {
- $this->markTestSkipped('igbinary is not compatible with PHP 7.4.');
+ $this->markTestSkipped('igbinary needs to be v3.1.6 or higher.');
}
$this->expectException(\DomainException::class);
@@ -96,7 +96,7 @@ public function testNativeUnserializeInvalid()
public function testIgbinaryUnserializeInvalid()
{
if (version_compare('3.1.6', phpversion('igbinary'), '>')) {
- $this->markTestSkipped('igbinary is not compatible with PHP 7.4.');
+ $this->markTestSkipped('igbinary needs to be v3.1.6 or higher.');
}
$this->expectException(\DomainException::class);
diff --git a/src/Symfony/Component/Cache/Tests/Psr16CacheWithExternalAdapter.php b/src/Symfony/Component/Cache/Tests/Psr16CacheWithExternalAdapter.php
new file mode 100644
index 0000000000000..e018a276de7ca
--- /dev/null
+++ b/src/Symfony/Component/Cache/Tests/Psr16CacheWithExternalAdapter.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Tests;
+
+use Cache\IntegrationTests\SimpleCacheTest;
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\Psr16Cache;
+use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
+
+/**
+ * @group time-sensitive
+ */
+class Psr16CacheWithExternalAdapter extends SimpleCacheTest
+{
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->skippedTests['testSetTtl'] =
+ $this->skippedTests['testSetMultipleTtl'] = 'The ExternalAdapter test class does not support TTLs.';
+ }
+
+ public function createSimpleCache(): CacheInterface
+ {
+ return new Psr16Cache(new ExternalAdapter());
+ }
+}
diff --git a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php
index 0149b7674c50e..64e35a47a174f 100644
--- a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php
+++ b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php
@@ -26,12 +26,12 @@ trait AbstractAdapterTrait
use LoggerAwareTrait;
/**
- * needs to be set by class, signature is function(string
, mixed , bool )
+ * needs to be set by class, signature is function(string , mixed , bool ).
*/
private static \Closure $createCacheItem;
/**
- * needs to be set by class, signature is function(array , string , array <&expiredIds>)
+ * needs to be set by class, signature is function(array , string , array <&expiredIds>).
*/
private static \Closure $mergeByLifetime;
diff --git a/src/Symfony/Component/Cache/Traits/ContractsTrait.php b/src/Symfony/Component/Cache/Traits/ContractsTrait.php
index 6361f95161a29..debaeacc45ad0 100644
--- a/src/Symfony/Component/Cache/Traits/ContractsTrait.php
+++ b/src/Symfony/Component/Cache/Traits/ContractsTrait.php
@@ -41,12 +41,20 @@ trait ContractsTrait
*/
public function setCallbackWrapper(?callable $callbackWrapper): callable
{
- $previousWrapper = $this->callbackWrapper ??= \Closure::fromCallable([LockRegistry::class, 'compute']);
+ if (!isset($this->callbackWrapper)) {
+ $this->callbackWrapper = \Closure::fromCallable([LockRegistry::class, 'compute']);
+
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
+ $this->setCallbackWrapper(null);
+ }
+ }
+
if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) {
$callbackWrapper = \Closure::fromCallable($callbackWrapper);
}
- $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) {
+ $previousWrapper = $this->callbackWrapper;
+ $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) {
return $callback($item, $save);
};
@@ -88,6 +96,10 @@ static function (CacheItem $item, float $startTime, ?array &$metadata) {
$this->computing[$key] = $key;
$startTime = microtime(true);
+ if (!isset($this->callbackWrapper)) {
+ $this->setCallbackWrapper($this->setCallbackWrapper(null));
+ }
+
try {
$value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) {
$setMetadata($item, $startTime, $metadata);
diff --git a/src/Symfony/Component/Cache/Traits/RedisClusterProxy.php b/src/Symfony/Component/Cache/Traits/RedisClusterProxy.php
index 741e114b4bfb6..2a8fcc8839936 100644
--- a/src/Symfony/Component/Cache/Traits/RedisClusterProxy.php
+++ b/src/Symfony/Component/Cache/Traits/RedisClusterProxy.php
@@ -10,8 +10,8 @@
*/
namespace Symfony\Component\Cache\Traits;
-/**
+/**
* @author Alessandro Chitolina
*
* @internal
diff --git a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php
index 71d156dfc47a1..22f8280d6018a 100644
--- a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php
+++ b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php
@@ -204,8 +204,6 @@ public function thenUnset(): static
/**
* Returns the related node.
*
- * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
- *
* @throws \RuntimeException
*/
public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
diff --git a/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php
index 1b55b185bd970..f8980a6e041c9 100644
--- a/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php
+++ b/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php
@@ -53,8 +53,6 @@ public function denyOverwrite(bool $deny = true): static
/**
* Returns the related node.
- *
- * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
*/
public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
{
diff --git a/src/Symfony/Component/Config/Exception/LoaderLoadException.php b/src/Symfony/Component/Config/Exception/LoaderLoadException.php
index cfd5bfe55ed15..97e804e1268cd 100644
--- a/src/Symfony/Component/Config/Exception/LoaderLoadException.php
+++ b/src/Symfony/Component/Config/Exception/LoaderLoadException.php
@@ -62,12 +62,7 @@ public function __construct(string $resource, string $sourceResource = null, int
$message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
$message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
} elseif (null !== $type) {
- // maybe there is no loader for this specific type
- if ('annotation' === $type) {
- $message .= ' Make sure to use PHP 8+ or that annotations are installed and enabled.';
- } else {
- $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
- }
+ $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
}
parent::__construct($message, $code, $previous);
diff --git a/src/Symfony/Component/Config/Tests/Exception/LoaderLoadExceptionTest.php b/src/Symfony/Component/Config/Tests/Exception/LoaderLoadExceptionTest.php
index 3150c5a83c31c..995a72963c84b 100644
--- a/src/Symfony/Component/Config/Tests/Exception/LoaderLoadExceptionTest.php
+++ b/src/Symfony/Component/Config/Tests/Exception/LoaderLoadExceptionTest.php
@@ -31,7 +31,7 @@ public function testMessageCannotLoadResourceWithType()
public function testMessageCannotLoadResourceWithAnnotationType()
{
$exception = new LoaderLoadException('resource', null, 0, null, 'annotation');
- $this->assertEquals('Cannot load resource "resource". Make sure to use PHP 8+ or that annotations are installed and enabled.', $exception->getMessage());
+ $this->assertEquals('Cannot load resource "resource". Make sure there is a loader supporting the "annotation" type.', $exception->getMessage());
}
public function testMessageCannotImportResourceFromSource()
diff --git a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php
index 184917b9eddbb..ce6772f06556d 100644
--- a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php
+++ b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php
@@ -169,6 +169,8 @@ public function getDataForPhpize(): array
[1, '1'],
[-1, '-1'],
[0777, '0777'],
+ [-511, '-0777'],
+ ['0877', '0877'],
[255, '0xFF'],
[100.0, '1e2'],
[-120.0, '-1.2E2'],
diff --git a/src/Symfony/Component/Config/Util/XmlUtils.php b/src/Symfony/Component/Config/Util/XmlUtils.php
index 1f81a2cd2a792..7d1644b701d0a 100644
--- a/src/Symfony/Component/Config/Util/XmlUtils.php
+++ b/src/Symfony/Component/Config/Util/XmlUtils.php
@@ -216,15 +216,11 @@ public static function phpize(string|\Stringable $value): mixed
case 'null' === $lowercaseValue:
return null;
case ctype_digit($value):
- $raw = $value;
- $cast = (int) $value;
-
- return '0' == $value[0] ? octdec($value) : (($raw === (string) $cast) ? $cast : $raw);
case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
$raw = $value;
$cast = (int) $value;
- return '0' == $value[1] ? octdec($value) : (($raw === (string) $cast) ? $cast : $raw);
+ return self::isOctal($value) ? \intval($value, 8) : (($raw === (string) $cast) ? $cast : $raw);
case 'true' === $lowercaseValue:
return true;
case 'false' === $lowercaseValue:
@@ -261,4 +257,13 @@ protected static function getXmlErrors(bool $internalErrors)
return $errors;
}
+
+ private static function isOctal(string $str): bool
+ {
+ if ('-' === $str[0]) {
+ $str = substr($str, 1);
+ }
+
+ return $str === '0'.decoct(\intval($str, 8));
+ }
}
diff --git a/src/Symfony/Component/Console/Completion/CompletionInput.php b/src/Symfony/Component/Console/Completion/CompletionInput.php
index eda95bef55468..368b945079484 100644
--- a/src/Symfony/Component/Console/Completion/CompletionInput.php
+++ b/src/Symfony/Component/Console/Completion/CompletionInput.php
@@ -109,12 +109,12 @@ public function bind(InputDefinition $definition): void
// complete argument value
$this->completionType = self::TYPE_ARGUMENT_VALUE;
- $arguments = $this->getArguments();
- foreach ($arguments as $argumentName => $argumentValue) {
- if (null === $argumentValue) {
+ foreach ($this->definition->getArguments() as $argumentName => $argument) {
+ if (!isset($this->arguments[$argumentName])) {
break;
}
+ $argumentValue = $this->arguments[$argumentName];
$this->completionName = $argumentName;
if (\is_array($argumentValue)) {
$this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
@@ -124,7 +124,7 @@ public function bind(InputDefinition $definition): void
}
if ($this->currentIndex >= \count($this->tokens)) {
- if (null === $arguments[$argumentName] || $this->definition->getArgument($argumentName)->isArray()) {
+ if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {
$this->completionName = $argumentName;
$this->completionValue = '';
} else {
diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php
index f89fa33c97eaf..143e4b10ae71f 100644
--- a/src/Symfony/Component/Console/Input/InputArgument.php
+++ b/src/Symfony/Component/Console/Input/InputArgument.php
@@ -105,8 +105,6 @@ public function setDefault(string|bool|int|float|array $default = null)
/**
* Returns the default value.
- *
- * @return string|bool|int|float|array|null
*/
public function getDefault(): string|bool|int|float|array|null
{
diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php
index 613af20353f08..f9d74a8961ce2 100644
--- a/src/Symfony/Component/Console/Input/InputOption.php
+++ b/src/Symfony/Component/Console/Input/InputOption.php
@@ -187,8 +187,6 @@ public function setDefault(string|bool|int|float|array $default = null)
/**
* Returns the default value.
- *
- * @return string|bool|int|float|array|null
*/
public function getDefault(): string|bool|int|float|array|null
{
diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php
index 7d5e3accb0c05..f99e685debf27 100644
--- a/src/Symfony/Component/Console/Question/Question.php
+++ b/src/Symfony/Component/Console/Question/Question.php
@@ -52,8 +52,6 @@ public function getQuestion(): string
/**
* Returns the default answer.
- *
- * @return string|bool|int|float|null
*/
public function getDefault(): string|bool|int|float|null
{
diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php
index 78af16670f7ba..56ad30a6f283c 100644
--- a/src/Symfony/Component/Console/Style/SymfonyStyle.php
+++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php
@@ -59,7 +59,7 @@ public function __construct(InputInterface $input, OutputInterface $output)
/**
* Formats a message as a block of text.
*/
- public function block(string|array $messages, ?string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
+ public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php
index a4fe5b8e4638a..619009efe29cb 100644
--- a/src/Symfony/Component/Console/Tests/ApplicationTest.php
+++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php
@@ -883,6 +883,9 @@ public function testRenderExceptionLineBreaks()
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/application_renderexception_linebreaks.txt', $tester->getDisplay(true), '->renderException() keep multiple line breaks');
}
+ /**
+ * @group transient-on-windows
+ */
public function testRenderAnonymousException()
{
$application = new Application();
@@ -906,6 +909,9 @@ public function testRenderAnonymousException()
$this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
}
+ /**
+ * @group transient-on-windows
+ */
public function testRenderExceptionStackTraceContainsRootException()
{
$application = new Application();
diff --git a/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php b/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php
index bf0ab972061bc..0e8a7f4f7fd1a 100644
--- a/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php
+++ b/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php
@@ -92,7 +92,7 @@ public function provideCompletionSuggestions()
yield 'nothing' => [
[''],
- [],
+ ['completion', 'help', 'list', 'foo:bar'],
];
yield 'command_name' => [
diff --git a/src/Symfony/Component/Console/Tests/Completion/CompletionInputTest.php b/src/Symfony/Component/Console/Tests/Completion/CompletionInputTest.php
index f83a0f89893aa..ee370076c17ac 100644
--- a/src/Symfony/Component/Console/Tests/Completion/CompletionInputTest.php
+++ b/src/Symfony/Component/Console/Tests/Completion/CompletionInputTest.php
@@ -97,6 +97,20 @@ public function provideBindWithLastArrayArgumentData()
yield [CompletionInput::fromTokens(['bin/console', 'symfony', 'sen'], 2), 'sen'];
}
+ public function testBindArgumentWithDefault()
+ {
+ $definition = new InputDefinition([
+ new InputArgument('arg-with-default', InputArgument::OPTIONAL, '', 'default'),
+ ]);
+
+ $input = CompletionInput::fromTokens(['bin/console'], 1);
+ $input->bind($definition);
+
+ $this->assertEquals(CompletionInput::TYPE_ARGUMENT_VALUE, $input->getCompletionType(), 'Unexpected type');
+ $this->assertEquals('arg-with-default', $input->getCompletionName(), 'Unexpected name');
+ $this->assertEquals('', $input->getCompletionValue(), 'Unexpected value');
+ }
+
/**
* @dataProvider provideFromStringData
*/
diff --git a/src/Symfony/Component/CssSelector/Parser/Reader.php b/src/Symfony/Component/CssSelector/Parser/Reader.php
index f926e605c59fa..c0b6923a4d1f8 100644
--- a/src/Symfony/Component/CssSelector/Parser/Reader.php
+++ b/src/Symfony/Component/CssSelector/Parser/Reader.php
@@ -60,9 +60,6 @@ public function getOffset(string $string)
return false === $position ? false : $position - $this->position;
}
- /**
- * @return array|false
- */
public function findPattern(string $pattern): array|false
{
$source = substr($this->source, $this->position);
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
index 49495f941a5ea..e9fa5a6808909 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
+use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
@@ -124,25 +125,35 @@ protected function getConstructor(Definition $definition, bool $required): ?\Ref
if ($factory) {
[$class, $method] = $factory;
+
+ if ('__construct' === $method) {
+ throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
+ }
+
if ($class instanceof Reference) {
- $class = $this->container->findDefinition((string) $class)->getClass();
+ $factoryDefinition = $this->container->findDefinition((string) $class);
+ while ((null === $class = $factoryDefinition->getClass()) && $factoryDefinition instanceof ChildDefinition) {
+ $factoryDefinition = $this->container->findDefinition($factoryDefinition->getParent());
+ }
} elseif ($class instanceof Definition) {
$class = $class->getClass();
} elseif (null === $class) {
$class = $definition->getClass();
}
- if ('__construct' === $method) {
- throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
- }
-
return $this->getReflectionMethod(new Definition($class), $method);
}
- $class = $definition->getClass();
+ while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) {
+ $definition = $this->container->findDefinition($definition->getParent());
+ }
try {
if (!$r = $this->container->getReflectionClass($class)) {
+ if (null === $class) {
+ throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
+ }
+
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
} catch (\ReflectionException $e) {
@@ -168,7 +179,11 @@ protected function getReflectionMethod(Definition $definition, string $method):
return $this->getConstructor($definition, true);
}
- if (!$class = $definition->getClass()) {
+ while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) {
+ $definition = $this->container->findDefinition($definition->getParent());
+ }
+
+ if (null === $class) {
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
}
@@ -177,6 +192,10 @@ protected function getReflectionMethod(Definition $definition, string $method):
}
if (!$r->hasMethod($method)) {
+ if ($r->hasMethod('__call') && ($r = $r->getMethod('__call')) && $r->isPublic()) {
+ return new \ReflectionMethod(static function (...$arguments) {}, '__invoke');
+ }
+
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
index 0126747f7f781..3a5c94b97b4b0 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
@@ -114,9 +114,6 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
*/
class PriorityTaggedServiceUtil
{
- /**
- * @return string|int|null
- */
public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null
{
if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) {
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php
index b58d4a452549f..dc94a8b95fd73 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php
@@ -134,6 +134,11 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
continue;
}
+ if (is_subclass_of($m[1], \UnitEnum::class)) {
+ $bindingNames[substr($key, \strlen($m[0]))] = $binding;
+ continue;
+ }
+
if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) {
throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected "%s", "%s", "%s", "%s" or null, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, ServiceLocatorArgument::class, get_debug_type($bindingValue)));
}
diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php
index dd38841487821..34638059fe86b 100644
--- a/src/Symfony/Component/DependencyInjection/Definition.php
+++ b/src/Symfony/Component/DependencyInjection/Definition.php
@@ -710,8 +710,6 @@ public function setConfigurator(string|array|Reference|null $configurator): stat
/**
* Gets the configurator to call after the service is fully initialized.
- *
- * @return string|array|null
*/
public function getConfigurator(): string|array|null
{
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
index c91d9bdfade26..9859feb2ea6b7 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -138,7 +138,7 @@ private function addService(Definition $definition, ?string $id, \DOMElement $pa
$tag->appendChild($this->document->createTextNode($name));
}
foreach ($attributes as $key => $value) {
- $tag->setAttribute($key, $value);
+ $tag->setAttribute($key, $value ?? '');
}
$service->appendChild($tag);
}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php
index 001a28c82ed23..21b3da96633d3 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php
@@ -135,7 +135,7 @@ private function executeCallback(callable $callback, ContainerConfigurator $cont
default:
try {
$configBuilder = $this->configBuilder($type);
- } catch (InvalidArgumentException | \LogicException $e) {
+ } catch (InvalidArgumentException|\LogicException $e) {
throw new \InvalidArgumentException(sprintf('Could not resolve argument "%s" for "%s".', $type.' $'.$parameter->getName(), $path), 0, $e);
}
$configBuilders[] = $configBuilder;
diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBag.php
index 435394c95d22b..d6559d8940384 100644
--- a/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBag.php
+++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBag.php
@@ -35,8 +35,6 @@ public function all(): array
/**
* {@inheritdoc}
- *
- * @return array|bool|string|int|float|null
*/
public function get(string $name): array|bool|string|int|float|null
{
diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php
index 6b9cc4c951e5c..7e276fd8f35c7 100644
--- a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php
+++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php
@@ -43,8 +43,6 @@ public function all(): array;
/**
* Gets a service container parameter.
*
- * @return array|bool|string|int|float|null
- *
* @throws ParameterNotFoundException if the parameter is not defined
*/
public function get(string $name): array|bool|string|int|float|null;
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AbstractRecursivePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AbstractRecursivePassTest.php
new file mode 100644
index 0000000000000..da13154e378f6
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AbstractRecursivePassTest.php
@@ -0,0 +1,127 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Tests\Compiler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\Bar;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\FactoryDummy;
+
+class AbstractRecursivePassTest extends TestCase
+{
+ public function testGetConstructorResolvesFactoryChildDefinitionsClass()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('factory_dummy_class', FactoryDummy::class);
+ $container
+ ->register('parent', '%factory_dummy_class%')
+ ->setAbstract(true);
+ $container->setDefinition('child', new ChildDefinition('parent'));
+ $container
+ ->register('foo', \stdClass::class)
+ ->setFactory([new Reference('child'), 'createFactory']);
+
+ $pass = new class() extends AbstractRecursivePass {
+ public $actual;
+
+ protected function processValue($value, $isRoot = false): mixed
+ {
+ if ($value instanceof Definition && 'foo' === $this->currentId) {
+ $this->actual = $this->getConstructor($value, true);
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+ };
+ $pass->process($container);
+
+ $this->assertInstanceOf(\ReflectionMethod::class, $pass->actual);
+ $this->assertSame(FactoryDummy::class, $pass->actual->class);
+ }
+
+ public function testGetConstructorResolvesChildDefinitionsClass()
+ {
+ $container = new ContainerBuilder();
+ $container
+ ->register('parent', Bar::class)
+ ->setAbstract(true);
+ $container->setDefinition('foo', new ChildDefinition('parent'));
+
+ $pass = new class() extends AbstractRecursivePass {
+ public $actual;
+
+ protected function processValue($value, $isRoot = false): mixed
+ {
+ if ($value instanceof Definition && 'foo' === $this->currentId) {
+ $this->actual = $this->getConstructor($value, true);
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+ };
+ $pass->process($container);
+
+ $this->assertInstanceOf(\ReflectionMethod::class, $pass->actual);
+ $this->assertSame(Bar::class, $pass->actual->class);
+ }
+
+ public function testGetReflectionMethodResolvesChildDefinitionsClass()
+ {
+ $container = new ContainerBuilder();
+ $container
+ ->register('parent', Bar::class)
+ ->setAbstract(true);
+ $container->setDefinition('foo', new ChildDefinition('parent'));
+
+ $pass = new class() extends AbstractRecursivePass {
+ public $actual;
+
+ protected function processValue($value, $isRoot = false): mixed
+ {
+ if ($value instanceof Definition && 'foo' === $this->currentId) {
+ $this->actual = $this->getReflectionMethod($value, 'create');
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+ };
+ $pass->process($container);
+
+ $this->assertInstanceOf(\ReflectionMethod::class, $pass->actual);
+ $this->assertSame(Bar::class, $pass->actual->class);
+ }
+
+ public function testGetConstructorDefinitionNoClass()
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('Invalid service "foo": the class is not set.');
+
+ $container = new ContainerBuilder();
+ $container->register('foo');
+
+ (new class() extends AbstractRecursivePass {
+ protected function processValue($value, $isRoot = false): mixed
+ {
+ if ($value instanceof Definition && 'foo' === $this->currentId) {
+ $this->getConstructor($value, true);
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+ })->process($container);
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
index c409f80cfcdd7..d26c0928c1050 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
@@ -961,4 +961,22 @@ public function testIntersectionTypeFailsWithReference()
(new CheckTypeDeclarationsPass(true))->process($container);
}
+
+ public function testCallableClass()
+ {
+ $container = new ContainerBuilder();
+ $definition = $container->register('foo', CallableClass::class);
+ $definition->addMethodCall('callMethod', [123]);
+
+ (new CheckTypeDeclarationsPass())->process($container);
+
+ $this->addToAssertionCount(1);
+ }
+}
+
+class CallableClass
+{
+ public function __call($name, $arguments)
+ {
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php
index 521e2fc87765f..7ca809b4df0cf 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php
@@ -25,7 +25,9 @@
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum;
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedEnumArgumentDummy;
use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists;
use Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget;
use Symfony\Component\DependencyInjection\TypedReference;
@@ -65,6 +67,27 @@ public function testProcess()
$this->assertEquals([['setSensitiveClass', [new Reference('foo')]]], $definition->getMethodCalls());
}
+ /**
+ * @requires PHP 8.1
+ */
+ public function testProcessEnum()
+ {
+ $container = new ContainerBuilder();
+
+ $bindings = [
+ FooUnitEnum::class.' $bar' => new BoundArgument(FooUnitEnum::BAR),
+ ];
+
+ $definition = $container->register(NamedEnumArgumentDummy::class, NamedEnumArgumentDummy::class);
+ $definition->setBindings($bindings);
+
+ $pass = new ResolveBindingsPass();
+ $pass->process($container);
+
+ $expected = [FooUnitEnum::BAR];
+ $this->assertEquals($expected, $definition->getArguments());
+ }
+
public function testUnusedBinding()
{
$this->expectException(InvalidArgumentException::class);
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedEnumArgumentDummy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedEnumArgumentDummy.php
new file mode 100644
index 0000000000000..c172c996a7fb7
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedEnumArgumentDummy.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
+
+class NamedEnumArgumentDummy
+{
+ public function __construct(FooUnitEnum $bar)
+ {
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
index 021b921ec208e..47922be9bde58 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
@@ -17,6 +17,7 @@
->register('foo', FooClass::class)
->addTag('foo', ['foo' => 'foo'])
->addTag('foo', ['bar' => 'bar', 'baz' => 'baz'])
+ ->addTag('nullable', ['bar' => 'bar', 'baz' => null])
->addTag('foo', ['name' => 'bar', 'baz' => 'baz'])
->setFactory(['Bar\\FooClass', 'getInstance'])
->setArguments(['foo', new Reference('foo.baz'), ['%foo%' => 'foo is %foo%', 'foobar' => '%foo%'], true, new Reference('service_container')])
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
index 0051808007a43..f59838396fb0b 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
@@ -11,6 +11,7 @@
foo
+
foo
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
index 9a78ebe4d6a9d..3f8446f682bdf 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
@@ -14,6 +14,7 @@ services:
- foo: { foo: foo }
- foo: { bar: bar, baz: baz }
- foo: { name: bar, baz: baz }
+ - nullable: { bar: bar, baz: ~ }
arguments: [foo, '@foo.baz', { '%foo%': 'foo is %foo%', foobar: '%foo%' }, true, '@service_container']
properties: { foo: bar, moo: '@foo.baz', qux: { '%foo%': 'foo is %foo%', foobar: '%foo%' } }
calls:
diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php
index d30b30e64a2d0..fce98cff91e25 100644
--- a/src/Symfony/Component/DomCrawler/Crawler.php
+++ b/src/Symfony/Component/DomCrawler/Crawler.php
@@ -629,8 +629,6 @@ public function outerHtml(): string
*
* Since an XPath expression might evaluate to either a simple type or a \DOMNodeList,
* this method will return either an array of simple types or a new Crawler instance.
- *
- * @return array|Crawler
*/
public function evaluate(string $xpath): array|Crawler
{
@@ -1055,7 +1053,7 @@ protected function sibling(\DOMNode $node, string $siblingDir = 'nextSibling'):
private function parseHtml5(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
{
- return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset), [], $charset);
+ return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset));
}
private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
@@ -1085,11 +1083,11 @@ private function convertToHtmlEntities(string $htmlContent, string $charset = 'U
try {
return mb_convert_encoding($htmlContent, 'HTML-ENTITIES', $charset);
- } catch (\Exception | \ValueError $e) {
+ } catch (\Exception|\ValueError $e) {
try {
$htmlContent = iconv($charset, 'UTF-8', $htmlContent);
$htmlContent = mb_convert_encoding($htmlContent, 'HTML-ENTITIES', 'UTF-8');
- } catch (\Exception | \ValueError $e) {
+ } catch (\Exception|\ValueError $e) {
}
return $htmlContent;
diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php
index 94473851ea62a..903111de81833 100644
--- a/src/Symfony/Component/Dotenv/Dotenv.php
+++ b/src/Symfony/Component/Dotenv/Dotenv.php
@@ -72,8 +72,8 @@ public function usePutenv(bool $usePutenv = true): static
/**
* Loads one or several .env files.
*
- * @param string $path A file to load
- * @param ...string $extraPaths A list of additional files to load
+ * @param string $path A file to load
+ * @param string[] ...$extraPaths A list of additional files to load
*
* @throws FormatException when a file has a syntax error
* @throws PathException when a file does not exist or is not readable
@@ -158,8 +158,8 @@ public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnv
/**
* Loads one or several .env files and enables override existing vars.
*
- * @param string $path A file to load
- * @param ...string $extraPaths A list of additional files to load
+ * @param string $path A file to load
+ * @param string[] ...$extraPaths A list of additional files to load
*
* @throws FormatException when a file has a syntax error
* @throws PathException when a file does not exist or is not readable
diff --git a/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php b/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php
index acfb48e76c05c..5707a8355bc90 100644
--- a/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php
+++ b/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php
@@ -30,7 +30,7 @@ class TentativeTypes
'format' => 'string',
'getTimezone' => 'DateTimeZone|false',
'getOffset' => 'int',
- 'getTimestamp' => 'int|false',
+ 'getTimestamp' => 'int',
'diff' => 'DateInterval',
'__wakeup' => 'void',
],
@@ -254,6 +254,7 @@ class TentativeTypes
'isEquivalentTo' => 'bool',
'isLenient' => 'bool',
'isWeekend' => 'bool',
+ 'roll' => 'bool',
'isSet' => 'bool',
'setTime' => 'bool',
'setTimeZone' => 'bool',
diff --git a/src/Symfony/Component/ErrorHandler/Resources/bin/extract-tentative-return-types.php b/src/Symfony/Component/ErrorHandler/Resources/bin/extract-tentative-return-types.php
index a4d2c201c04da..cc98f58b58fa0 100755
--- a/src/Symfony/Component/ErrorHandler/Resources/bin/extract-tentative-return-types.php
+++ b/src/Symfony/Component/ErrorHandler/Resources/bin/extract-tentative-return-types.php
@@ -40,7 +40,7 @@ class TentativeTypes
EOPHP;
-while (false !== $file = fgets(STDIN)) {
+while (false !== $file = fgets(\STDIN)) {
$code = file_get_contents(substr($file, 0, -1));
if (!str_contains($code, '@tentative-return-type')) {
diff --git a/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations b/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations
index 4e96448810cd7..efcfcb25daa5a 100755
--- a/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations
+++ b/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations
@@ -71,7 +71,7 @@ set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$dep
$exclude = getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null;
foreach ($loader->getClassMap() as $class => $file) {
- if (false !== strpos($file = realpath($file), '/vendor/')) {
+ if (false !== strpos($file = realpath($file), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
continue;
}
diff --git a/src/Symfony/Component/EventDispatcher/GenericEvent.php b/src/Symfony/Component/EventDispatcher/GenericEvent.php
index 4a573f8fa92fa..68a20306334c3 100644
--- a/src/Symfony/Component/EventDispatcher/GenericEvent.php
+++ b/src/Symfony/Component/EventDispatcher/GenericEvent.php
@@ -148,6 +148,8 @@ public function offsetExists(mixed $key): bool
/**
* IteratorAggregate for iterating over the object like an array.
+ *
+ * @return \ArrayIterator
*/
public function getIterator(): \ArrayIterator
{
diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php
index 3519b3568027e..e5772c459f88f 100644
--- a/src/Symfony/Component/Finder/Finder.php
+++ b/src/Symfony/Component/Finder/Finder.php
@@ -601,6 +601,8 @@ public function in(string|array $dirs): static
*
* This method implements the IteratorAggregate interface.
*
+ * @return \Iterator
+ *
* @throws \LogicException if the in() method has not been called
*/
public function getIterator(): \Iterator
diff --git a/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php
index 037810aea799f..f48cc941f8ad3 100644
--- a/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php
+++ b/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php
@@ -21,7 +21,7 @@ class RecursiveDirectoryIteratorTest extends IteratorTestCase
public function testRewindOnFtp()
{
try {
- $i = new RecursiveDirectoryIterator('ftp://speedtest.tele2.net/', \RecursiveDirectoryIterator::SKIP_DOTS);
+ $i = new RecursiveDirectoryIterator('ftp://speedtest:speedtest@ftp.otenet.gr/', \RecursiveDirectoryIterator::SKIP_DOTS);
} catch (\UnexpectedValueException $e) {
$this->markTestSkipped('Unsupported stream "ftp".');
}
@@ -37,14 +37,14 @@ public function testRewindOnFtp()
public function testSeekOnFtp()
{
try {
- $i = new RecursiveDirectoryIterator('ftp://speedtest.tele2.net/', \RecursiveDirectoryIterator::SKIP_DOTS);
+ $i = new RecursiveDirectoryIterator('ftp://speedtest:speedtest@ftp.otenet.gr/', \RecursiveDirectoryIterator::SKIP_DOTS);
} catch (\UnexpectedValueException $e) {
$this->markTestSkipped('Unsupported stream "ftp".');
}
$contains = [
- 'ftp://speedtest.tele2.net'.\DIRECTORY_SEPARATOR.'1000GB.zip',
- 'ftp://speedtest.tele2.net'.\DIRECTORY_SEPARATOR.'100GB.zip',
+ 'ftp://speedtest:speedtest@ftp.otenet.gr'.\DIRECTORY_SEPARATOR.'test100Mb.db',
+ 'ftp://speedtest:speedtest@ftp.otenet.gr'.\DIRECTORY_SEPARATOR.'test100k.db',
];
$actual = [];
diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php
index 23008e3458734..10c951f06acbf 100644
--- a/src/Symfony/Component/Form/Command/DebugCommand.php
+++ b/src/Symfony/Component/Form/Command/DebugCommand.php
@@ -275,7 +275,7 @@ private function completeOptions(string $class, CompletionSuggestions $suggestio
if (!class_exists($class) || !is_subclass_of($class, FormTypeInterface::class)) {
$classes = $this->getFqcnTypeClasses($class);
- if (1 === count($classes)) {
+ if (1 === \count($classes)) {
$class = $classes[0];
}
}
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php
index f51773e405c63..0e1d32bfc857c 100644
--- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php
+++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php
@@ -78,8 +78,6 @@ public function buildFinalFormTree(FormInterface $form, FormView $view);
/**
* Returns all collected data.
- *
- * @return array|Data
*/
public function getData(): array|Data;
}
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index 2b7ac7ced867a..7c07824cf8ef3 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -727,8 +727,6 @@ public function isValid(): bool
/**
* Returns the button that was used to submit the form.
- *
- * @return FormInterface|ClickableInterface|null
*/
public function getClickedButton(): FormInterface|ClickableInterface|null
{
diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php
index 13c9209e3bd9d..fe32f90ceff61 100644
--- a/src/Symfony/Component/Form/FormView.php
+++ b/src/Symfony/Component/Form/FormView.php
@@ -16,8 +16,8 @@
/**
* @author Bernhard Schussek
*
- * @implements \ArrayAccess
- * @implements \IteratorAggregate
+ * @implements \ArrayAccess
+ * @implements \IteratorAggregate
*/
class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
{
@@ -37,7 +37,7 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
/**
* The child views.
*
- * @var array
+ * @var array
*/
public $children = [];
@@ -100,7 +100,7 @@ public function setMethodRendered()
/**
* Returns a child by name (implements \ArrayAccess).
*
- * @param string $name The child name
+ * @param int|string $name The child name
*/
public function offsetGet(mixed $name): self
{
@@ -110,7 +110,7 @@ public function offsetGet(mixed $name): self
/**
* Returns whether the given child exists (implements \ArrayAccess).
*
- * @param string $name The child name
+ * @param int|string $name The child name
*/
public function offsetExists(mixed $name): bool
{
@@ -130,7 +130,7 @@ public function offsetSet(mixed $name, mixed $value): void
/**
* Removes a child (implements \ArrayAccess).
*
- * @param string $name The child name
+ * @param int|string $name The child name
*/
public function offsetUnset(mixed $name): void
{
@@ -140,7 +140,7 @@ public function offsetUnset(mixed $name): void
/**
* Returns an iterator to iterate over children (implements \IteratorAggregate).
*
- * @return \ArrayIterator
+ * @return \ArrayIterator
*/
public function getIterator(): \ArrayIterator
{
diff --git a/src/Symfony/Component/Form/Guess/ValueGuess.php b/src/Symfony/Component/Form/Guess/ValueGuess.php
index ddc8b53dd5e1f..36abe6602d723 100644
--- a/src/Symfony/Component/Form/Guess/ValueGuess.php
+++ b/src/Symfony/Component/Form/Guess/ValueGuess.php
@@ -32,8 +32,6 @@ public function __construct(string|int|bool|null $value, int $confidence)
/**
* Returns the guessed value.
- *
- * @return string|int|bool|null
*/
public function getValue(): string|int|bool|null
{
diff --git a/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf b/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf
index 4ed719917549d..4a98eea8eb314 100644
--- a/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf
+++ b/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf
@@ -24,7 +24,7 @@
The selected choice is invalid.
- گزینهی انتخابشده نامعتبر است.
+ گزینه انتخاب شده نامعتبر است.
The collection is invalid.
@@ -44,7 +44,7 @@
Please choose a valid date interval.
- لطفاً یک بازهی زمانی معتبر انتخاب کنید.
+ لطفاً یک بازه زمانی معتبر انتخاب کنید.
Please enter a valid date and time.
@@ -124,15 +124,15 @@
Please select a valid option.
- لطفاً یک گزینهی معتبر انتخاب کنید.
+ لطفاً یک گزینه معتبر انتخاب کنید.
Please select a valid range.
- لطفاً یک محدودهی معتبر انتخاب کنید.
+ لطفاً یک محدوده معتبر انتخاب کنید.
Please enter a valid week.
- لطفاً یک هفتهی معتبر وارد کنید.
+ لطفاً یک هفته معتبر وارد کنید.