- *
- * @internal
- */
-class CoverageListenerTrait
-{
- private $sutFqcnResolver;
- private $warningOnSutNotFound;
- private $warnings;
-
- public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false)
- {
- $this->sutFqcnResolver = $sutFqcnResolver;
- $this->warningOnSutNotFound = $warningOnSutNotFound;
- $this->warnings = [];
- }
-
- public function startTest($test)
- {
- if (!$test instanceof TestCase) {
- return;
- }
-
- $annotations = Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false));
-
- $ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing'];
-
- foreach ($ignoredAnnotations as $annotation) {
- if (isset($annotations['class'][$annotation]) || isset($annotations['method'][$annotation])) {
- return;
- }
- }
-
- $sutFqcn = $this->findSutFqcn($test);
- if (!$sutFqcn) {
- if ($this->warningOnSutNotFound) {
- $message = 'Could not find the tested class.';
- // addWarning does not exist on old PHPUnit version
- if (method_exists($test->getTestResultObject(), 'addWarning') && class_exists(Warning::class)) {
- $test->getTestResultObject()->addWarning($test, new Warning($message), 0);
- } else {
- $this->warnings[] = sprintf("%s::%s\n%s", \get_class($test), $test->getName(), $message);
- }
- }
-
- return;
- }
-
- $covers = $sutFqcn;
- if (!\is_array($sutFqcn)) {
- $covers = [$sutFqcn];
- while ($parent = get_parent_class($sutFqcn)) {
- $covers[] = $parent;
- $sutFqcn = $parent;
- }
- }
-
- if (class_exists(Registry::class)) {
- $this->addCoversForDocBlockInsideRegistry($test, $covers);
-
- return;
- }
-
- $this->addCoversForClassToAnnotationCache($test, $covers);
- }
-
- private function addCoversForClassToAnnotationCache($test, $covers)
- {
- $r = new \ReflectionProperty(Test::class, 'annotationCache');
- $r->setAccessible(true);
-
- $cache = $r->getValue();
- $cache = array_replace_recursive($cache, [
- \get_class($test) => [
- 'covers' => $covers,
- ],
- ]);
-
- $r->setValue(Test::class, $cache);
- }
-
- private function addCoversForDocBlockInsideRegistry($test, $covers)
- {
- $docBlock = Registry::getInstance()->forClassName(\get_class($test));
-
- $symbolAnnotations = new \ReflectionProperty($docBlock, 'symbolAnnotations');
- $symbolAnnotations->setAccessible(true);
-
- // Exclude internal classes; PHPUnit 9.1+ is picky about tests covering, say, a \RuntimeException
- $covers = array_filter($covers, function ($class) {
- $reflector = new \ReflectionClass($class);
-
- return $reflector->isUserDefined();
- });
-
- $symbolAnnotations->setValue($docBlock, array_replace($docBlock->symbolAnnotations(), [
- 'covers' => $covers,
- ]));
- }
-
- private function findSutFqcn($test)
- {
- if ($this->sutFqcnResolver) {
- $resolver = $this->sutFqcnResolver;
-
- return $resolver($test);
- }
-
- $class = \get_class($test);
-
- $sutFqcn = str_replace('\\Tests\\', '\\', $class);
- $sutFqcn = preg_replace('{Test$}', '', $sutFqcn);
-
- return class_exists($sutFqcn) ? $sutFqcn : null;
- }
-
- public function __sleep()
- {
- throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
- }
-
- public function __wakeup()
- {
- throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
- }
-
- public function __destruct()
- {
- if (!$this->warnings) {
- return;
- }
-
- echo "\n";
-
- foreach ($this->warnings as $key => $warning) {
- echo sprintf("%d) %s\n", ++$key, $warning);
- }
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php
new file mode 100644
index 0000000000000..03368b5597fbe
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit\Legacy;
+
+/**
+ * @internal, use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait instead.
+ */
+trait ExpectDeprecationTraitBeforeV8_4
+{
+ /**
+ * @param string $message
+ *
+ * @return void
+ */
+ protected function expectDeprecation($message)
+ {
+ // Expected deprecations set by isolated tests need to be written to a file
+ // so that the test running process can take account of them.
+ if ($file = getenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE')) {
+ $this->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
+ $expectedDeprecations = file_get_contents($file);
+ if ($expectedDeprecations) {
+ $expectedDeprecations = array_merge(unserialize($expectedDeprecations), [$message]);
+ } else {
+ $expectedDeprecations = [$message];
+ }
+ file_put_contents($file, serialize($expectedDeprecations));
+
+ return;
+ }
+
+ if (!SymfonyTestsListenerTrait::$previousErrorHandler) {
+ SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']);
+ }
+
+ SymfonyTestsListenerTrait::$expectedDeprecations[] = $message;
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitForV8_4.php b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitForV8_4.php
new file mode 100644
index 0000000000000..d15963520d6f2
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitForV8_4.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit\Legacy;
+
+/**
+ * @internal use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait instead
+ */
+trait ExpectDeprecationTraitForV8_4
+{
+ /**
+ * @param string $message
+ */
+ public function expectDeprecation(): void
+ {
+ if (1 > \func_num_args() || !\is_string($message = func_get_arg(0))) {
+ throw new \InvalidArgumentException(sprintf('The "%s()" method requires the string $message argument.', __FUNCTION__));
+ }
+
+ // Expected deprecations set by isolated tests need to be written to a file
+ // so that the test running process can take account of them.
+ if ($file = getenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE')) {
+ $this->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
+ $expectedDeprecations = file_get_contents($file);
+ if ($expectedDeprecations) {
+ $expectedDeprecations = array_merge(unserialize($expectedDeprecations), [$message]);
+ } else {
+ $expectedDeprecations = [$message];
+ }
+ file_put_contents($file, serialize($expectedDeprecations));
+
+ return;
+ }
+
+ if (!SymfonyTestsListenerTrait::$previousErrorHandler) {
+ SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']);
+ }
+
+ SymfonyTestsListenerTrait::$expectedDeprecations[] = $message;
+ }
+
+ /**
+ * @internal use expectDeprecation() instead
+ */
+ public function expectDeprecationMessage(string $message): void
+ {
+ throw new \BadMethodCallException(sprintf('The "%s()" method is not supported by Symfony\'s PHPUnit Bridge ExpectDeprecationTrait, pass the message to expectDeprecation() instead.', __FUNCTION__));
+ }
+
+ /**
+ * @internal use expectDeprecation() instead
+ */
+ public function expectDeprecationMessageMatches(string $regularExpression): void
+ {
+ throw new \BadMethodCallException(sprintf('The "%s()" method is not supported by Symfony\'s PHPUnit Bridge ExpectDeprecationTrait.', __FUNCTION__));
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php
index 5a66282d855ca..7424b7226ea14 100644
--- a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php
@@ -11,9 +11,7 @@
namespace Symfony\Bridge\PhpUnit\Legacy;
-use PHPUnit\Framework\Constraint\IsEqual;
use PHPUnit\Framework\Constraint\LogicalNot;
-use PHPUnit\Framework\Constraint\StringContains;
use PHPUnit\Framework\Constraint\TraversableContains;
/**
@@ -21,18 +19,6 @@
*/
trait PolyfillAssertTrait
{
- /**
- * @param float $delta
- * @param string $message
- *
- * @return void
- */
- public static function assertEqualsWithDelta($expected, $actual, $delta, $message = '')
- {
- $constraint = new IsEqual($expected, $delta);
- static::assertThat($actual, $constraint, $message);
- }
-
/**
* @param iterable $haystack
* @param string $message
@@ -57,225 +43,6 @@ public static function assertNotContainsEquals($needle, $haystack, $message = ''
static::assertThat($haystack, $constraint, $message);
}
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsArray($actual, $message = '')
- {
- static::assertInternalType('array', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsBool($actual, $message = '')
- {
- static::assertInternalType('bool', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsFloat($actual, $message = '')
- {
- static::assertInternalType('float', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsInt($actual, $message = '')
- {
- static::assertInternalType('int', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsNumeric($actual, $message = '')
- {
- static::assertInternalType('numeric', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsObject($actual, $message = '')
- {
- static::assertInternalType('object', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsResource($actual, $message = '')
- {
- static::assertInternalType('resource', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsString($actual, $message = '')
- {
- static::assertInternalType('string', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsScalar($actual, $message = '')
- {
- static::assertInternalType('scalar', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsCallable($actual, $message = '')
- {
- static::assertInternalType('callable', $actual, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertIsIterable($actual, $message = '')
- {
- static::assertInternalType('iterable', $actual, $message);
- }
-
- /**
- * @param string $needle
- * @param string $haystack
- * @param string $message
- *
- * @return void
- */
- public static function assertStringContainsString($needle, $haystack, $message = '')
- {
- $constraint = new StringContains($needle, false);
- static::assertThat($haystack, $constraint, $message);
- }
-
- /**
- * @param string $needle
- * @param string $haystack
- * @param string $message
- *
- * @return void
- */
- public static function assertStringContainsStringIgnoringCase($needle, $haystack, $message = '')
- {
- $constraint = new StringContains($needle, true);
- static::assertThat($haystack, $constraint, $message);
- }
-
- /**
- * @param string $needle
- * @param string $haystack
- * @param string $message
- *
- * @return void
- */
- public static function assertStringNotContainsString($needle, $haystack, $message = '')
- {
- $constraint = new LogicalNot(new StringContains($needle, false));
- static::assertThat($haystack, $constraint, $message);
- }
-
- /**
- * @param string $needle
- * @param string $haystack
- * @param string $message
- *
- * @return void
- */
- public static function assertStringNotContainsStringIgnoringCase($needle, $haystack, $message = '')
- {
- $constraint = new LogicalNot(new StringContains($needle, true));
- static::assertThat($haystack, $constraint, $message);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertFinite($actual, $message = '')
- {
- static::assertInternalType('float', $actual, $message);
- static::assertTrue(is_finite($actual), $message ?: "Failed asserting that $actual is finite.");
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertInfinite($actual, $message = '')
- {
- static::assertInternalType('float', $actual, $message);
- static::assertTrue(is_infinite($actual), $message ?: "Failed asserting that $actual is infinite.");
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public static function assertNan($actual, $message = '')
- {
- static::assertInternalType('float', $actual, $message);
- static::assertTrue(is_nan($actual), $message ?: "Failed asserting that $actual is nan.");
- }
-
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertIsReadable($filename, $message = '')
- {
- static::assertInternalType('string', $filename, $message);
- static::assertTrue(is_readable($filename), $message ?: "Failed asserting that $filename is readable.");
- }
-
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertNotIsReadable($filename, $message = '')
- {
- static::assertInternalType('string', $filename, $message);
- static::assertFalse(is_readable($filename), $message ?: "Failed asserting that $filename is not readable.");
- }
-
/**
* @param string $filename
* @param string $message
@@ -287,30 +54,6 @@ public static function assertIsNotReadable($filename, $message = '')
static::assertNotIsReadable($filename, $message);
}
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertIsWritable($filename, $message = '')
- {
- static::assertInternalType('string', $filename, $message);
- static::assertTrue(is_writable($filename), $message ?: "Failed asserting that $filename is writable.");
- }
-
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertNotIsWritable($filename, $message = '')
- {
- static::assertInternalType('string', $filename, $message);
- static::assertFalse(is_writable($filename), $message ?: "Failed asserting that $filename is not writable.");
- }
-
/**
* @param string $filename
* @param string $message
@@ -322,30 +65,6 @@ public static function assertIsNotWritable($filename, $message = '')
static::assertNotIsWritable($filename, $message);
}
- /**
- * @param string $directory
- * @param string $message
- *
- * @return void
- */
- public static function assertDirectoryExists($directory, $message = '')
- {
- static::assertInternalType('string', $directory, $message);
- static::assertTrue(is_dir($directory), $message ?: "Failed asserting that $directory exists.");
- }
-
- /**
- * @param string $directory
- * @param string $message
- *
- * @return void
- */
- public static function assertDirectoryNotExists($directory, $message = '')
- {
- static::assertInternalType('string', $directory, $message);
- static::assertFalse(is_dir($directory), $message ?: "Failed asserting that $directory does not exist.");
- }
-
/**
* @param string $directory
* @param string $message
@@ -357,30 +76,6 @@ public static function assertDirectoryDoesNotExist($directory, $message = '')
static::assertDirectoryNotExists($directory, $message);
}
- /**
- * @param string $directory
- * @param string $message
- *
- * @return void
- */
- public static function assertDirectoryIsReadable($directory, $message = '')
- {
- static::assertDirectoryExists($directory, $message);
- static::assertIsReadable($directory, $message);
- }
-
- /**
- * @param string $directory
- * @param string $message
- *
- * @return void
- */
- public static function assertDirectoryNotIsReadable($directory, $message = '')
- {
- static::assertDirectoryExists($directory, $message);
- static::assertNotIsReadable($directory, $message);
- }
-
/**
* @param string $directory
* @param string $message
@@ -392,30 +87,6 @@ public static function assertDirectoryIsNotReadable($directory, $message = '')
static::assertDirectoryNotIsReadable($directory, $message);
}
- /**
- * @param string $directory
- * @param string $message
- *
- * @return void
- */
- public static function assertDirectoryIsWritable($directory, $message = '')
- {
- static::assertDirectoryExists($directory, $message);
- static::assertIsWritable($directory, $message);
- }
-
- /**
- * @param string $directory
- * @param string $message
- *
- * @return void
- */
- public static function assertDirectoryNotIsWritable($directory, $message = '')
- {
- static::assertDirectoryExists($directory, $message);
- static::assertNotIsWritable($directory, $message);
- }
-
/**
* @param string $directory
* @param string $message
@@ -427,30 +98,6 @@ public static function assertDirectoryIsNotWritable($directory, $message = '')
static::assertDirectoryNotIsWritable($directory, $message);
}
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertFileExists($filename, $message = '')
- {
- static::assertInternalType('string', $filename, $message);
- static::assertTrue(file_exists($filename), $message ?: "Failed asserting that $filename exists.");
- }
-
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertFileNotExists($filename, $message = '')
- {
- static::assertInternalType('string', $filename, $message);
- static::assertFalse(file_exists($filename), $message ?: "Failed asserting that $filename does not exist.");
- }
-
/**
* @param string $filename
* @param string $message
@@ -462,30 +109,6 @@ public static function assertFileDoesNotExist($filename, $message = '')
static::assertFileNotExists($filename, $message);
}
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertFileIsReadable($filename, $message = '')
- {
- static::assertFileExists($filename, $message);
- static::assertIsReadable($filename, $message);
- }
-
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertFileNotIsReadable($filename, $message = '')
- {
- static::assertFileExists($filename, $message);
- static::assertNotIsReadable($filename, $message);
- }
-
/**
* @param string $filename
* @param string $message
@@ -497,30 +120,6 @@ public static function assertFileIsNotReadable($filename, $message = '')
static::assertFileNotIsReadable($filename, $message);
}
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertFileIsWritable($filename, $message = '')
- {
- static::assertFileExists($filename, $message);
- static::assertIsWritable($filename, $message);
- }
-
- /**
- * @param string $filename
- * @param string $message
- *
- * @return void
- */
- public static function assertFileNotIsWritable($filename, $message = '')
- {
- static::assertFileExists($filename, $message);
- static::assertNotIsWritable($filename, $message);
- }
-
/**
* @param string $filename
* @param string $message
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php
index ad2150436833d..8673bdc0a1d2b 100644
--- a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php
@@ -14,88 +14,12 @@
use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
/**
* This trait is @internal.
*/
trait PolyfillTestCaseTrait
{
- /**
- * @param string|string[] $originalClassName
- *
- * @return MockObject
- */
- protected function createMock($originalClassName)
- {
- $mock = $this->getMockBuilder($originalClassName)
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->disableArgumentCloning();
-
- if (method_exists($mock, 'disallowMockingUnknownTypes')) {
- $mock = $mock->disallowMockingUnknownTypes();
- }
-
- return $mock->getMock();
- }
-
- /**
- * @param string|string[] $originalClassName
- * @param string[] $methods
- *
- * @return MockObject
- */
- protected function createPartialMock($originalClassName, array $methods)
- {
- $mock = $this->getMockBuilder($originalClassName)
- ->disableOriginalConstructor()
- ->disableOriginalClone()
- ->disableArgumentCloning()
- ->setMethods(empty($methods) ? null : $methods);
-
- if (method_exists($mock, 'disallowMockingUnknownTypes')) {
- $mock = $mock->disallowMockingUnknownTypes();
- }
-
- return $mock->getMock();
- }
-
- /**
- * @param string $exception
- *
- * @return void
- */
- public function expectException($exception)
- {
- $this->doExpectException($exception);
- }
-
- /**
- * @param int|string $code
- *
- * @return void
- */
- public function expectExceptionCode($code)
- {
- $property = new \ReflectionProperty(TestCase::class, 'expectedExceptionCode');
- $property->setAccessible(true);
- $property->setValue($this, $code);
- }
-
- /**
- * @param string $message
- *
- * @return void
- */
- public function expectExceptionMessage($message)
- {
- $property = new \ReflectionProperty(TestCase::class, 'expectedExceptionMessage');
- $property->setAccessible(true);
- $property->setValue($this, $message);
- }
-
/**
* @param string $messageRegExp
*
@@ -106,24 +30,12 @@ public function expectExceptionMessageMatches($messageRegExp)
$this->expectExceptionMessageRegExp($messageRegExp);
}
- /**
- * @param string $messageRegExp
- *
- * @return void
- */
- public function expectExceptionMessageRegExp($messageRegExp)
- {
- $property = new \ReflectionProperty(TestCase::class, 'expectedExceptionMessageRegExp');
- $property->setAccessible(true);
- $property->setValue($this, $messageRegExp);
- }
-
/**
* @return void
*/
public function expectNotice()
{
- $this->doExpectException(Notice::class);
+ $this->expectException(Notice::class);
}
/**
@@ -151,7 +63,7 @@ public function expectNoticeMessageMatches($regularExpression)
*/
public function expectWarning()
{
- $this->doExpectException(Warning::class);
+ $this->expectException(Warning::class);
}
/**
@@ -179,7 +91,7 @@ public function expectWarningMessageMatches($regularExpression)
*/
public function expectError()
{
- $this->doExpectException(Error::class);
+ $this->expectException(Error::class);
}
/**
@@ -201,11 +113,4 @@ public function expectErrorMessageMatches($regularExpression)
{
$this->expectExceptionMessageMatches($regularExpression);
}
-
- private function doExpectException($exception)
- {
- $property = new \ReflectionProperty(TestCase::class, 'expectedException');
- $property->setAccessible(true);
- $property->setValue($this, $exception);
- }
}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php
deleted file mode 100644
index ca29c2ae49ab8..0000000000000
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV5.php
+++ /dev/null
@@ -1,70 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\PhpUnit\Legacy;
-
-/**
- * @internal
- */
-trait SetUpTearDownTraitForV5
-{
- /**
- * @return void
- */
- public static function setUpBeforeClass()
- {
- self::doSetUpBeforeClass();
- }
-
- /**
- * @return void
- */
- public static function tearDownAfterClass()
- {
- self::doTearDownAfterClass();
- }
-
- /**
- * @return void
- */
- protected function setUp()
- {
- self::doSetUp();
- }
-
- /**
- * @return void
- */
- protected function tearDown()
- {
- self::doTearDown();
- }
-
- private static function doSetUpBeforeClass()
- {
- parent::setUpBeforeClass();
- }
-
- private static function doTearDownAfterClass()
- {
- parent::tearDownAfterClass();
- }
-
- private function doSetUp()
- {
- parent::setUp();
- }
-
- private function doTearDown()
- {
- parent::tearDown();
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV8.php b/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV8.php
deleted file mode 100644
index cc81df281880a..0000000000000
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SetUpTearDownTraitForV8.php
+++ /dev/null
@@ -1,58 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\PhpUnit\Legacy;
-
-/**
- * @internal
- */
-trait SetUpTearDownTraitForV8
-{
- public static function setUpBeforeClass(): void
- {
- self::doSetUpBeforeClass();
- }
-
- public static function tearDownAfterClass(): void
- {
- self::doTearDownAfterClass();
- }
-
- protected function setUp(): void
- {
- self::doSetUp();
- }
-
- protected function tearDown(): void
- {
- self::doTearDown();
- }
-
- private static function doSetUpBeforeClass(): void
- {
- parent::setUpBeforeClass();
- }
-
- private static function doTearDownAfterClass(): void
- {
- parent::tearDownAfterClass();
- }
-
- private function doSetUp(): void
- {
- parent::setUp();
- }
-
- private function doTearDown(): void
- {
- parent::tearDown();
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php
deleted file mode 100644
index 9b646dca8dfab..0000000000000
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php
+++ /dev/null
@@ -1,54 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\PhpUnit\Legacy;
-
-/**
- * Collects and replays skipped tests.
- *
- * @author Nicolas Grekas
- *
- * @internal
- */
-class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener
-{
- private $trait;
-
- public function __construct(array $mockedNamespaces = [])
- {
- $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces);
- }
-
- public function globalListenerDisabled()
- {
- $this->trait->globalListenerDisabled();
- }
-
- public function startTestSuite(\PHPUnit_Framework_TestSuite $suite)
- {
- $this->trait->startTestSuite($suite);
- }
-
- public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
- {
- $this->trait->addSkippedTest($test, $e, $time);
- }
-
- public function startTest(\PHPUnit_Framework_Test $test)
- {
- $this->trait->startTest($test);
- }
-
- public function endTest(\PHPUnit_Framework_Test $test, $time)
- {
- $this->trait->endTest($test, $time);
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php
deleted file mode 100644
index 8f2f6b5a7ed54..0000000000000
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php
+++ /dev/null
@@ -1,58 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\PhpUnit\Legacy;
-
-use PHPUnit\Framework\BaseTestListener;
-use PHPUnit\Framework\Test;
-use PHPUnit\Framework\TestSuite;
-
-/**
- * Collects and replays skipped tests.
- *
- * @author Nicolas Grekas
- *
- * @internal
- */
-class SymfonyTestsListenerForV6 extends BaseTestListener
-{
- private $trait;
-
- public function __construct(array $mockedNamespaces = [])
- {
- $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces);
- }
-
- public function globalListenerDisabled()
- {
- $this->trait->globalListenerDisabled();
- }
-
- public function startTestSuite(TestSuite $suite)
- {
- $this->trait->startTestSuite($suite);
- }
-
- public function addSkippedTest(Test $test, \Exception $e, $time)
- {
- $this->trait->addSkippedTest($test, $e, $time);
- }
-
- public function startTest(Test $test)
- {
- $this->trait->startTest($test);
- }
-
- public function endTest(Test $test, $time)
- {
- $this->trait->endTest($test, $time);
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php
index 0f9238bdd9c1c..c84ec1b66ddbd 100644
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php
@@ -13,6 +13,7 @@
use Doctrine\Common\Annotations\AnnotationRegistry;
use PHPUnit\Framework\AssertionFailedError;
+use PHPUnit\Framework\RiskyTestError;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\BaseTestRunner;
@@ -21,6 +22,7 @@
use PHPUnit\Util\Test;
use Symfony\Bridge\PhpUnit\ClockMock;
use Symfony\Bridge\PhpUnit\DnsMock;
+use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
use Symfony\Component\ErrorHandler\DebugClassLoader;
@@ -33,16 +35,16 @@
*/
class SymfonyTestsListenerTrait
{
+ public static $expectedDeprecations = [];
+ public static $previousErrorHandler;
+ private static $gatheredDeprecations = [];
private static $globallyEnabled = false;
private $state = -1;
private $skippedFile = false;
private $wasSkipped = [];
private $isSkipped = [];
- private $expectedDeprecations = [];
- private $gatheredDeprecations = [];
- private $previousErrorHandler;
- private $error;
private $runsInSeparateProcess = false;
+ private $checkNumAssertions = false;
/**
* @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive)
@@ -121,7 +123,7 @@ public function startTestSuite($suite)
$suiteName = $suite->getName();
foreach ($suite->tests() as $test) {
- if (!($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) {
+ if (!$test instanceof TestCase) {
continue;
}
if (null === Test::getPreserveGlobalStateSettings(\get_class($test), $test->getName(false))) {
@@ -156,7 +158,7 @@ public function startTestSuite($suite)
$testSuites = [$suite];
for ($i = 0; isset($testSuites[$i]); ++$i) {
foreach ($testSuites[$i]->tests() as $test) {
- if ($test instanceof \PHPUnit_Framework_TestSuite || $test instanceof TestSuite) {
+ if ($test instanceof TestSuite) {
if (!class_exists($test->getName(), false)) {
$testSuites[] = $test;
continue;
@@ -176,11 +178,11 @@ public function startTestSuite($suite)
$skipped = [];
while ($s = array_shift($suites)) {
foreach ($s->tests() as $test) {
- if ($test instanceof \PHPUnit_Framework_TestSuite || $test instanceof TestSuite) {
+ if ($test instanceof TestSuite) {
$suites[] = $test;
continue;
}
- if (($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)
+ if ($test instanceof TestCase
&& isset($this->wasSkipped[\get_class($test)][$test->getName()])
) {
$skipped[] = $test;
@@ -200,11 +202,12 @@ public function addSkippedTest($test, \Exception $e, $time)
public function startTest($test)
{
- if (-2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) {
+ if (-2 < $this->state && $test instanceof TestCase) {
// This event is triggered before the test is re-run in isolation
if ($this->willBeIsolated($test)) {
$this->runsInSeparateProcess = tempnam(sys_get_temp_dir(), 'deprec');
putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$this->runsInSeparateProcess);
+ putenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE='.tempnam(sys_get_temp_dir(), 'expectdeprec'));
}
$groups = Test::getGroups(\get_class($test), $test->getName(false));
@@ -228,21 +231,35 @@ public function startTest($test)
if (isset($annotations['class']['expectedDeprecation'])) {
$test->getTestResultObject()->addError($test, new AssertionFailedError('`@expectedDeprecation` annotations are not allowed at the class level.'), 0);
}
- if (isset($annotations['method']['expectedDeprecation'])) {
- if (!\in_array('legacy', $groups, true)) {
- $this->error = new AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.');
+ if (isset($annotations['method']['expectedDeprecation']) || $this->checkNumAssertions = method_exists($test, 'expectDeprecation') && (new \ReflectionMethod($test, 'expectDeprecation'))->getFileName() === (new \ReflectionMethod(ExpectDeprecationTrait::class, 'expectDeprecation'))->getFileName()) {
+ if (isset($annotations['method']['expectedDeprecation'])) {
+ self::$expectedDeprecations = $annotations['method']['expectedDeprecation'];
+ self::$previousErrorHandler = set_error_handler([self::class, 'handleError']);
+ @trigger_error('Since symfony/phpunit-bridge 5.1: Using "@expectedDeprecation" annotations in tests is deprecated, use the "ExpectDeprecationTrait::expectDeprecation()" method instead.', \E_USER_DEPRECATED);
}
- $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
+ if ($this->checkNumAssertions) {
+ $this->checkNumAssertions = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything();
+ }
- $this->expectedDeprecations = $annotations['method']['expectedDeprecation'];
- $this->previousErrorHandler = set_error_handler([$this, 'handleError']);
+ $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
}
}
}
public function endTest($test, $time)
{
+ if ($file = getenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE')) {
+ putenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE');
+ $expectedDeprecations = file_get_contents($file);
+ if ($expectedDeprecations) {
+ self::$expectedDeprecations = array_merge(self::$expectedDeprecations, unserialize($expectedDeprecations));
+ if (!self::$previousErrorHandler) {
+ self::$previousErrorHandler = set_error_handler([self::class, 'handleError']);
+ }
+ }
+ }
+
if (class_exists(DebugClassLoader::class, false)) {
DebugClassLoader::checkClasses();
}
@@ -250,9 +267,15 @@ public function endTest($test, $time)
$className = \get_class($test);
$groups = Test::getGroups($className, $test->getName(false));
- if ($errored = null !== $this->error) {
- $test->getTestResultObject()->addError($test, $this->error, 0);
- $this->error = null;
+ if ($this->checkNumAssertions) {
+ $assertions = \count(self::$expectedDeprecations) + $test->getNumAssertions();
+ if ($test->doesNotPerformAssertions() && $assertions > 0) {
+ $test->getTestResultObject()->addFailure($test, new RiskyTestError(sprintf('This test is annotated with "@doesNotPerformAssertions", but performed %s assertions', $assertions)), $time);
+ } elseif ($assertions === 0 && $test->getTestResultObject()->noneSkipped()) {
+ $test->getTestResultObject()->addFailure($test, new RiskyTestError('This test did not perform any assertions'), $time);
+ }
+
+ $this->checkNumAssertions = false;
}
if ($this->runsInSeparateProcess) {
@@ -260,7 +283,7 @@ public function endTest($test, $time)
unlink($this->runsInSeparateProcess);
putenv('SYMFONY_DEPRECATIONS_SERIALIZE');
foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) {
- $error = serialize(['deprecation' => $deprecation[1], 'class' => $className, 'method' => $test->getName(false), 'triggering_file' => isset($deprecation[2]) ? $deprecation[2] : null, 'files_stack' => isset($deprecation[3]) ? $deprecation[3] : []]);
+ $error = serialize(['deprecation' => $deprecation[1], 'class' => $className, 'method' => $test->getName(false), 'triggering_file' => $deprecation[2] ?? null, 'files_stack' => $deprecation[3] ?? []]);
if ($deprecation[0]) {
// unsilenced on purpose
trigger_error($error, \E_USER_DEPRECATED);
@@ -271,26 +294,28 @@ public function endTest($test, $time)
$this->runsInSeparateProcess = false;
}
- if ($this->expectedDeprecations) {
+ if (self::$expectedDeprecations) {
if (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true)) {
- $test->addToAssertionCount(\count($this->expectedDeprecations));
+ $test->addToAssertionCount(\count(self::$expectedDeprecations));
}
restore_error_handler();
- if (!$errored && !\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) {
+ if (!\in_array('legacy', $groups, true)) {
+ $test->getTestResultObject()->addError($test, new AssertionFailedError('Only tests with the `@group legacy` annotation can expect a deprecation.'), 0);
+ } elseif (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) {
try {
$prefix = "@expectedDeprecation:\n";
- $test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", $this->expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", $this->gatheredDeprecations)."\n");
+ $test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", self::$expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", self::$gatheredDeprecations)."\n");
} catch (AssertionFailedError $e) {
$test->getTestResultObject()->addFailure($test, $e, $time);
}
}
- $this->expectedDeprecations = $this->gatheredDeprecations = [];
- $this->previousErrorHandler = null;
+ self::$expectedDeprecations = self::$gatheredDeprecations = [];
+ self::$previousErrorHandler = null;
}
- if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) {
+ if (!$this->runsInSeparateProcess && -2 < $this->state && $test instanceof TestCase) {
if (\in_array('time-sensitive', $groups, true)) {
ClockMock::withClockMock(false);
}
@@ -300,10 +325,10 @@ public function endTest($test, $time)
}
}
- public function handleError($type, $msg, $file, $line, $context = [])
+ public static function handleError($type, $msg, $file, $line, $context = [])
{
if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) {
- $h = $this->previousErrorHandler;
+ $h = self::$previousErrorHandler;
return $h ? $h($type, $msg, $file, $line, $context) : false;
}
@@ -316,7 +341,7 @@ public function handleError($type, $msg, $file, $line, $context = [])
if (error_reporting() & $type) {
$msg = 'Unsilenced deprecation: '.$msg;
}
- $this->gatheredDeprecations[] = $msg;
+ self::$gatheredDeprecations[] = $msg;
return null;
}
diff --git a/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php b/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php
deleted file mode 100644
index e27c3a4fb0934..0000000000000
--- a/src/Symfony/Bridge/PhpUnit/SetUpTearDownTrait.php
+++ /dev/null
@@ -1,28 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\PhpUnit;
-
-use PHPUnit\Framework\TestCase;
-
-// A trait to provide forward compatibility with newest PHPUnit versions
-$r = new \ReflectionClass(TestCase::class);
-if (\PHP_VERSION_ID < 70000 || !$r->getMethod('setUp')->hasReturnType()) {
- trait SetUpTearDownTrait
- {
- use Legacy\SetUpTearDownTraitForV5;
- }
-} else {
- trait SetUpTearDownTrait
- {
- use Legacy\SetUpTearDownTraitForV8;
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php
index d3cd7563bd41f..47f0f42afc8fd 100644
--- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php
+++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php
@@ -11,13 +11,7 @@
namespace Symfony\Bridge\PhpUnit;
-if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
- class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
-} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
- class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
-} else {
- class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
-}
+class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
if (false) {
class SymfonyTestsListener
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php b/src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php
deleted file mode 100644
index d1811575087df..0000000000000
--- a/src/Symfony/Bridge/PhpUnit/Tests/BootstrapTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\PhpUnit\Tests;
-
-use PHPUnit\Framework\TestCase;
-
-class BootstrapTest extends TestCase
-{
- /**
- * @requires PHPUnit < 6.0
- */
- public function testAliasingOfErrorClasses()
- {
- $this->assertInstanceOf(
- \PHPUnit_Framework_Error::class,
- new \PHPUnit\Framework\Error\Error('message', 0, __FILE__, __LINE__)
- );
- $this->assertInstanceOf(
- \PHPUnit_Framework_Error_Deprecated::class,
- new \PHPUnit\Framework\Error\Deprecated('message', 0, __FILE__, __LINE__)
- );
- $this->assertInstanceOf(
- \PHPUnit_Framework_Error_Notice::class,
- new \PHPUnit\Framework\Error\Notice('message', 0, __FILE__, __LINE__)
- );
- $this->assertInstanceOf(
- \PHPUnit_Framework_Error_Warning::class,
- new \PHPUnit\Framework\Error\Warning('message', 0, __FILE__, __LINE__)
- );
- }
-}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php
index 53b2bb8d6cdff..b309606d5bd4e 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php
@@ -14,7 +14,7 @@ public function test()
exec('type phpdbg 2> /dev/null', $output, $returnCode);
- if (\PHP_VERSION_ID >= 70000 && 0 === $returnCode) {
+ if (0 === $returnCode) {
$php = 'phpdbg -qrr';
} else {
exec('php --ri xdebug -d zend_extension=xdebug.so 2> /dev/null', $output, $returnCode);
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php
index 39e792cd3a2cb..5d36a43bff54f 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php
@@ -13,9 +13,13 @@
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Configuration;
+use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
+use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\DeprecationGroup;
class ConfigurationTest extends TestCase
{
+ private $files;
+
public function testItThrowsOnStringishValue()
{
$this->expectException(\InvalidArgumentException::class);
@@ -47,122 +51,122 @@ public function testItThrowsOnStringishThreshold()
public function testItNoticesExceededTotalThreshold()
{
$configuration = Configuration::fromUrlEncodedString('max[total]=3');
- $this->assertTrue($configuration->tolerates([
- 'unsilencedCount' => 1,
- 'remaining selfCount' => 0,
- 'legacyCount' => 1,
- 'otherCount' => 0,
- 'remaining directCount' => 1,
- 'remaining indirectCount' => 1,
- ]));
- $this->assertFalse($configuration->tolerates([
- 'unsilencedCount' => 1,
- 'remaining selfCount' => 1,
- 'legacyCount' => 1,
- 'otherCount' => 0,
- 'remaining directCount' => 1,
- 'remaining indirectCount' => 1,
- ]));
+ $this->assertTrue($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1,
+ 'self' => 0,
+ 'legacy' => 1,
+ 'other' => 0,
+ 'direct' => 1,
+ 'indirect' => 1,
+ ])));
+ $this->assertFalse($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1,
+ 'self' => 1,
+ 'legacy' => 1,
+ 'other' => 0,
+ 'direct' => 1,
+ 'indirect' => 1,
+ ])));
}
public function testItNoticesExceededSelfThreshold()
{
$configuration = Configuration::fromUrlEncodedString('max[self]=1');
- $this->assertTrue($configuration->tolerates([
- 'unsilencedCount' => 1234,
- 'remaining selfCount' => 1,
- 'legacyCount' => 23,
- 'otherCount' => 13,
- 'remaining directCount' => 124,
- 'remaining indirectCount' => 3244,
- ]));
- $this->assertFalse($configuration->tolerates([
- 'unsilencedCount' => 1234,
- 'remaining selfCount' => 2,
- 'legacyCount' => 23,
- 'otherCount' => 13,
- 'remaining directCount' => 124,
- 'remaining indirectCount' => 3244,
- ]));
+ $this->assertTrue($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1234,
+ 'self' => 1,
+ 'legacy' => 23,
+ 'other' => 13,
+ 'direct' => 124,
+ 'indirect' => 3244,
+ ])));
+ $this->assertFalse($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1234,
+ 'self' => 2,
+ 'legacy' => 23,
+ 'other' => 13,
+ 'direct' => 124,
+ 'indirect' => 3244,
+ ])));
}
public function testItNoticesExceededDirectThreshold()
{
$configuration = Configuration::fromUrlEncodedString('max[direct]=1&max[self]=999999');
- $this->assertTrue($configuration->tolerates([
- 'unsilencedCount' => 1234,
- 'remaining selfCount' => 123,
- 'legacyCount' => 23,
- 'otherCount' => 13,
- 'remaining directCount' => 1,
- 'remaining indirectCount' => 3244,
- ]));
- $this->assertFalse($configuration->tolerates([
- 'unsilencedCount' => 1234,
- 'remaining selfCount' => 124,
- 'legacyCount' => 23,
- 'otherCount' => 13,
- 'remaining directCount' => 2,
- 'remaining indirectCount' => 3244,
- ]));
+ $this->assertTrue($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1234,
+ 'self' => 123,
+ 'legacy' => 23,
+ 'other' => 13,
+ 'direct' => 1,
+ 'indirect' => 3244,
+ ])));
+ $this->assertFalse($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1234,
+ 'self' => 124,
+ 'legacy' => 23,
+ 'other' => 13,
+ 'direct' => 2,
+ 'indirect' => 3244,
+ ])));
}
public function testItNoticesExceededIndirectThreshold()
{
$configuration = Configuration::fromUrlEncodedString('max[indirect]=1&max[direct]=999999&max[self]=999999');
- $this->assertTrue($configuration->tolerates([
- 'unsilencedCount' => 1234,
- 'remaining selfCount' => 123,
- 'legacyCount' => 23,
- 'otherCount' => 13,
- 'remaining directCount' => 1234,
- 'remaining indirectCount' => 1,
- ]));
- $this->assertFalse($configuration->tolerates([
- 'unsilencedCount' => 1234,
- 'remaining selfCount' => 124,
- 'legacyCount' => 23,
- 'otherCount' => 13,
- 'remaining directCount' => 2324,
- 'remaining indirectCount' => 2,
- ]));
+ $this->assertTrue($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1234,
+ 'self' => 123,
+ 'legacy' => 23,
+ 'other' => 13,
+ 'direct' => 1234,
+ 'indirect' => 1,
+ ])));
+ $this->assertFalse($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 1234,
+ 'self' => 124,
+ 'legacy' => 23,
+ 'other' => 13,
+ 'direct' => 2324,
+ 'indirect' => 2,
+ ])));
}
public function testIndirectThresholdIsUsedAsADefaultForDirectAndSelfThreshold()
{
$configuration = Configuration::fromUrlEncodedString('max[indirect]=1');
- $this->assertTrue($configuration->tolerates([
- 'unsilencedCount' => 0,
- 'remaining selfCount' => 1,
- 'legacyCount' => 0,
- 'otherCount' => 0,
- 'remaining directCount' => 0,
- 'remaining indirectCount' => 0,
- ]));
- $this->assertFalse($configuration->tolerates([
- 'unsilencedCount' => 0,
- 'remaining selfCount' => 2,
- 'legacyCount' => 0,
- 'otherCount' => 0,
- 'remaining directCount' => 0,
- 'remaining indirectCount' => 0,
- ]));
- $this->assertTrue($configuration->tolerates([
- 'unsilencedCount' => 0,
- 'remaining selfCount' => 0,
- 'legacyCount' => 0,
- 'otherCount' => 0,
- 'remaining directCount' => 1,
- 'remaining indirectCount' => 0,
- ]));
- $this->assertFalse($configuration->tolerates([
- 'unsilencedCount' => 0,
- 'remaining selfCount' => 0,
- 'legacyCount' => 0,
- 'otherCount' => 0,
- 'remaining directCount' => 2,
- 'remaining indirectCount' => 0,
- ]));
+ $this->assertTrue($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 0,
+ 'self' => 1,
+ 'legacy' => 0,
+ 'other' => 0,
+ 'direct' => 0,
+ 'indirect' => 0,
+ ])));
+ $this->assertFalse($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 0,
+ 'self' => 2,
+ 'legacy' => 0,
+ 'other' => 0,
+ 'direct' => 0,
+ 'indirect' => 0,
+ ])));
+ $this->assertTrue($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 0,
+ 'self' => 0,
+ 'legacy' => 0,
+ 'other' => 0,
+ 'direct' => 1,
+ 'indirect' => 0,
+ ])));
+ $this->assertFalse($configuration->tolerates($this->buildGroups([
+ 'unsilenced' => 0,
+ 'self' => 0,
+ 'legacy' => 0,
+ 'other' => 0,
+ 'direct' => 2,
+ 'indirect' => 0,
+ ])));
}
public function testItCanTellWhetherToDisplayAStackTrace()
@@ -175,21 +179,237 @@ public function testItCanTellWhetherToDisplayAStackTrace()
$this->assertTrue($configuration->shouldDisplayStackTrace('interesting'));
}
- public function testItCanBeDisabled()
+ public function provideItCanBeDisabled(): array
+ {
+ return [
+ ['disabled', false],
+ ['disabled=1', false],
+ ['disabled=0', true],
+ ];
+ }
+
+ /**
+ * @dataProvider provideItCanBeDisabled
+ */
+ public function testItCanBeDisabled(string $encodedString, bool $expectedEnabled)
{
- $configuration = Configuration::fromUrlEncodedString('disabled');
- $this->assertFalse($configuration->isEnabled());
+ $configuration = Configuration::fromUrlEncodedString($encodedString);
+ $this->assertSame($expectedEnabled, $configuration->isEnabled());
}
public function testItCanBeShushed()
{
$configuration = Configuration::fromUrlEncodedString('verbose');
- $this->assertFalse($configuration->verboseOutput());
+ $this->assertFalse($configuration->verboseOutput('unsilenced'));
+ $this->assertFalse($configuration->verboseOutput('direct'));
+ $this->assertFalse($configuration->verboseOutput('indirect'));
+ $this->assertFalse($configuration->verboseOutput('self'));
+ $this->assertFalse($configuration->verboseOutput('other'));
+ }
+
+ public function testItCanBePartiallyShushed()
+ {
+ $configuration = Configuration::fromUrlEncodedString('quiet[]=unsilenced&quiet[]=indirect&quiet[]=other');
+ $this->assertFalse($configuration->verboseOutput('unsilenced'));
+ $this->assertTrue($configuration->verboseOutput('direct'));
+ $this->assertFalse($configuration->verboseOutput('indirect'));
+ $this->assertTrue($configuration->verboseOutput('self'));
+ $this->assertFalse($configuration->verboseOutput('other'));
+ }
+
+ public function testItThrowsOnUnknownVerbosityGroup()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('made-up');
+ Configuration::fromUrlEncodedString('quiet[]=made-up');
}
public function testOutputIsNotVerboseInWeakMode()
{
$configuration = Configuration::inWeakMode();
- $this->assertFalse($configuration->verboseOutput());
+ $this->assertFalse($configuration->verboseOutput('unsilenced'));
+ $this->assertFalse($configuration->verboseOutput('direct'));
+ $this->assertFalse($configuration->verboseOutput('indirect'));
+ $this->assertFalse($configuration->verboseOutput('self'));
+ $this->assertFalse($configuration->verboseOutput('other'));
+ }
+
+ private function buildGroups($counts)
+ {
+ $groups = [];
+ foreach ($counts as $name => $count) {
+ $groups[$name] = new DeprecationGroup();
+ $i = 0;
+ while ($i++ < $count) {
+ $groups[$name]->addNotice();
+ }
+ }
+
+ return $groups;
+ }
+
+ public function testBaselineGenerationEmptyFile()
+ {
+ $filename = $this->createFile();
+ $configuration = Configuration::fromUrlEncodedString('generateBaseline=true&baselineFile='.urlencode($filename));
+ $this->assertTrue($configuration->isGeneratingBaseline());
+ $trace = debug_backtrace();
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 2', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, '')));
+ $configuration->writeBaseline();
+ $this->assertEquals($filename, $configuration->getBaselineFile());
+ $expected_baseline = [
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 1',
+ 'count' => 2,
+ ],
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 2',
+ 'count' => 1,
+ ],
+ ];
+ $this->assertEquals(json_encode($expected_baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename));
+ }
+
+ public function testBaselineGenerationNoFile()
+ {
+ $filename = $this->createFile();
+ $configuration = Configuration::fromUrlEncodedString('generateBaseline=true&baselineFile='.urlencode($filename));
+ $this->assertTrue($configuration->isGeneratingBaseline());
+ $trace = debug_backtrace();
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 2', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 2', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, '')));
+ $configuration->writeBaseline();
+ $this->assertEquals($filename, $configuration->getBaselineFile());
+ $expected_baseline = [
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 1',
+ 'count' => 2,
+ ],
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 2',
+ 'count' => 2,
+ ],
+ ];
+ $this->assertEquals(json_encode($expected_baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename));
+ }
+
+ public function testExistingBaseline()
+ {
+ $filename = $this->createFile();
+ $baseline = [
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 1',
+ 'count' => 1,
+ ],
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 2',
+ 'count' => 1,
+ ],
+ ];
+ file_put_contents($filename, json_encode($baseline));
+
+ $configuration = Configuration::fromUrlEncodedString('baselineFile='.urlencode($filename));
+ $this->assertFalse($configuration->isGeneratingBaseline());
+ $trace = debug_backtrace();
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 1', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 2', $trace, '')));
+ $this->assertFalse($configuration->isBaselineDeprecation(new Deprecation('Test message 3', $trace, '')));
+ $this->assertEquals($filename, $configuration->getBaselineFile());
+ }
+
+ public function testExistingBaselineAndGeneration()
+ {
+ $filename = $this->createFile();
+ $baseline = [
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 1',
+ 'count' => 1,
+ ],
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 2',
+ 'count' => 1,
+ ],
+ ];
+ file_put_contents($filename, json_encode($baseline));
+ $configuration = Configuration::fromUrlEncodedString('generateBaseline=true&baselineFile='.urlencode($filename));
+ $this->assertTrue($configuration->isGeneratingBaseline());
+ $trace = debug_backtrace();
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 2', $trace, '')));
+ $this->assertTrue($configuration->isBaselineDeprecation(new Deprecation('Test message 3', $trace, '')));
+ $configuration->writeBaseline();
+ $this->assertEquals($filename, $configuration->getBaselineFile());
+ $expected_baseline = [
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 2',
+ 'count' => 1,
+ ],
+ [
+ 'location' => 'Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler\ConfigurationTest::runTest',
+ 'message' => 'Test message 3',
+ 'count' => 1,
+ ],
+ ];
+ $this->assertEquals(json_encode($expected_baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES), file_get_contents($filename));
+ }
+
+ public function testBaselineArgumentException()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('You cannot use the "generateBaseline" configuration option without providing a "baselineFile" configuration option.');
+ Configuration::fromUrlEncodedString('generateBaseline=true');
+ }
+
+ public function testBaselineFileException()
+ {
+ $filename = $this->createFile();
+ unlink($filename);
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage(sprintf('The baselineFile "%s" does not exist.', $filename));
+ Configuration::fromUrlEncodedString('baselineFile='.urlencode($filename));
+ }
+
+ public function testBaselineFileWriteError()
+ {
+ $filename = $this->createFile();
+ chmod($filename, 0444);
+ $this->expectError();
+ $this->expectErrorMessageMatches('/[Ff]ailed to open stream: Permission denied/');
+ $configuration = Configuration::fromUrlEncodedString('generateBaseline=true&baselineFile='.urlencode($filename));
+ $configuration->writeBaseline();
+ }
+
+ protected function setUp(): void
+ {
+ $this->files = [];
+ }
+
+ protected function tearDown(): void
+ {
+ foreach ($this->files as $file) {
+ if (file_exists($file)) {
+ @unlink($file);
+ }
+ }
+ }
+
+ private function createFile()
+ {
+ $filename = tempnam(sys_get_temp_dir(), 'sf-');
+ $this->files[] = $filename;
+
+ return $filename;
}
}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationGroupTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationGroupTest.php
new file mode 100644
index 0000000000000..df746e5e38907
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationGroupTest.php
@@ -0,0 +1,30 @@
+addNoticeFromObject(
+ 'Calling sfContext::getInstance() is deprecated',
+ 'MonsterController',
+ 'get5klocMethod'
+ );
+ $group->addNoticeFromProceduralCode('Calling sfContext::getInstance() is deprecated');
+ $this->assertCount(1, $group->notices());
+ $this->assertSame(2, $group->count());
+ }
+
+ public function testItAllowsAddingANoticeWithoutClutteringTheMemory()
+ {
+ // this is useful for notices in the legacy group
+ $group = new DeprecationGroup();
+ $group->addNotice();
+ $this->assertSame(1, $group->count());
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationNoticeTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationNoticeTest.php
new file mode 100644
index 0000000000000..c0a88c443b4d7
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationNoticeTest.php
@@ -0,0 +1,35 @@
+addObjectOccurrence('MyAction', '__invoke');
+ $notice->addObjectOccurrence('MyAction', '__invoke');
+ $notice->addObjectOccurrence('MyOtherAction', '__invoke');
+
+ $countsByCaller = $notice->getCountsByCaller();
+
+ $this->assertCount(2, $countsByCaller);
+ $this->assertArrayHasKey('MyAction::__invoke', $countsByCaller);
+ $this->assertArrayHasKey('MyOtherAction::__invoke', $countsByCaller);
+ $this->assertSame(2, $countsByCaller['MyAction::__invoke']);
+ $this->assertSame(1, $countsByCaller['MyOtherAction::__invoke']);
+ }
+
+ public function testItCountsBothTypesOfOccurrences()
+ {
+ $notice = new DeprecationNotice();
+ $notice->addObjectOccurrence('MyAction', '__invoke');
+ $this->assertSame(1, $notice->count());
+
+ $notice->addProceduralOccurrence();
+ $this->assertSame(2, $notice->count());
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php
index 9cb0a0e32ce3a..a1d3c06ea668f 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php
@@ -14,7 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
-use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5;
+use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7;
class DeprecationTest extends TestCase
{
@@ -30,7 +30,7 @@ private static function getVendorDir()
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
- $vendorDir = \dirname(\dirname($r->getFileName()));
+ $vendorDir = \dirname($r->getFileName(), 2);
if (file_exists($vendorDir.'/composer/installed.json') && @mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true)) {
break;
}
@@ -58,7 +58,7 @@ public function testItCanTellWhetherItIsInternal()
{
$r = new \ReflectionClass(Deprecation::class);
- if (\dirname(\dirname($r->getFileName())) !== \dirname(\dirname(__DIR__))) {
+ if (\dirname($r->getFileName(), 2) !== \dirname(__DIR__, 2)) {
$this->markTestSkipped('Test case is not compatible with having the bridge in vendor/');
}
@@ -161,7 +161,7 @@ public function providerGetTypeDetectsSelf()
'triggering_file' => 'dummy_vendor_path',
'files_stack' => [],
]),
- SymfonyTestsListenerForV5::class,
+ SymfonyTestsListenerForV7::class,
'',
],
];
@@ -188,7 +188,7 @@ public function providerGetTypeUsesRightTrace()
$fakeTrace = [
['function' => 'trigger_error'],
['class' => SymfonyTestsListenerTrait::class, 'function' => 'endTest'],
- ['class' => SymfonyTestsListenerForV5::class, 'function' => 'endTest'],
+ ['class' => SymfonyTestsListenerForV7::class, 'function' => 'endTest'],
];
return [
@@ -270,7 +270,7 @@ public static function setupBeforeClass(): void
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
- $v = \dirname(\dirname($r->getFileName()));
+ $v = \dirname($r->getFileName(), 2);
if (file_exists($v.'/composer/installed.json')) {
$loader = require $v.'/autoload.php';
$reflection = new \ReflectionClass($loader);
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline.phpt
new file mode 100644
index 0000000000000..533912c106cbd
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline.phpt
@@ -0,0 +1,80 @@
+--TEST--
+Test DeprecationErrorHandler in baseline mode
+--FILE--
+ 'FooTestCase::testLegacyFoo',
+ 'message' => 'silenced foo deprecation',
+ 'count' => 1,
+],
+[
+ 'location' => 'FooTestCase::testNonLegacyBar',
+ 'message' => 'silenced bar deprecation',
+ 'count' => 1,
+],
+[
+ 'location' => 'procedural code',
+ 'message' => 'root deprecation',
+ 'count' => 1,
+]];
+file_put_contents($filename, json_encode($baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
+
+$k = 'SYMFONY_DEPRECATIONS_HELPER';
+unset($_SERVER[$k], $_ENV[$k]);
+putenv($k.'='.$_SERVER[$k] = $_ENV[$k] = 'baselineFile=' . urlencode($filename));
+putenv('ANSICON');
+putenv('ConEmuANSI');
+putenv('TERM');
+
+$vendor = __DIR__;
+while (!file_exists($vendor.'/vendor')) {
+ $vendor = dirname($vendor);
+}
+define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
+require PHPUNIT_COMPOSER_INSTALL;
+require_once __DIR__.'/../../bootstrap.php';
+
+@trigger_error('root deprecation', E_USER_DEPRECATED);
+
+eval(<<<'EOPHP'
+namespace PHPUnit\Util;
+
+class Test
+{
+ public static function getGroups()
+ {
+ return array();
+ }
+}
+EOPHP
+);
+
+class PHPUnit_Util_Test
+{
+ public static function getGroups()
+ {
+ return array();
+ }
+}
+
+class FooTestCase
+{
+ public function testLegacyFoo()
+ {
+ @trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
+ }
+
+ public function testNonLegacyBar()
+ {
+ @trigger_error('silenced bar deprecation', E_USER_DEPRECATED);
+ }
+}
+
+$foo = new FooTestCase();
+$foo->testLegacyFoo();
+$foo->testNonLegacyBar();
+print "Cannot test baselineFile contents because it is generated in a shutdown function registered by another shutdown function."
+?>
+--EXPECT--
+Cannot test baselineFile contents because it is generated in a shutdown function registered by another shutdown function.
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline2.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline2.phpt
new file mode 100644
index 0000000000000..f520912694a1e
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline2.phpt
@@ -0,0 +1,74 @@
+--TEST--
+Test DeprecationErrorHandler in baseline mode
+--FILE--
+ 'FooTestCase::testLegacyFoo',
+ 'message' => 'silenced foo deprecation',
+ 'count' => 1,
+]];
+file_put_contents($filename, json_encode($baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
+
+$k = 'SYMFONY_DEPRECATIONS_HELPER';
+unset($_SERVER[$k], $_ENV[$k]);
+putenv($k.'='.$_SERVER[$k] = $_ENV[$k] = 'baselineFile=' . urlencode($filename));
+putenv('ANSICON');
+putenv('ConEmuANSI');
+putenv('TERM');
+
+$vendor = __DIR__;
+while (!file_exists($vendor.'/vendor')) {
+ $vendor = dirname($vendor);
+}
+define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
+require PHPUNIT_COMPOSER_INSTALL;
+require_once __DIR__.'/../../bootstrap.php';
+
+@trigger_error('root deprecation', E_USER_DEPRECATED);
+
+eval(<<<'EOPHP'
+namespace PHPUnit\Util;
+
+class Test
+{
+ public static function getGroups()
+ {
+ return array();
+ }
+}
+EOPHP
+);
+
+class PHPUnit_Util_Test
+{
+ public static function getGroups()
+ {
+ return array();
+ }
+}
+
+class FooTestCase
+{
+ public function testLegacyFoo()
+ {
+ @trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
+ }
+
+ public function testNonLegacyBar()
+ {
+ @trigger_error('silenced bar deprecation', E_USER_DEPRECATED);
+ }
+}
+
+$foo = new FooTestCase();
+$foo->testLegacyFoo();
+$foo->testNonLegacyBar();
+?>
+--EXPECTF--
+Other deprecation notices (2)
+
+ 1x: root deprecation
+
+ 1x: silenced bar deprecation
+ 1x in FooTestCase::testNonLegacyBar
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline3.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline3.phpt
new file mode 100644
index 0000000000000..28d1a74ffd427
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/baseline3.phpt
@@ -0,0 +1,79 @@
+--TEST--
+Test DeprecationErrorHandler in baseline mode
+--FILE--
+ 'FooTestCase::testLegacyFoo',
+ 'message' => 'silenced foo deprecation',
+ 'count' => 1,
+]];
+file_put_contents($filename, json_encode($baseline, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
+
+$k = 'SYMFONY_DEPRECATIONS_HELPER';
+unset($_SERVER[$k], $_ENV[$k]);
+putenv($k.'='.$_SERVER[$k] = $_ENV[$k] = 'baselineFile=' . urlencode($filename));
+putenv('ANSICON');
+putenv('ConEmuANSI');
+putenv('TERM');
+
+$vendor = __DIR__;
+while (!file_exists($vendor.'/vendor')) {
+ $vendor = dirname($vendor);
+}
+define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
+require PHPUNIT_COMPOSER_INSTALL;
+require_once __DIR__.'/../../bootstrap.php';
+
+@trigger_error('root deprecation', E_USER_DEPRECATED);
+
+eval(<<<'EOPHP'
+namespace PHPUnit\Util;
+
+class Test
+{
+ public static function getGroups()
+ {
+ return array();
+ }
+}
+EOPHP
+);
+
+class PHPUnit_Util_Test
+{
+ public static function getGroups()
+ {
+ return array();
+ }
+}
+
+class FooTestCase
+{
+ public function testLegacyFoo()
+ {
+ @trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
+ // This will cause a deprecation because the baseline only expects 1
+ // deprecation.
+ @trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
+ }
+
+ public function testNonLegacyBar()
+ {
+ @trigger_error('silenced bar deprecation', E_USER_DEPRECATED);
+ }
+}
+
+$foo = new FooTestCase();
+$foo->testLegacyFoo();
+$foo->testNonLegacyBar();
+?>
+--EXPECTF--
+Legacy deprecation notices (1)
+
+Other deprecation notices (2)
+
+ 1x: root deprecation
+
+ 1x: silenced bar deprecation
+ 1x in FooTestCase::testNonLegacyBar
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled_1.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled_1.phpt
new file mode 100644
index 0000000000000..acb5f096306d0
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/disabled_1.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Test DeprecationErrorHandler in default mode
+--FILE--
+
+--EXPECTREGEX--
+.{0}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/AppService.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/AppService.php
index 2e5ccf3c78811..2b6cb316af143 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/AppService.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/AppService.php
@@ -7,6 +7,37 @@
final class AppService
{
+ public function directDeprecationsTwoVendors()
+ {
+ $service1 = new SomeService();
+ $service1->deprecatedApi();
+
+ $service2 = new SomeOtherService();
+ $service2->deprecatedApi();
+ }
+
+ public function selfDeprecation(bool $useContracts = false)
+ {
+ $args = [__FUNCTION__, __FUNCTION__];
+ if ($useContracts) {
+ trigger_deprecation('App', '3.0', sprintf('%s is deprecated, use %s_new instead.', ...$args));
+ } else {
+ @trigger_error(sprintf('Since App 3.0: %s is deprecated, use %s_new instead.', ...$args), \E_USER_DEPRECATED);
+ }
+ }
+
+ public function directDeprecation(bool $useContracts = false)
+ {
+ $service = new SomeService();
+ $service->deprecatedApi($useContracts);
+ }
+
+ public function indirectDeprecation(bool $useContracts = false)
+ {
+ $service = new SomeService();
+ $service->indirectDeprecatedApi($useContracts);
+ }
+
public function directDeprecations()
{
$service1 = new SomeService();
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/SomeService.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/SomeService.php
index d18cfbd3319bd..cc237e6146c23 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/SomeService.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/SomeService.php
@@ -2,13 +2,22 @@
namespace acme\lib;
+use bar\lib\AnotherService;
+
class SomeService
{
- public function deprecatedApi()
+ public function deprecatedApi(bool $useContracts = false)
+ {
+ $args = [__FUNCTION__, __FUNCTION__];
+ if ($useContracts) {
+ trigger_deprecation('acme/lib', '3.0', sprintf('%s is deprecated, use %s_new instead.', ...$args));
+ } else {
+ @trigger_error(sprintf('Since acme/lib 3.0: %s is deprecated, use %s_new instead.', ...$args), \E_USER_DEPRECATED);
+ }
+ }
+
+ public function indirectDeprecatedApi(bool $useContracts = false)
{
- @trigger_error(
- __FUNCTION__.' is deprecated! You should stop relying on it!',
- \E_USER_DEPRECATED
- );
+ (new AnotherService())->deprecatedApi($useContracts);
}
}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/bar/lib/AnotherService.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/bar/lib/AnotherService.php
new file mode 100644
index 0000000000000..2e2f0f9b6b4b5
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/bar/lib/AnotherService.php
@@ -0,0 +1,16 @@
+ [__DIR__.'/../../fake_app/'],
'acme\\lib\\' => [__DIR__.'/../acme/lib/'],
+ 'bar\\lib\\' => [__DIR__.'/../bar/lib/'],
'fcy\\lib\\' => [__DIR__.'/../fcy/lib/'],
];
}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/generate_baseline.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/generate_baseline.phpt
new file mode 100644
index 0000000000000..112a02b4c41a0
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/generate_baseline.phpt
@@ -0,0 +1,64 @@
+--TEST--
+Test DeprecationErrorHandler in baseline generation mode
+--FILE--
+testLegacyFoo();
+$foo->testNonLegacyBar();
+print "Cannot test baselineFile contents because it is generated in a shutdown function registered by another shutdown function."
+?>
+--EXPECT--
+Cannot test baselineFile contents because it is generated in a shutdown function registered by another shutdown function.
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/lagging_vendor.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/lagging_vendor.phpt
index 2f7686fd98819..b5253df4299e4 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/lagging_vendor.phpt
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/lagging_vendor.phpt
@@ -35,5 +35,5 @@ require __DIR__.'/fake_vendor/acme/outdated-lib/outdated_file.php';
--EXPECTF--
Remaining indirect deprecation notices (1)
- 1x: deprecatedApi is deprecated! You should stop relying on it!
+ 1x: Since acme/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
1x in SomeService::deprecatedApi from acme\lib
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/log_file.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/log_file.phpt
new file mode 100644
index 0000000000000..7f114ab5e2e5a
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/log_file.phpt
@@ -0,0 +1,58 @@
+--TEST--
+Test DeprecationErrorHandler with log file
+--FILE--
+testLegacyFoo();
+$foo->testLegacyBar();
+
+register_shutdown_function(function () use ($filename) {
+ var_dump(file_get_contents($filename));
+});
+?>
+--EXPECTF--
+string(234) "
+Unsilenced deprecation notices (3)
+
+ 2x: unsilenced foo deprecation
+ 2x in FooTestCase::testLegacyFoo
+
+ 1x: unsilenced bar deprecation
+ 1x in FooTestCase::testLegacyBar
+
+Other deprecation notices (1)
+
+ 1x: root deprecation
+
+"
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/multiple_autoloads.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/multiple_autoloads.phpt
index 336001f500113..edf9f4f6f3731 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/multiple_autoloads.phpt
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/multiple_autoloads.phpt
@@ -38,7 +38,7 @@ require __DIR__.'/fake_vendor_bis/autoload.php';
--EXPECTF--
Remaining direct deprecation notices (2)
- 1x: deprecatedApi is deprecated! You should stop relying on it!
+ 1x: Since acme/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
1x in AppService::directDeprecations from App\Services
1x: deprecatedApi from foo is deprecated! You should stop relying on it!
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet.phpt
new file mode 100644
index 0000000000000..83b135b34bab7
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test DeprecationErrorHandler quiet on everything but indirect deprecations
+--FILE--
+
+--EXPECTF--
+Unsilenced deprecation notices (3)
+
+Remaining direct deprecation notices (2)
+
+Remaining indirect deprecation notices (1)
+
+ 1x: Since acme/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
+ 1x in SomeService::deprecatedApi from acme\lib
+
+Legacy deprecation notices (2)
+
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet_but_failing.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet_but_failing.phpt
new file mode 100644
index 0000000000000..8d8f8b4ff490d
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet_but_failing.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Test DeprecationErrorHandler when failing and not verbose
+--FILE--
+
+--EXPECTF--
+Remaining indirect deprecation notices (1)
+
+ 1x: Since acme/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
+ 1x in SomeService::deprecatedApi from acme\lib
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/trigger_deprecation_types.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/trigger_deprecation_types.phpt
new file mode 100644
index 0000000000000..261b6ec83f675
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/trigger_deprecation_types.phpt
@@ -0,0 +1,58 @@
+--TEST--
+Test deprecation types with trigger_deprecation
+--FILE--
+selfDeprecation(true);
+(new \App\Services\AppService())->directDeprecation(true);
+(new \App\Services\AppService())->indirectDeprecation(true);
+trigger_deprecation('foo/bar', '2.0', 'func is deprecated, use new instead.');
+?>
+--EXPECTF--
+Remaining self deprecation notices (1)
+
+ 1x: Since App 3.0: selfDeprecation is deprecated, use selfDeprecation_new instead.
+ 1x in AppService::selfDeprecation from App\Services
+
+Remaining direct deprecation notices (1)
+
+ 1x: Since acme/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
+ 1x in AppService::directDeprecation from App\Services
+
+Remaining indirect deprecation notices (1)
+
+ 1x: Since bar/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
+ 1x in AppService::indirectDeprecation from App\Services
+
+Other deprecation notices (1)
+
+ 1x: Since foo/bar 2.0: func is deprecated, use new instead.
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/trigger_error_types.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/trigger_error_types.phpt
new file mode 100644
index 0000000000000..4dd6bdaed9f0b
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/trigger_error_types.phpt
@@ -0,0 +1,58 @@
+--TEST--
+Test deprecation types with trigger_error
+--FILE--
+selfDeprecation();
+(new \App\Services\AppService())->directDeprecation();
+(new \App\Services\AppService())->indirectDeprecation();
+@trigger_error('Since foo/bar 2.0: func is deprecated, use new instead.', E_USER_DEPRECATED);
+?>
+--EXPECTF--
+Remaining self deprecation notices (1)
+
+ 1x: Since App 3.0: selfDeprecation is deprecated, use selfDeprecation_new instead.
+ 1x in AppService::selfDeprecation from App\Services
+
+Remaining direct deprecation notices (1)
+
+ 1x: Since acme/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
+ 1x in AppService::directDeprecation from App\Services
+
+Remaining indirect deprecation notices (1)
+
+ 1x: Since bar/lib 3.0: deprecatedApi is deprecated, use deprecatedApi_new instead.
+ 1x in AppService::indirectDeprecation from App\Services
+
+Other deprecation notices (1)
+
+ 1x: Since foo/bar 2.0: func is deprecated, use new instead.
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ExpectDeprecationTraitTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ExpectDeprecationTraitTest.php
new file mode 100644
index 0000000000000..5e6350e673101
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/ExpectDeprecationTraitTest.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
+
+final class ExpectDeprecationTraitTest extends TestCase
+{
+ use ExpectDeprecationTrait;
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ */
+ public function testOne()
+ {
+ $this->expectDeprecation('foo');
+ @trigger_error('foo', \E_USER_DEPRECATED);
+ }
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ * @runInSeparateProcess
+ */
+ public function testOneInIsolation()
+ {
+ $this->expectDeprecation('foo');
+ @trigger_error('foo', \E_USER_DEPRECATED);
+ }
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ */
+ public function testMany()
+ {
+ $this->expectDeprecation('foo');
+ $this->expectDeprecation('bar');
+ @trigger_error('foo', \E_USER_DEPRECATED);
+ @trigger_error('bar', \E_USER_DEPRECATED);
+ }
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ *
+ * @expectedDeprecation foo
+ */
+ public function testOneWithAnnotation()
+ {
+ $this->expectDeprecation('bar');
+ @trigger_error('foo', \E_USER_DEPRECATED);
+ @trigger_error('bar', \E_USER_DEPRECATED);
+ }
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ *
+ * @expectedDeprecation foo
+ * @expectedDeprecation bar
+ */
+ public function testManyWithAnnotation()
+ {
+ $this->expectDeprecation('ccc');
+ $this->expectDeprecation('fcy');
+ @trigger_error('foo', \E_USER_DEPRECATED);
+ @trigger_error('bar', \E_USER_DEPRECATED);
+ @trigger_error('ccc', \E_USER_DEPRECATED);
+ @trigger_error('fcy', \E_USER_DEPRECATED);
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/FailTests/ExpectDeprecationTraitTestFail.php b/src/Symfony/Bridge/PhpUnit/Tests/FailTests/ExpectDeprecationTraitTestFail.php
new file mode 100644
index 0000000000000..ba35b268deebe
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/FailTests/ExpectDeprecationTraitTestFail.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit\Tests\FailTests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
+
+/**
+ * Class ExpectDeprecationTraitTestFail.
+ *
+ * This class is deliberately suffixed with *TestFail.php so that it is ignored
+ * by PHPUnit. This test is designed to fail. See ../expectdeprecationfail.phpt.
+ */
+final class ExpectDeprecationTraitTestFail extends TestCase
+{
+ use ExpectDeprecationTrait;
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ */
+ public function testOne()
+ {
+ $this->expectDeprecation('foo');
+ @trigger_error('bar', \E_USER_DEPRECATED);
+ }
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ * @runInSeparateProcess
+ */
+ public function testOneInIsolation()
+ {
+ $this->expectDeprecation('foo');
+ @trigger_error('bar', \E_USER_DEPRECATED);
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/FailTests/NoAssertionsTestRisky.php b/src/Symfony/Bridge/PhpUnit/Tests/FailTests/NoAssertionsTestRisky.php
new file mode 100644
index 0000000000000..4a22baf125a7f
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/FailTests/NoAssertionsTestRisky.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit\Tests\FailTests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
+
+/**
+ * This class is deliberately suffixed with *TestRisky.php so that it is ignored
+ * by PHPUnit. This test is designed to fail. See ../expectrisky.phpt.
+ */
+final class NoAssertionsTestRisky extends TestCase
+{
+ use ExpectDeprecationTrait;
+
+ /**
+ * Do not remove this test in the next major version.
+ *
+ * @group legacy
+ */
+ public function testOne()
+ {
+ $this->expectNotToPerformAssertions();
+ $this->expectDeprecation('foo');
+ @trigger_error('foo', \E_USER_DEPRECATED);
+ }
+
+ /**
+ * Do not remove this test in the next major version.
+ */
+ public function testTwo()
+ {
+ $this->expectNotToPerformAssertions();
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php
index 3e45381dce2a0..e302fa05ea74c 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php
@@ -12,14 +12,4 @@
require __DIR__.'/../src/BarCov.php';
require __DIR__.'/../src/FooCov.php';
-require __DIR__.'/../../../../Legacy/CoverageListenerTrait.php';
-
-if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
- require_once __DIR__.'/../../../../Legacy/CoverageListenerForV5.php';
-} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
- require_once __DIR__.'/../../../../Legacy/CoverageListenerForV6.php';
-} else {
- require_once __DIR__.'/../../../../Legacy/CoverageListenerForV7.php';
-}
-
require __DIR__.'/../../../../CoverageListener.php';
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt b/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt
new file mode 100644
index 0000000000000..f968cd188a0a7
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Test ExpectDeprecationTrait failing tests
+--FILE--
+
+--EXPECTF--
+PHPUnit %s
+
+%ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\ExpectDeprecationTraitTestFail
+FF 2 / 2 (100%)
+
+Time: %s, Memory: %s
+
+There were 2 failures:
+
+1) Symfony\Bridge\PhpUnit\Tests\FailTests\ExpectDeprecationTraitTestFail::testOne
+Failed asserting that string matches format description.
+--- Expected
++++ Actual
+@@ @@
+ @expectedDeprecation:
+-%A foo
++ bar
+
+2) Symfony\Bridge\PhpUnit\Tests\FailTests\ExpectDeprecationTraitTestFail::testOneInIsolation
+Failed asserting that string matches format description.
+--- Expected
++++ Actual
+@@ @@
+ @expectedDeprecation:
+-%A foo
++ bar
+
+FAILURES!
+Tests: 2, Assertions: 2, Failures: 2.
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt b/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt
new file mode 100644
index 0000000000000..91e0830553950
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Test NoAssertionsTestRisky risky test
+--SKIPIF--
+
+--EXPECTF--
+PHPUnit %s
+
+%ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\NoAssertionsTestRisky
+R. 2 / 2 (100%)
+
+Time: %s, Memory: %s
+
+There was 1 risky test:
+
+1) Symfony\Bridge\PhpUnit\Tests\FailTests\NoAssertionsTestRisky::testOne
+This test is annotated with "@doesNotPerformAssertions", but performed 1 assertions
+
+OK, but incomplete, skipped, or risky tests!
+Tests: 2, Assertions: 1, Risky: 1.
diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php
index 8690812b56b57..3cc158f6b8e72 100644
--- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php
+++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php
@@ -11,10 +11,8 @@
namespace Symfony\Bridge\PhpUnit\TextUI;
-if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
- class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV5', 'Symfony\Bridge\PhpUnit\TextUI\Command');
-} elseif (version_compare(\PHPUnit\Runner\Version::id(), '9.0.0', '<')) {
- class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV6', 'Symfony\Bridge\PhpUnit\TextUI\Command');
+if (version_compare(\PHPUnit\Runner\Version::id(), '9.0.0', '<')) {
+ class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV7', 'Symfony\Bridge\PhpUnit\TextUI\Command');
} else {
class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV9', 'Symfony\Bridge\PhpUnit\TextUI\Command');
}
diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
index e7c3da2b8d7a3..ea88872ae9b86 100644
--- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
+++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
@@ -15,8 +15,8 @@
error_reporting(-1);
global $argv, $argc;
-$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : [];
-$argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0;
+$argv = $_SERVER['argv'] ?? [];
+$argc = $_SERVER['argc'] ?? 0;
$getEnvVar = function ($name, $default = false) use ($argv) {
if (false !== $value = getenv($name)) {
return $value;
@@ -99,18 +99,14 @@
} elseif (\PHP_VERSION_ID >= 70200) {
// PHPUnit 8 requires PHP 7.2+
$PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '8.5') ?: '8.5';
-} elseif (\PHP_VERSION_ID >= 70100) {
- // PHPUnit 7 requires PHP 7.1+
- $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.5') ?: '7.5';
-} elseif (\PHP_VERSION_ID >= 70000) {
- // PHPUnit 6 requires PHP 7.0+
- $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '6.5') ?: '6.5';
-} elseif (\PHP_VERSION_ID >= 50600) {
- // PHPUnit 4 does not support PHP 7
- $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '5.7') ?: '5.7';
} else {
- // PHPUnit 5.1 requires PHP 5.6+
- $PHPUNIT_VERSION = '4.8';
+ $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.5') ?: '7.5';
+}
+
+$MAX_PHPUNIT_VERSION = $getEnvVar('SYMFONY_MAX_PHPUNIT_VERSION', false);
+
+if ($MAX_PHPUNIT_VERSION && version_compare($MAX_PHPUNIT_VERSION, $PHPUNIT_VERSION, '<')) {
+ $PHPUNIT_VERSION = $MAX_PHPUNIT_VERSION;
}
$PHPUNIT_REMOVE_RETURN_TYPEHINT = filter_var($getEnvVar('SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT', '0'), \FILTER_VALIDATE_BOOLEAN);
@@ -137,6 +133,7 @@
'COMPOSER' => 'composer.json',
'COMPOSER_VENDOR_DIR' => 'vendor',
'COMPOSER_BIN_DIR' => 'bin',
+ 'SYMFONY_SIMPLE_PHPUNIT_BIN_DIR' => __DIR__,
];
foreach ($defaultEnvs as $envName => $envValue) {
@@ -171,7 +168,8 @@
}
}
$SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml' : ''));
-$configurationHash = md5(implode(\PHP_EOL, [md5_file(__FILE__), $SYMFONY_PHPUNIT_REMOVE, (int) $PHPUNIT_REMOVE_RETURN_TYPEHINT]));
+$SYMFONY_PHPUNIT_REQUIRE = $getEnvVar('SYMFONY_PHPUNIT_REQUIRE', '');
+$configurationHash = md5(implode(\PHP_EOL, [md5_file(__FILE__), $SYMFONY_PHPUNIT_REMOVE, $SYMFONY_PHPUNIT_REQUIRE, (int) $PHPUNIT_REMOVE_RETURN_TYPEHINT]));
$PHPUNIT_VERSION_DIR = sprintf('phpunit-%s-%d', $PHPUNIT_VERSION, $PHPUNIT_REMOVE_RETURN_TYPEHINT);
if (!file_exists("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit") || $configurationHash !== @file_get_contents("$PHPUNIT_DIR/.$PHPUNIT_VERSION_DIR.md5")) {
// Build a standalone phpunit without symfony/yaml nor prophecy by default
@@ -228,6 +226,9 @@
if ($SYMFONY_PHPUNIT_REMOVE) {
$passthruOrFail("$COMPOSER remove --no-update --no-interaction ".$SYMFONY_PHPUNIT_REMOVE);
}
+ if ($SYMFONY_PHPUNIT_REQUIRE) {
+ $passthruOrFail("$COMPOSER require --no-update --no-interaction ".$SYMFONY_PHPUNIT_REQUIRE);
+ }
if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) {
$passthruOrFail("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\"");
}
@@ -271,12 +272,12 @@
if ($PHPUNIT_REMOVE_RETURN_TYPEHINT) {
$alteredCode = preg_replace('/^ ((?:protected|public)(?: static)? function \w+\(\)): void/m', ' $1', $alteredCode);
}
- $alteredCode = preg_replace('/abstract class (?:TestCase|PHPUnit_Framework_TestCase)[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillTestCaseTrait;", $alteredCode, 1);
+ $alteredCode = preg_replace('/abstract class TestCase[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillTestCaseTrait;", $alteredCode, 1);
file_put_contents($alteredFile, $alteredCode);
// Mutate Assert code
$alteredCode = file_get_contents($alteredFile = './src/Framework/Assert.php');
- $alteredCode = preg_replace('/abstract class (?:Assert|PHPUnit_Framework_Assert)[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillAssertTrait;", $alteredCode, 1);
+ $alteredCode = preg_replace('/abstract class Assert[^\{]+\{/', '$0 '.\PHP_EOL." use \Symfony\Bridge\PhpUnit\Legacy\PolyfillAssertTrait;", $alteredCode, 1);
file_put_contents($alteredFile, $alteredCode);
file_put_contents('phpunit', <<<'EOPHP'
@@ -373,7 +374,7 @@ class_exists(\SymfonyExcludeListSimplePhpunit::class, false) && PHPUnit\Util\Bla
}
if ($components) {
- $skippedTests = isset($_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS']) ? $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] : false;
+ $skippedTests = $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] ?? false;
$runningProcs = [];
foreach ($components as $component) {
diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php
index d9947a7f4e1c8..e07c8d6cf5de8 100644
--- a/src/Symfony/Bridge/PhpUnit/bootstrap.php
+++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php
@@ -12,96 +12,6 @@
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
-if (class_exists(\PHPUnit_Runner_Version::class) && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
- $classes = [
- 'PHPUnit_Framework_Assert', // override PhpUnit's ForwardCompat child class
- 'PHPUnit_Framework_AssertionFailedError', // override PhpUnit's ForwardCompat child class
- 'PHPUnit_Framework_BaseTestListener', // override PhpUnit's ForwardCompat child class
-
- 'PHPUnit_Framework_Constraint',
- 'PHPUnit_Framework_Constraint_ArrayHasKey',
- 'PHPUnit_Framework_Constraint_ArraySubset',
- 'PHPUnit_Framework_Constraint_Attribute',
- 'PHPUnit_Framework_Constraint_Callback',
- 'PHPUnit_Framework_Constraint_ClassHasAttribute',
- 'PHPUnit_Framework_Constraint_ClassHasStaticAttribute',
- 'PHPUnit_Framework_Constraint_Composite',
- 'PHPUnit_Framework_Constraint_Count',
- 'PHPUnit_Framework_Constraint_Exception',
- 'PHPUnit_Framework_Constraint_ExceptionCode',
- 'PHPUnit_Framework_Constraint_ExceptionMessage',
- 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp',
- 'PHPUnit_Framework_Constraint_FileExists',
- 'PHPUnit_Framework_Constraint_GreaterThan',
- 'PHPUnit_Framework_Constraint_IsAnything',
- 'PHPUnit_Framework_Constraint_IsEmpty',
- 'PHPUnit_Framework_Constraint_IsEqual',
- 'PHPUnit_Framework_Constraint_IsFalse',
- 'PHPUnit_Framework_Constraint_IsIdentical',
- 'PHPUnit_Framework_Constraint_IsInstanceOf',
- 'PHPUnit_Framework_Constraint_IsJson',
- 'PHPUnit_Framework_Constraint_IsNull',
- 'PHPUnit_Framework_Constraint_IsTrue',
- 'PHPUnit_Framework_Constraint_IsType',
- 'PHPUnit_Framework_Constraint_JsonMatches',
- 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider',
- 'PHPUnit_Framework_Constraint_LessThan',
- 'PHPUnit_Framework_Constraint_ObjectHasAttribute',
- 'PHPUnit_Framework_Constraint_PCREMatch',
- 'PHPUnit_Framework_Constraint_SameSize',
- 'PHPUnit_Framework_Constraint_StringContains',
- 'PHPUnit_Framework_Constraint_StringEndsWith',
- 'PHPUnit_Framework_Constraint_StringMatches',
- 'PHPUnit_Framework_Constraint_StringStartsWith',
- 'PHPUnit_Framework_Constraint_TraversableContains',
- 'PHPUnit_Framework_Constraint_TraversableContainsOnly',
-
- 'PHPUnit_Framework_Error_Deprecated',
- 'PHPUnit_Framework_Error_Notice',
- 'PHPUnit_Framework_Error_Warning',
- 'PHPUnit_Framework_Exception',
- 'PHPUnit_Framework_ExpectationFailedException',
-
- 'PHPUnit_Framework_MockObject_MockObject',
-
- 'PHPUnit_Framework_IncompleteTest',
- 'PHPUnit_Framework_IncompleteTestCase',
- 'PHPUnit_Framework_IncompleteTestError',
- 'PHPUnit_Framework_RiskyTest',
- 'PHPUnit_Framework_RiskyTestError',
- 'PHPUnit_Framework_SkippedTest',
- 'PHPUnit_Framework_SkippedTestCase',
- 'PHPUnit_Framework_SkippedTestError',
- 'PHPUnit_Framework_SkippedTestSuiteError',
-
- 'PHPUnit_Framework_SyntheticError',
-
- 'PHPUnit_Framework_Test',
- 'PHPUnit_Framework_TestCase', // override PhpUnit's ForwardCompat child class
- 'PHPUnit_Framework_TestFailure',
- 'PHPUnit_Framework_TestListener',
- 'PHPUnit_Framework_TestResult',
- 'PHPUnit_Framework_TestSuite', // override PhpUnit's ForwardCompat child class
-
- 'PHPUnit_Runner_BaseTestRunner',
- 'PHPUnit_Runner_Version',
-
- 'PHPUnit_Util_Blacklist',
- 'PHPUnit_Util_ErrorHandler',
- 'PHPUnit_Util_Test',
- 'PHPUnit_Util_XML',
- ];
- foreach ($classes as $class) {
- class_alias($class, '\\'.strtr($class, '_', '\\'));
- }
-
- class_alias('PHPUnit_Framework_Constraint_And', 'PHPUnit\Framework\Constraint\LogicalAnd');
- class_alias('PHPUnit_Framework_Constraint_Not', 'PHPUnit\Framework\Constraint\LogicalNot');
- class_alias('PHPUnit_Framework_Constraint_Or', 'PHPUnit\Framework\Constraint\LogicalOr');
- class_alias('PHPUnit_Framework_Constraint_Xor', 'PHPUnit\Framework\Constraint\LogicalXor');
- class_alias('PHPUnit_Framework_Error', 'PHPUnit\Framework\Error\Error');
-}
-
// Detect if we need to serialize deprecations to a file.
if ($file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) {
DeprecationErrorHandler::collectDeprecations($file);
@@ -110,7 +20,7 @@ class_alias('PHPUnit_Framework_Error', 'PHPUnit\Framework\Error\Error');
}
// Detect if we're loaded by an actual run of phpunit
-if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists(\PHPUnit_TextUI_Command::class, false) && !class_exists(\PHPUnit\TextUI\Command::class, false)) {
+if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists(\PHPUnit\TextUI\Command::class, false)) {
return;
}
diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json
index 54cb26cf70164..b0ccda04315f1 100644
--- a/src/Symfony/Bridge/PhpUnit/composer.json
+++ b/src/Symfony/Bridge/PhpUnit/composer.json
@@ -16,18 +16,19 @@
}
],
"require": {
- "php": ">=5.5.9 EVEN ON LATEST SYMFONY VERSIONS TO ALLOW USING",
+ "php": ">=7.1.3 EVEN ON LATEST SYMFONY VERSIONS TO ALLOW USING",
"php": "THIS BRIDGE WHEN TESTING LOWEST SYMFONY VERSIONS.",
- "php": ">=5.5.9"
+ "php": ">=7.1.3"
},
"require-dev": {
- "symfony/error-handler": "^4.4|^5.0"
+ "symfony/deprecation-contracts": "^2.1|^3.0",
+ "symfony/error-handler": "^5.4|^6.0"
},
"suggest": {
"symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
},
"conflict": {
- "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0|9.1.2"
+ "phpunit/phpunit": "<7.5|9.1.2"
},
"autoload": {
"files": [ "bootstrap.php" ],
diff --git a/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist b/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist
index d37d2eac3650a..cde576e2c7536 100644
--- a/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist
+++ b/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist
@@ -1,7 +1,7 @@
-
-
+
+
./
-
- ./Tests
- ./vendor
-
-
-
+
+
+ ./Tests
+ ./vendor
+
+
diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php
index caea93ac5d9f4..abd2b1d384f1d 100644
--- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php
+++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php
@@ -27,6 +27,6 @@ class LazyLoadingValueHolderFactory extends BaseFactory
*/
public function getGenerator(): ProxyGeneratorInterface
{
- return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator();
+ return $this->generator ??= new LazyLoadingValueHolderGenerator();
}
}
diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php
index 7f5aa8ab51d86..1fd3459ae4cb4 100644
--- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php
+++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php
@@ -38,7 +38,7 @@ public function __construct()
/**
* {@inheritdoc}
*/
- public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
+ public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object
{
return $this->factory->createProxy(
$this->factory->getGenerator()->getProxifiedClass($definition),
diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
index 599fa7c6ec404..d3f23d393e50f 100644
--- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
+++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
@@ -25,7 +25,7 @@
*/
class ProxyDumper implements DumperInterface
{
- private $salt;
+ private string $salt;
private $proxyGenerator;
private $classGenerator;
@@ -47,7 +47,7 @@ public function isProxyCandidate(Definition $definition): bool
/**
* {@inheritdoc}
*/
- public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = null): string
+ public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string
{
$instantiation = 'return';
@@ -55,10 +55,6 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode =
$instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true));
}
- if (null === $factoryCode) {
- throw new \InvalidArgumentException(sprintf('Missing factory code to construct the service "%s".', $id));
- }
-
$proxyClass = $this->getProxyClassName($definition);
return <<expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Missing factory code to construct the service "foo".');
- $definition = new Definition(__CLASS__);
- $definition->setLazy(true);
- $this->dumper->getProxyFactoryCode($definition, 'foo');
- }
-
public function testGetProxyFactoryCodeForInterface()
{
$class = DummyClass::class;
diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json
index 577138489e690..430c4edd1e608 100644
--- a/src/Symfony/Bridge/ProxyManager/composer.json
+++ b/src/Symfony/Bridge/ProxyManager/composer.json
@@ -16,13 +16,12 @@
}
],
"require": {
- "php": ">=7.1.3",
+ "php": ">=8.0.2",
"friendsofphp/proxy-manager-lts": "^1.0.2",
- "symfony/dependency-injection": "^4.0|^5.0",
- "symfony/polyfill-php80": "^1.16"
+ "symfony/dependency-injection": "^5.4|^6.0"
},
"require-dev": {
- "symfony/config": "^3.4|^4.0|^5.0"
+ "symfony/config": "^5.4|^6.0"
},
"autoload": {
"psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" },
diff --git a/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist b/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist
index 60d6ffc3aab3e..d93048d2cbe15 100644
--- a/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist
+++ b/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist
@@ -1,7 +1,7 @@
-
-
+
+
./
-
- ./Resources
- ./Tests
- ./vendor
-
-
-
+
+
+ ./Resources
+ ./Tests
+ ./vendor
+
+
diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php
index 4a22265908b42..a6103a25c884a 100644
--- a/src/Symfony/Bridge/Twig/AppVariable.php
+++ b/src/Symfony/Bridge/Twig/AppVariable.php
@@ -16,6 +16,7 @@
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
/**
* Exposes some Symfony parameters and services as an "app" global variable.
@@ -26,8 +27,8 @@ class AppVariable
{
private $tokenStorage;
private $requestStack;
- private $environment;
- private $debug;
+ private string $environment;
+ private bool $debug;
public function setTokenStorage(TokenStorageInterface $tokenStorage)
{
@@ -39,62 +40,50 @@ public function setRequestStack(RequestStack $requestStack)
$this->requestStack = $requestStack;
}
- public function setEnvironment($environment)
+ public function setEnvironment(string $environment)
{
$this->environment = $environment;
}
- public function setDebug($debug)
+ public function setDebug(bool $debug)
{
- $this->debug = (bool) $debug;
+ $this->debug = $debug;
}
/**
* Returns the current token.
*
- * @return TokenInterface|null
- *
* @throws \RuntimeException When the TokenStorage is not available
*/
- public function getToken()
+ public function getToken(): ?TokenInterface
{
- if (null === $tokenStorage = $this->tokenStorage) {
+ if (!isset($this->tokenStorage)) {
throw new \RuntimeException('The "app.token" variable is not available.');
}
- return $tokenStorage->getToken();
+ return $this->tokenStorage->getToken();
}
/**
* Returns the current user.
*
- * @return object|null
- *
* @see TokenInterface::getUser()
*/
- public function getUser()
+ public function getUser(): ?UserInterface
{
- if (null === $tokenStorage = $this->tokenStorage) {
+ if (!isset($this->tokenStorage)) {
throw new \RuntimeException('The "app.user" variable is not available.');
}
- if (!$token = $tokenStorage->getToken()) {
- return null;
- }
-
- $user = $token->getUser();
-
- return \is_object($user) ? $user : null;
+ return $this->tokenStorage->getToken()?->getUser();
}
/**
* Returns the current request.
- *
- * @return Request|null The HTTP request object
*/
- public function getRequest()
+ public function getRequest(): ?Request
{
- if (null === $this->requestStack) {
+ if (!isset($this->requestStack)) {
throw new \RuntimeException('The "app.request" variable is not available.');
}
@@ -103,12 +92,10 @@ public function getRequest()
/**
* Returns the current session.
- *
- * @return Session|null The session
*/
- public function getSession()
+ public function getSession(): ?Session
{
- if (null === $this->requestStack) {
+ if (!isset($this->requestStack)) {
throw new \RuntimeException('The "app.session" variable is not available.');
}
$request = $this->getRequest();
@@ -118,12 +105,10 @@ public function getSession()
/**
* Returns the current app environment.
- *
- * @return string The current environment string (e.g 'dev')
*/
- public function getEnvironment()
+ public function getEnvironment(): string
{
- if (null === $this->environment) {
+ if (!isset($this->environment)) {
throw new \RuntimeException('The "app.environment" variable is not available.');
}
@@ -132,12 +117,10 @@ public function getEnvironment()
/**
* Returns the current app debug mode.
- *
- * @return bool The current debug mode
*/
- public function getDebug()
+ public function getDebug(): bool
{
- if (null === $this->debug) {
+ if (!isset($this->debug)) {
throw new \RuntimeException('The "app.debug" variable is not available.');
}
@@ -149,10 +132,8 @@ public function getDebug()
* * getFlashes() returns all the flash messages
* * getFlashes('notice') returns a simple array with flash messages of that type
* * getFlashes(['notice', 'error']) returns a nested array of type => messages.
- *
- * @return array
*/
- public function getFlashes($types = null)
+ public function getFlashes(string|array $types = null): array
{
try {
if (null === $session = $this->getSession()) {
diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md
index 71a30ae880d78..535df0c0897b4 100644
--- a/src/Symfony/Bridge/Twig/CHANGELOG.md
+++ b/src/Symfony/Bridge/Twig/CHANGELOG.md
@@ -1,12 +1,47 @@
CHANGELOG
=========
+5.4
+---
+
+* Add `github` format & autodetection to render errors as annotations when
+ running the Twig linter command in a Github Actions environment.
+
+5.3
+---
+
+ * Add a new `markAsPublic` method on `NotificationEmail` to change the `importance` context option to null after creation
+ * Add a new `fragment_uri()` helper to generate the URI of a fragment
+ * Add support of Bootstrap 5 for form theming
+ * Add a new `serialize` filter to serialize objects using the Serializer component
+
+5.2.0
+-----
+
+ * added the `impersonation_exit_url()` and `impersonation_exit_path()` functions. They return a URL that allows to switch back to the original user.
+ * added the `workflow_transition()` function to easily retrieve a specific transition object
+ * added support for translating `TranslatableInterface` objects
+ * added the `t()` function to easily create `TranslatableMessage` objects
+ * Added support for extracting messages from the `t()` function
+ * Added `field_*` Twig functions to access string values from Form fields
+ * changed the `importance` context option of `NotificationEmail` to allow `null`
+
+5.0.0
+-----
+
+ * removed `TwigEngine` class, use `\Twig\Environment` instead.
+ * removed `transChoice` filter and token
+ * `HttpFoundationExtension` requires a `UrlHelper` on instantiation
+ * removed support for implicit STDIN usage in the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit.
+ * added form theme for Foundation 6
+ * added support for Foundation 6 switches: add the `switch-input` class to the attributes of a `CheckboxType`
+
4.4.0
-----
* added a new `TwigErrorRenderer` for `html` format, integrated with the `ErrorHandler` component
* marked all classes extending twig as `@final`
- * deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the
+ * deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the
`DebugCommand::__construct()` method, swap the variables position.
* the `LintCommand` lints all the templates stored in all configured Twig paths if none argument is provided
* deprecated accepting STDIN implicitly when using the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit.
@@ -19,7 +54,7 @@ CHANGELOG
* added the `form_parent()` function that allows to reliably retrieve the parent form in Twig templates
* added the `workflow_transition_blockers()` function
- * deprecated the `$requestStack` and `$requestContext` arguments of the
+ * deprecated the `$requestStack` and `$requestContext` arguments of the
`HttpFoundationExtension`, pass a `Symfony\Component\HttpFoundation\UrlHelper`
instance as the only argument instead
diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php
index a6a1e9a2cebde..e78723be91d1f 100644
--- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php
+++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php
@@ -11,7 +11,10 @@
namespace Symfony\Bridge\Twig\Command;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputArgument;
@@ -30,23 +33,22 @@
*
* @author Jordi Boggiano
*/
+#[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')]
class DebugCommand extends Command
{
- protected static $defaultName = 'debug:twig';
-
private $twig;
- private $projectDir;
- private $bundlesMetadata;
- private $twigDefaultPath;
- private $rootDir;
- private $filesystemLoaders;
- private $fileLinkFormatter;
+ private ?string $projectDir;
+ private array $bundlesMetadata;
+ private ?string $twigDefaultPath;
/**
- * @param FileLinkFormatter|null $fileLinkFormatter
- * @param string|null $rootDir
+ * @var FilesystemLoader[]
*/
- public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, $fileLinkFormatter = null, $rootDir = null)
+ private array $filesystemLoaders;
+
+ private $fileLinkFormatter;
+
+ public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null)
{
parent::__construct();
@@ -54,16 +56,7 @@ public function __construct(Environment $twig, string $projectDir = null, array
$this->projectDir = $projectDir;
$this->bundlesMetadata = $bundlesMetadata;
$this->twigDefaultPath = $twigDefaultPath;
-
- if (\is_string($fileLinkFormatter) || $rootDir instanceof FileLinkFormatter) {
- @trigger_error(sprintf('Passing a string as "$fileLinkFormatter" 5th argument or an instance of FileLinkFormatter as "$rootDir" 6th argument of the "%s()" method is deprecated since Symfony 4.4, swap the variables position.', __METHOD__), \E_USER_DEPRECATED);
-
- $this->rootDir = $fileLinkFormatter;
- $this->fileLinkFormatter = $rootDir;
- } else {
- $this->fileLinkFormatter = $fileLinkFormatter;
- $this->rootDir = $rootDir;
- }
+ $this->fileLinkFormatter = $fileLinkFormatter;
}
protected function configure()
@@ -74,7 +67,6 @@ protected function configure()
new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'),
])
- ->setDescription('Show a list of twig functions, filters, globals and tests')
->setHelp(<<<'EOF'
The %command.name% command outputs a list of twig functions,
filters, globals and tests.
@@ -99,7 +91,7 @@ protected function configure()
;
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
@@ -123,6 +115,17 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 0;
}
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('name')) {
+ $suggestions->suggestValues(array_keys($this->getLoaderPaths()));
+ }
+
+ if ($input->mustSuggestOptionValuesFor('format')) {
+ $suggestions->suggestValues(['text', 'json']);
+ }
+ }
+
private function displayPathsText(SymfonyStyle $io, string $name)
{
$file = new \ArrayIterator($this->findTemplateFiles($name));
@@ -306,7 +309,7 @@ private function getLoaderPaths(string $name = null): array
return $loaderPaths;
}
- private function getMetadata(string $type, $entity)
+ private function getMetadata(string $type, mixed $entity)
{
if ('globals' === $type) {
return $entity;
@@ -364,7 +367,7 @@ private function getMetadata(string $type, $entity)
return null;
}
- private function getPrettyMetadata(string $type, $entity, bool $decorated): ?string
+ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated): ?string
{
if ('tests' === $type) {
return '';
@@ -405,22 +408,6 @@ private function findWrongBundleOverrides(): array
$alternatives = [];
$bundleNames = [];
- if ($this->rootDir && $this->projectDir) {
- $folders = glob($this->rootDir.'/Resources/*/views', \GLOB_ONLYDIR);
- $relativePath = ltrim(substr($this->rootDir.\DIRECTORY_SEPARATOR.'Resources/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
- $bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) {
- if (str_starts_with($absolutePath, $this->projectDir)) {
- $name = basename(\dirname($absolutePath));
- $path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR);
- $carry[$name] = $path;
-
- @trigger_error(sprintf('Loading Twig templates from the "%s" directory is deprecated since Symfony 4.2, use "%s" instead.', $absolutePath, $this->twigDefaultPath.'/bundles/'.$name), \E_USER_DEPRECATED);
- }
-
- return $carry;
- }, $bundleNames);
- }
-
if ($this->twigDefaultPath && $this->projectDir) {
$folders = glob($this->twigDefaultPath.'/bundles/*', \GLOB_ONLYDIR);
$relativePath = ltrim(substr($this->twigDefaultPath.'/bundles/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
@@ -586,7 +573,7 @@ private function isAbsolutePath(string $file): bool
*/
private function getFilesystemLoaders(): array
{
- if (null !== $this->filesystemLoaders) {
+ if (isset($this->filesystemLoaders)) {
return $this->filesystemLoaders;
}
$this->filesystemLoaders = [];
diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php
index 9dcbf4964e228..b7295c87840e2 100644
--- a/src/Symfony/Bridge/Twig/Command/LintCommand.php
+++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php
@@ -11,7 +11,11 @@
namespace Symfony\Bridge\Twig\Command;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\CI\GithubActionReporter;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
@@ -32,11 +36,11 @@
* @author Marc Weistroff
* @author Jérôme Tamarelle
*/
+#[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')]
class LintCommand extends Command
{
- protected static $defaultName = 'lint:twig';
-
private $twig;
+ private string $format;
public function __construct(Environment $twig)
{
@@ -48,8 +52,7 @@ public function __construct(Environment $twig)
protected function configure()
{
$this
- ->setDescription('Lint a template and outputs encountered errors')
- ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
+ ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors')
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->setHelp(<<<'EOF'
@@ -74,24 +77,18 @@ protected function configure()
;
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$filenames = $input->getArgument('filename');
$showDeprecations = $input->getOption('show-deprecations');
+ $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt');
if (['-'] === $filenames) {
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
}
if (!$filenames) {
- // @deprecated to be removed in 5.0
- if (0 === ftell(\STDIN)) {
- @trigger_error('Piping content from STDIN to the "lint:twig" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', \E_USER_DEPRECATED);
-
- return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
- }
-
$loader = $this->twig->getLoader();
if ($loader instanceof FilesystemLoader) {
$paths = [];
@@ -144,7 +141,7 @@ private function getFilesInfo(array $filenames): array
return $filesInfo;
}
- protected function findFiles($filename)
+ protected function findFiles(string $filename)
{
if (is_file($filename)) {
return [$filename];
@@ -175,26 +172,29 @@ private function validate(string $template, string $file): array
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files)
{
- switch ($input->getOption('format')) {
+ switch ($this->format) {
case 'txt':
return $this->displayTxt($output, $io, $files);
case 'json':
return $this->displayJson($output, $files);
+ case 'github':
+ return $this->displayTxt($output, $io, $files, true);
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
}
}
- private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo)
+ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
{
$errors = 0;
+ $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null;
foreach ($filesInfo as $info) {
if ($info['valid'] && $output->isVerbose()) {
$io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
} elseif (!$info['valid']) {
++$errors;
- $this->renderException($io, $info['template'], $info['exception'], $info['file']);
+ $this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter);
}
}
@@ -226,10 +226,14 @@ private function displayJson(OutputInterface $output, array $filesInfo)
return min($errors, 1);
}
- private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null)
+ private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null)
{
$line = $exception->getTemplateLine();
+ if ($githubReporter) {
+ $githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line);
+ }
+
if ($file) {
$output->text(sprintf(' ERROR in %s (line %s)', $file, $line));
} else {
@@ -272,4 +276,11 @@ private function getContext(string $template, int $line, int $context = 3)
return $result;
}
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestOptionValuesFor('format')) {
+ $suggestions->suggestValues(['txt', 'json', 'github']);
+ }
+ }
}
diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php
index fd6bc2d1e2663..ed27f6b89a365 100644
--- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php
+++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php
@@ -22,17 +22,15 @@
use Twig\Profiler\Profile;
/**
- * TwigDataCollector.
- *
* @author Fabien Potencier
*
- * @final since Symfony 4.4
+ * @final
*/
class TwigDataCollector extends DataCollector implements LateDataCollectorInterface
{
private $profile;
private $twig;
- private $computed;
+ private array $computed;
public function __construct(Profile $profile, Environment $twig = null)
{
@@ -42,10 +40,8 @@ public function __construct(Profile $profile, Environment $twig = null)
/**
* {@inheritdoc}
- *
- * @param \Throwable|null $exception
*/
- public function collect(Request $request, Response $response/*, \Throwable $exception = null*/)
+ public function collect(Request $request, Response $response, \Throwable $exception = null)
{
}
@@ -55,7 +51,7 @@ public function collect(Request $request, Response $response/*, \Throwable $exce
public function reset()
{
$this->profile->reset();
- $this->computed = null;
+ unset($this->computed);
$this->data = [];
}
@@ -144,18 +140,12 @@ public function getHtmlCallGraph()
public function getProfile()
{
- if (null === $this->profile) {
- $this->profile = unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]);
- }
-
- return $this->profile;
+ return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]);
}
private function getComputedData(string $index)
{
- if (null === $this->computed) {
- $this->computed = $this->computeData($this->getProfile());
- }
+ $this->computed ??= $this->computeData($this->getProfile());
return $this->computed[$index];
}
@@ -200,7 +190,7 @@ private function computeData(Profile $profile)
/**
* {@inheritdoc}
*/
- public function getName()
+ public function getName(): string
{
return 'twig';
}
diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php
index 1f5caee4ee5c7..50cc9cc91d5b3 100644
--- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php
+++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php
@@ -16,8 +16,6 @@
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Environment;
-use Twig\Error\LoaderError;
-use Twig\Loader\ExistsLoaderInterface;
/**
* Provides the ability to render custom Twig-based HTML error pages
@@ -29,20 +27,16 @@ class TwigErrorRenderer implements ErrorRendererInterface
{
private $twig;
private $fallbackErrorRenderer;
- private $debug;
+ private \Closure|bool $debug;
/**
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
*/
- public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, $debug = false)
+ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false)
{
- if (!\is_bool($debug) && !\is_callable($debug)) {
- throw new \TypeError(sprintf('Argument 3 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \is_object($debug) ? \get_class($debug) : \gettype($debug)));
- }
-
$this->twig = $twig;
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
- $this->debug = $debug;
+ $this->debug = !\is_callable($debug) || $debug instanceof \Closure ? $debug : \Closure::fromCallable($debug);
}
/**
@@ -58,7 +52,6 @@ public function render(\Throwable $exception): FlattenException
}
return $exception->setAsString($this->twig->render($template, [
- 'legacy' => false, // to be removed in 5.0
'exception' => $exception,
'status_code' => $exception->getStatusCode(),
'status_text' => $exception->getStatusText(),
@@ -79,39 +72,15 @@ public static function isDebug(RequestStack $requestStack, bool $debug): \Closur
private function findTemplate(int $statusCode): ?string
{
$template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode);
- if ($this->templateExists($template)) {
+ if ($this->twig->getLoader()->exists($template)) {
return $template;
}
$template = '@Twig/Exception/error.html.twig';
- if ($this->templateExists($template)) {
+ if ($this->twig->getLoader()->exists($template)) {
return $template;
}
return null;
}
-
- /**
- * To be removed in 5.0.
- *
- * Use instead:
- *
- * $this->twig->getLoader()->exists($template)
- */
- private function templateExists(string $template): bool
- {
- $loader = $this->twig->getLoader();
- if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) {
- return $loader->exists($template);
- }
-
- try {
- $loader->getSourceContext($template);
-
- return true;
- } catch (LoaderError $e) {
- }
-
- return false;
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php
index 62e7b91cb2db6..694821f7bf6b8 100644
--- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php
@@ -19,10 +19,8 @@
* Twig extension for the Symfony Asset component.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class AssetExtension extends AbstractExtension
+final class AssetExtension extends AbstractExtension
{
private $packages;
@@ -33,10 +31,8 @@ public function __construct(Packages $packages)
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('asset', [$this, 'getAssetUrl']),
@@ -49,37 +45,17 @@ public function getFunctions()
*
* If the package used to generate the path is an instance of
* UrlPackage, you will always get a URL and not a path.
- *
- * @param string $path A public path
- * @param string $packageName The name of the asset package to use
- *
- * @return string The public path of the asset
*/
- public function getAssetUrl($path, $packageName = null)
+ public function getAssetUrl(string $path, string $packageName = null): string
{
return $this->packages->getUrl($path, $packageName);
}
/**
* Returns the version of an asset.
- *
- * @param string $path A public path
- * @param string $packageName The name of the asset package to use
- *
- * @return string The asset version
*/
- public function getAssetVersion($path, $packageName = null)
+ public function getAssetVersion(string $path, string $packageName = null): string
{
return $this->packages->getVersion($path, $packageName);
}
-
- /**
- * Returns the name of the extension.
- *
- * @return string The extension name
- */
- public function getName()
- {
- return 'asset';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php
index 54b43caca2cb3..62573d9f961e6 100644
--- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php
@@ -19,21 +19,14 @@
* Twig extension relate to PHP code and used by the profiler and the default exception templates.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class CodeExtension extends AbstractExtension
+final class CodeExtension extends AbstractExtension
{
- private $fileLinkFormat;
- private $charset;
- private $projectDir;
+ private string|FileLinkFormatter|array|false $fileLinkFormat;
+ private string $charset;
+ private string $projectDir;
- /**
- * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
- * @param string $projectDir The project directory
- * @param string $charset The charset
- */
- public function __construct($fileLinkFormat, string $projectDir, string $charset)
+ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $projectDir, string $charset)
{
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->projectDir = str_replace('\\', '/', $projectDir).'/';
@@ -42,10 +35,8 @@ public function __construct($fileLinkFormat, string $projectDir, string $charset
/**
* {@inheritdoc}
- *
- * @return TwigFilter[]
*/
- public function getFilters()
+ public function getFilters(): array
{
return [
new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]),
@@ -61,7 +52,7 @@ public function getFilters()
];
}
- public function abbrClass($class)
+ public function abbrClass(string $class): string
{
$parts = explode('\\', $class);
$short = array_pop($parts);
@@ -69,7 +60,7 @@ public function abbrClass($class)
return sprintf('%s', $class, $short);
}
- public function abbrMethod($method)
+ public function abbrMethod(string $method): string
{
if (str_contains($method, '::')) {
[$class, $method] = explode('::', $method, 2);
@@ -85,12 +76,8 @@ public function abbrMethod($method)
/**
* Formats an array as a string.
- *
- * @param array $args The argument array
- *
- * @return string
*/
- public function formatArgs($args)
+ public function formatArgs(array $args): string
{
$result = [];
foreach ($args as $key => $item) {
@@ -118,26 +105,16 @@ public function formatArgs($args)
/**
* Formats an array as a string.
- *
- * @param array $args The argument array
- *
- * @return string
*/
- public function formatArgsAsText($args)
+ public function formatArgsAsText(array $args): string
{
return strip_tags($this->formatArgs($args));
}
/**
* Returns an excerpt of a code file around the given line number.
- *
- * @param string $file A file path
- * @param int $line The selected line number
- * @param int $srcContext The number of displayed lines around or -1 for the whole file
- *
- * @return string An HTML string
*/
- public function fileExcerpt($file, $line, $srcContext = 3)
+ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?string
{
if (is_file($file) && is_readable($file)) {
// highlight_file could throw warnings
@@ -157,7 +134,7 @@ public function fileExcerpt($file, $line, $srcContext = 3)
}
for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) {
- $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).'
';
+ $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).'
';
}
return ''.implode("\n", $lines).'
';
@@ -168,14 +145,8 @@ public function fileExcerpt($file, $line, $srcContext = 3)
/**
* Formats a file path.
- *
- * @param string $file An absolute file path
- * @param int $line The line number
- * @param string $text Use this text for the link rather than the file path
- *
- * @return string
*/
- public function formatFile($file, $line, $text = null)
+ public function formatFile(string $file, int $line, string $text = null): string
{
$file = trim($file);
@@ -198,15 +169,7 @@ public function formatFile($file, $line, $text = null)
return $text;
}
- /**
- * Returns the link for a given file/line pair.
- *
- * @param string $file An absolute file path
- * @param int $line The line number
- *
- * @return string|false A link or false
- */
- public function getFileLink($file, $line)
+ public function getFileLink(string $file, int $line): string|false
{
if ($fmt = $this->fileLinkFormat) {
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
@@ -226,7 +189,7 @@ public function getFileRelative(string $file): ?string
return null;
}
- public function formatFileFromText($text)
+ public function formatFileFromText(string $text): string
{
return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) {
return 'in '.$this->formatFile($match[2], $match[3]);
@@ -254,15 +217,7 @@ public function formatLogMessage(string $message, array $context): string
return htmlspecialchars($message, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset);
}
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'code';
- }
-
- protected static function fixCodeMarkup($line)
+ protected static function fixCodeMarkup(string $line): string
{
// ending tag from previous line
$opening = strpos($line, '
* @author Titouan Galopin
- *
- * @final since Symfony 4.4
*/
-class CsrfExtension extends AbstractExtension
+final class CsrfExtension extends AbstractExtension
{
/**
* {@inheritdoc}
diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php
index ea857c7ed583b..c3d5da6470c25 100644
--- a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php
+++ b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php
@@ -16,10 +16,8 @@
/**
* @author Christian Flothmann
* @author Titouan Galopin
- *
- * @final since Symfony 4.4
*/
-class CsrfRuntime
+final class CsrfRuntime
{
private $csrfTokenManager;
diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
index 8937c890e3a99..46ad8eaf679c2 100644
--- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
@@ -17,17 +17,14 @@
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\Template;
-use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFunction;
/**
* Provides integration of the dump() function with Twig.
*
* @author Nicolas Grekas
- *
- * @final since Symfony 4.4
*/
-class DumpExtension extends AbstractExtension
+final class DumpExtension extends AbstractExtension
{
private $cloner;
private $dumper;
@@ -39,9 +36,9 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null)
}
/**
- * @return TwigFunction[]
+ * {@inheritdoc}
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]),
@@ -49,19 +46,14 @@ public function getFunctions()
}
/**
- * @return TokenParserInterface[]
+ * {@inheritdoc}
*/
- public function getTokenParsers()
+ public function getTokenParsers(): array
{
return [new DumpTokenParser()];
}
- public function getName()
- {
- return 'dump';
- }
-
- public function dump(Environment $env, $context)
+ public function dump(Environment $env, array $context): ?string
{
if (!$env->isDebug()) {
return null;
diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php
index af7be97c4f9bd..8d2a35c99f682 100644
--- a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php
@@ -19,35 +19,21 @@
* ExpressionExtension gives a way to create Expressions from a template.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class ExpressionExtension extends AbstractExtension
+final class ExpressionExtension extends AbstractExtension
{
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('expression', [$this, 'createExpression']),
];
}
- public function createExpression($expression)
+ public function createExpression(string $expression): Expression
{
return new Expression($expression);
}
-
- /**
- * Returns the name of the extension.
- *
- * @return string The extension name
- */
- public function getName()
- {
- return 'expression';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
index 174a5cc3fe4bb..f7d82e042138d 100644
--- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
@@ -12,10 +12,12 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
+use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
+use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormView;
+use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension;
-use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
@@ -25,17 +27,20 @@
*
* @author Fabien Potencier
* @author Bernhard Schussek
- *
- * @final since Symfony 4.4
*/
-class FormExtension extends AbstractExtension
+final class FormExtension extends AbstractExtension
{
+ private $translator;
+
+ public function __construct(TranslatorInterface $translator = null)
+ {
+ $this->translator = $translator;
+ }
+
/**
* {@inheritdoc}
- *
- * @return TokenParserInterface[]
*/
- public function getTokenParsers()
+ public function getTokenParsers(): array
{
return [
// {% form_theme form "SomeBundle::widgets.twig" %}
@@ -45,10 +50,8 @@ public function getTokenParsers()
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
@@ -62,15 +65,19 @@ public function getFunctions()
new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']),
new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'),
+ new TwigFunction('field_name', [$this, 'getFieldName']),
+ new TwigFunction('field_value', [$this, 'getFieldValue']),
+ new TwigFunction('field_label', [$this, 'getFieldLabel']),
+ new TwigFunction('field_help', [$this, 'getFieldHelp']),
+ new TwigFunction('field_errors', [$this, 'getFieldErrors']),
+ new TwigFunction('field_choices', [$this, 'getFieldChoices']),
];
}
/**
* {@inheritdoc}
- *
- * @return TwigFilter[]
*/
- public function getFilters()
+ public function getFilters(): array
{
return [
new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']),
@@ -80,10 +87,8 @@ public function getFilters()
/**
* {@inheritdoc}
- *
- * @return TwigTest[]
*/
- public function getTests()
+ public function getTests(): array
{
return [
new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'),
@@ -91,12 +96,88 @@ public function getTests()
];
}
+ public function getFieldName(FormView $view): string
+ {
+ $view->setRendered();
+
+ return $view->vars['full_name'];
+ }
+
+ public function getFieldValue(FormView $view): string|array
+ {
+ return $view->vars['value'];
+ }
+
+ public function getFieldLabel(FormView $view): ?string
+ {
+ if (false === $label = $view->vars['label']) {
+ return null;
+ }
+
+ if (!$label && $labelFormat = $view->vars['label_format']) {
+ $label = str_replace(['%id%', '%name%'], [$view->vars['id'], $view->vars['name']], $labelFormat);
+ } elseif (!$label) {
+ $label = ucfirst(strtolower(trim(preg_replace(['/([A-Z])/', '/[_\s]+/'], ['_$1', ' '], $view->vars['name']))));
+ }
+
+ return $this->createFieldTranslation(
+ $label,
+ $view->vars['label_translation_parameters'] ?: [],
+ $view->vars['translation_domain']
+ );
+ }
+
+ public function getFieldHelp(FormView $view): ?string
+ {
+ return $this->createFieldTranslation(
+ $view->vars['help'],
+ $view->vars['help_translation_parameters'] ?: [],
+ $view->vars['translation_domain']
+ );
+ }
+
/**
- * {@inheritdoc}
+ * @return string[]
+ */
+ public function getFieldErrors(FormView $view): iterable
+ {
+ /** @var FormError $error */
+ foreach ($view->vars['errors'] as $error) {
+ yield $error->getMessage();
+ }
+ }
+
+ /**
+ * @return string[]|string[][]
*/
- public function getName()
+ public function getFieldChoices(FormView $view): iterable
{
- return 'form';
+ yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']);
+ }
+
+ private function createFieldChoicesList(iterable $choices, string|false|null $translationDomain): iterable
+ {
+ foreach ($choices as $choice) {
+ $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain);
+
+ if ($choice instanceof ChoiceGroupView) {
+ yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain);
+
+ continue;
+ }
+
+ /* @var ChoiceView $choice */
+ yield $translatableLabel => $choice->value;
+ }
+ }
+
+ private function createFieldTranslation(?string $value, array $parameters, string|false|null $domain): ?string
+ {
+ if (!$this->translator || !$value || false === $domain) {
+ return $value;
+ }
+
+ return $this->translator->trans($value, $parameters, $domain);
}
}
@@ -105,13 +186,9 @@ public function getName()
*
* This is a function and not callable due to performance reasons.
*
- * @param string|array $selectedValue The selected value to compare
- *
- * @return bool Whether the choice is selected
- *
* @see ChoiceView::isSelected()
*/
-function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool
+function twig_is_selected_choice(ChoiceView $choice, string|array $selectedValue): bool
{
if (\is_array($selectedValue)) {
return \in_array($choice->value, $selectedValue, true);
diff --git a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php
index f3c42482f9d1d..a9ee05c4d0093 100644
--- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php
@@ -12,9 +12,7 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\UrlHelper;
-use Symfony\Component\Routing\RequestContext;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
@@ -22,47 +20,20 @@
* Twig extension for the Symfony HttpFoundation component.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class HttpFoundationExtension extends AbstractExtension
+final class HttpFoundationExtension extends AbstractExtension
{
private $urlHelper;
- /**
- * @param UrlHelper $urlHelper
- */
- public function __construct($urlHelper)
+ public function __construct(UrlHelper $urlHelper)
{
- if ($urlHelper instanceof UrlHelper) {
- $this->urlHelper = $urlHelper;
-
- return;
- }
-
- if (!$urlHelper instanceof RequestStack) {
- throw new \TypeError(sprintf('The first argument must be an instance of "%s" or an instance of "%s".', UrlHelper::class, RequestStack::class));
- }
-
- @trigger_error(sprintf('Passing a "%s" instance as the first argument to the "%s" constructor is deprecated since Symfony 4.3, pass a "%s" instance instead.', RequestStack::class, __CLASS__, UrlHelper::class), \E_USER_DEPRECATED);
-
- $requestContext = null;
- if (2 === \func_num_args()) {
- $requestContext = func_get_arg(1);
- if (null !== $requestContext && !$requestContext instanceof RequestContext) {
- throw new \TypeError(sprintf('The second argument must be an instance of "%s".', RequestContext::class));
- }
- }
-
- $this->urlHelper = new UrlHelper($urlHelper, $requestContext);
+ $this->urlHelper = $urlHelper;
}
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']),
@@ -75,13 +46,9 @@ public function getFunctions()
*
* This method returns the path unchanged if no request is available.
*
- * @param string $path The path
- *
- * @return string The absolute URL
- *
* @see Request::getUriForPath()
*/
- public function generateAbsoluteUrl($path)
+ public function generateAbsoluteUrl(string $path): string
{
return $this->urlHelper->getAbsoluteUrl($path);
}
@@ -91,24 +58,10 @@ public function generateAbsoluteUrl($path)
*
* This method returns the path unchanged if no request is available.
*
- * @param string $path The path
- *
- * @return string The relative path
- *
* @see Request::getRelativeUriForPath()
*/
- public function generateRelativePath($path)
+ public function generateRelativePath(string $path): string
{
return $this->urlHelper->getRelativePath($path);
}
-
- /**
- * Returns the name of the extension.
- *
- * @return string The extension name
- */
- public function getName()
- {
- return 'request';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php
index 286bc420c66c5..1af9ddb23cf51 100644
--- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php
@@ -19,33 +19,24 @@
* Provides integration with the HttpKernel component.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class HttpKernelExtension extends AbstractExtension
+final class HttpKernelExtension extends AbstractExtension
{
/**
- * @return TwigFunction[]
+ * {@inheritdoc}
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]),
new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]),
+ new TwigFunction('fragment_uri', [HttpKernelRuntime::class, 'generateFragmentUri']),
new TwigFunction('controller', static::class.'::controller'),
];
}
- public static function controller($controller, $attributes = [], $query = [])
+ public static function controller(string $controller, array $attributes = [], array $query = []): ControllerReference
{
return new ControllerReference($controller, $attributes, $query);
}
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'http_kernel';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php
index edcf8a4dc0e6f..7c86d7dd40add 100644
--- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php
+++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php
@@ -13,34 +13,30 @@
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
+use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface;
/**
* Provides integration with the HttpKernel component.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class HttpKernelRuntime
+final class HttpKernelRuntime
{
private $handler;
+ private $fragmentUriGenerator;
- public function __construct(FragmentHandler $handler)
+ public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null)
{
$this->handler = $handler;
+ $this->fragmentUriGenerator = $fragmentUriGenerator;
}
/**
* Renders a fragment.
*
- * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
- * @param array $options An array of options
- *
- * @return string The fragment content
- *
* @see FragmentHandler::render()
*/
- public function renderFragment($uri, $options = [])
+ public function renderFragment(string|ControllerReference $uri, array $options = []): string
{
$strategy = $options['strategy'] ?? 'inline';
unset($options['strategy']);
@@ -51,16 +47,19 @@ public function renderFragment($uri, $options = [])
/**
* Renders a fragment.
*
- * @param string $strategy A strategy name
- * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
- * @param array $options An array of options
- *
- * @return string The fragment content
- *
* @see FragmentHandler::render()
*/
- public function renderFragmentStrategy($strategy, $uri, $options = [])
+ public function renderFragmentStrategy(string $strategy, string|ControllerReference $uri, array $options = []): string
{
return $this->handler->render($uri, $strategy, $options);
}
+
+ public function generateFragmentUri(ControllerReference $controller, bool $absolute = false, bool $strict = true, bool $sign = true): string
+ {
+ if (null === $this->fragmentUriGenerator) {
+ throw new \LogicException(sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__));
+ }
+
+ return $this->fragmentUriGenerator->generate($controller, null, $absolute, $strict, $sign);
+ }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php
index a6648dc072db1..071b9ff247f1d 100644
--- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php
@@ -19,10 +19,8 @@
* LogoutUrlHelper provides generator functions for the logout URL to Twig.
*
* @author Jeremy Mikola
- *
- * @final since Symfony 4.4
*/
-class LogoutUrlExtension extends AbstractExtension
+final class LogoutUrlExtension extends AbstractExtension
{
private $generator;
@@ -33,10 +31,8 @@ public function __construct(LogoutUrlGenerator $generator)
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('logout_url', [$this, 'getLogoutUrl']),
@@ -48,10 +44,8 @@ public function getFunctions()
* Generates the relative logout URL for the firewall.
*
* @param string|null $key The firewall key or null to use the current firewall key
- *
- * @return string The relative logout URL
*/
- public function getLogoutPath($key = null)
+ public function getLogoutPath(string $key = null): string
{
return $this->generator->getLogoutPath($key);
}
@@ -60,19 +54,9 @@ public function getLogoutPath($key = null)
* Generates the absolute logout URL for the firewall.
*
* @param string|null $key The firewall key or null to use the current firewall key
- *
- * @return string The absolute logout URL
*/
- public function getLogoutUrl($key = null)
+ public function getLogoutUrl(string $key = null): string
{
return $this->generator->getLogoutUrl($key);
}
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'logout_url';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
index a46f2cdbb8936..cba3ab8d46329 100644
--- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
@@ -12,18 +12,21 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Component\Stopwatch\StopwatchEvent;
use Twig\Extension\ProfilerExtension as BaseProfilerExtension;
use Twig\Profiler\Profile;
/**
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class ProfilerExtension extends BaseProfilerExtension
+final class ProfilerExtension extends BaseProfilerExtension
{
private $stopwatch;
- private $events;
+
+ /**
+ * @var \SplObjectStorage
+ */
+ private \SplObjectStorage $events;
public function __construct(Profile $profile, Stopwatch $stopwatch = null)
{
@@ -33,10 +36,7 @@ public function __construct(Profile $profile, Stopwatch $stopwatch = null)
$this->events = new \SplObjectStorage();
}
- /**
- * @return void
- */
- public function enter(Profile $profile)
+ public function enter(Profile $profile): void
{
if ($this->stopwatch && $profile->isTemplate()) {
$this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template');
@@ -45,10 +45,7 @@ public function enter(Profile $profile)
parent::enter($profile);
}
- /**
- * @return void
- */
- public function leave(Profile $profile)
+ public function leave(Profile $profile): void
{
parent::leave($profile);
@@ -57,12 +54,4 @@ public function leave(Profile $profile)
unset($this->events[$profile]);
}
}
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'native_profiler';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php
index 1ba528546d6d2..800c22f6d4c2c 100644
--- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php
@@ -22,10 +22,8 @@
* Provides integration of the Routing component with Twig.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class RoutingExtension extends AbstractExtension
+final class RoutingExtension extends AbstractExtension
{
private $generator;
@@ -36,10 +34,8 @@ public function __construct(UrlGeneratorInterface $generator)
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
@@ -47,26 +43,12 @@ public function getFunctions()
];
}
- /**
- * @param string $name
- * @param array $parameters
- * @param bool $relative
- *
- * @return string
- */
- public function getPath($name, $parameters = [], $relative = false)
+ public function getPath(string $name, array $parameters = [], bool $relative = false): string
{
return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
}
- /**
- * @param string $name
- * @param array $parameters
- * @param bool $schemeRelative
- *
- * @return string
- */
- public function getUrl($name, $parameters = [], $schemeRelative = false)
+ public function getUrl(string $name, array $parameters = [], bool $schemeRelative = false): string
{
return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL);
}
@@ -92,8 +74,6 @@ public function getUrl($name, $parameters = [], $schemeRelative = false)
* @param Node $argsNode The arguments of the path/url function
*
* @return array An array with the contexts the URL is safe
- *
- * @final
*/
public function isUrlGenerationSafe(Node $argsNode): array
{
@@ -110,12 +90,4 @@ public function isUrlGenerationSafe(Node $argsNode): array
return [];
}
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'routing';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php
index 4acd7bbf9cc72..aedeefdca9d52 100644
--- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php
@@ -14,6 +14,7 @@
use Symfony\Component\Security\Acl\Voter\FieldVote;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
+use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
@@ -21,19 +22,19 @@
* SecurityExtension exposes security context features.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class SecurityExtension extends AbstractExtension
+final class SecurityExtension extends AbstractExtension
{
private $securityChecker;
+ private $impersonateUrlGenerator;
- public function __construct(AuthorizationCheckerInterface $securityChecker = null)
+ public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null)
{
$this->securityChecker = $securityChecker;
+ $this->impersonateUrlGenerator = $impersonateUrlGenerator;
}
- public function isGranted($role, $object = null, $field = null)
+ public function isGranted(mixed $role, mixed $object = null, string $field = null): bool
{
if (null === $this->securityChecker) {
return false;
@@ -50,23 +51,33 @@ public function isGranted($role, $object = null, $field = null)
}
}
- /**
- * {@inheritdoc}
- *
- * @return TwigFunction[]
- */
- public function getFunctions()
+ public function getImpersonateExitUrl(string $exitTo = null): string
{
- return [
- new TwigFunction('is_granted', [$this, 'isGranted']),
- ];
+ if (null === $this->impersonateUrlGenerator) {
+ return '';
+ }
+
+ return $this->impersonateUrlGenerator->generateExitUrl($exitTo);
+ }
+
+ public function getImpersonateExitPath(string $exitTo = null): string
+ {
+ if (null === $this->impersonateUrlGenerator) {
+ return '';
+ }
+
+ return $this->impersonateUrlGenerator->generateExitPath($exitTo);
}
/**
* {@inheritdoc}
*/
- public function getName()
+ public function getFunctions(): array
{
- return 'security';
+ return [
+ new TwigFunction('is_granted', [$this, 'isGranted']),
+ new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']),
+ new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']),
+ ];
}
}
diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php b/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php
new file mode 100644
index 0000000000000..f38571efaaac8
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\Twig\Extension;
+
+use Twig\Extension\AbstractExtension;
+use Twig\TwigFilter;
+
+/**
+ * @author Jesse Rushlow
+ */
+final class SerializerExtension extends AbstractExtension
+{
+ public function getFilters(): array
+ {
+ return [
+ new TwigFilter('serialize', [SerializerRuntime::class, 'serialize']),
+ ];
+ }
+}
diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php
new file mode 100644
index 0000000000000..dbffa31c2741b
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\Twig\Extension;
+
+use Symfony\Component\Serializer\SerializerInterface;
+use Twig\Extension\RuntimeExtensionInterface;
+
+/**
+ * @author Jesse Rushlow
+ */
+final class SerializerRuntime implements RuntimeExtensionInterface
+{
+ private $serializer;
+
+ public function __construct(SerializerInterface $serializer)
+ {
+ $this->serializer = $serializer;
+ }
+
+ public function serialize(mixed $data, string $format = 'json', array $context = []): string
+ {
+ return $this->serializer->serialize($data, $format, $context);
+ }
+}
diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php
index f4b9a24ced5cd..635216f23203b 100644
--- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php
@@ -20,13 +20,11 @@
* Twig extension for the stopwatch helper.
*
* @author Wouter J
- *
- * @final since Symfony 4.4
*/
-class StopwatchExtension extends AbstractExtension
+final class StopwatchExtension extends AbstractExtension
{
private $stopwatch;
- private $enabled;
+ private bool $enabled;
public function __construct(Stopwatch $stopwatch = null, bool $enabled = true)
{
@@ -34,7 +32,7 @@ public function __construct(Stopwatch $stopwatch = null, bool $enabled = true)
$this->enabled = $enabled;
}
- public function getStopwatch()
+ public function getStopwatch(): Stopwatch
{
return $this->stopwatch;
}
@@ -42,7 +40,7 @@ public function getStopwatch()
/**
* @return TokenParserInterface[]
*/
- public function getTokenParsers()
+ public function getTokenParsers(): array
{
return [
/*
@@ -53,9 +51,4 @@ public function getTokenParsers()
new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled),
];
}
-
- public function getName()
- {
- return 'stopwatch';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
index 96df106307da7..fa5c66fd43c63 100644
--- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
@@ -13,16 +13,15 @@
use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor;
use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
-use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser;
use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser;
use Symfony\Bridge\Twig\TokenParser\TransTokenParser;
-use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Component\Translation\TranslatableMessage;
+use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait;
use Twig\Extension\AbstractExtension;
-use Twig\NodeVisitor\NodeVisitorInterface;
-use Twig\TokenParser\AbstractTokenParser;
use Twig\TwigFilter;
+use Twig\TwigFunction;
// Help opcache.preload discover always-needed symbols
class_exists(TranslatorInterface::class);
@@ -32,30 +31,19 @@ class_exists(TranslatorTrait::class);
* Provides integration of the Translation component with Twig.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.2
*/
-class TranslationExtension extends AbstractExtension
+final class TranslationExtension extends AbstractExtension
{
private $translator;
private $translationNodeVisitor;
- /**
- * @param TranslatorInterface|null $translator
- */
- public function __construct($translator = null, NodeVisitorInterface $translationNodeVisitor = null)
+ public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null)
{
- if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
- throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
- }
$this->translator = $translator;
$this->translationNodeVisitor = $translationNodeVisitor;
}
- /**
- * @return TranslatorInterface|null
- */
- public function getTranslator()
+ public function getTranslator(): TranslatorInterface
{
if (null === $this->translator) {
if (!interface_exists(TranslatorInterface::class)) {
@@ -72,33 +60,33 @@ public function getTranslator()
/**
* {@inheritdoc}
- *
- * @return TwigFilter[]
*/
- public function getFilters()
+ public function getFunctions(): array
+ {
+ return [
+ new TwigFunction('t', [$this, 'createTranslatable']),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFilters(): array
{
return [
new TwigFilter('trans', [$this, 'trans']),
- new TwigFilter('transchoice', [$this, 'transchoice'], ['deprecated' => '4.2', 'alternative' => 'trans" with parameter "%count%']),
];
}
/**
- * Returns the token parser instance to add to the existing list.
- *
- * @return AbstractTokenParser[]
+ * {@inheritdoc}
*/
- public function getTokenParsers()
+ public function getTokenParsers(): array
{
return [
// {% trans %}Symfony is great!{% endtrans %}
new TransTokenParser(),
- // {% transchoice count %}
- // {0} There is no apples|{1} There is one apple|]1,Inf] There is {{ count }} apples
- // {% endtranschoice %}
- new TransChoiceTokenParser(),
-
// {% trans_default_domain "foobar" %}
new TransDefaultDomainTokenParser(),
];
@@ -106,21 +94,38 @@ public function getTokenParsers()
/**
* {@inheritdoc}
- *
- * @return NodeVisitorInterface[]
*/
- public function getNodeVisitors()
+ public function getNodeVisitors(): array
{
return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()];
}
- public function getTranslationNodeVisitor()
+ public function getTranslationNodeVisitor(): TranslationNodeVisitor
{
return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor();
}
- public function trans($message, array $arguments = [], $domain = null, $locale = null, $count = null)
+ /**
+ * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface
+ */
+ public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], string $domain = null, string $locale = null, int $count = null): string
{
+ if ($message instanceof TranslatableInterface) {
+ if ([] !== $arguments && !\is_string($arguments)) {
+ throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments)));
+ }
+
+ return $message->trans($this->getTranslator(), $locale ?? (\is_string($arguments) ? $arguments : null));
+ }
+
+ if (!\is_array($arguments)) {
+ throw new \TypeError(sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments)));
+ }
+
+ if ('' === $message = (string) $message) {
+ return '';
+ }
+
if (null !== $count) {
$arguments['%count%'] = $count;
}
@@ -128,25 +133,12 @@ public function trans($message, array $arguments = [], $domain = null, $locale =
return $this->getTranslator()->trans($message, $arguments, $domain, $locale);
}
- /**
- * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
- */
- public function transchoice($message, $count, array $arguments = [], $domain = null, $locale = null)
+ public function createTranslatable(string $message, array $parameters = [], string $domain = null): TranslatableMessage
{
- $translator = $this->getTranslator();
-
- if ($translator instanceof TranslatorInterface) {
- return $translator->trans($message, array_merge(['%count%' => $count], $arguments), $domain, $locale);
+ if (!class_exists(TranslatableMessage::class)) {
+ throw new \LogicException(sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__));
}
- return $translator->transChoice($message, $count, array_merge(['%count%' => $count], $arguments), $domain, $locale);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'translator';
+ return new TranslatableMessage($message, $parameters, $domain);
}
}
diff --git a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
index c2c6f8ba8fcf6..652a75762c63b 100644
--- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
@@ -21,10 +21,8 @@
* Twig extension for the Symfony WebLink component.
*
* @author Kévin Dunglas
- *
- * @final since Symfony 4.4
*/
-class WebLinkExtension extends AbstractExtension
+final class WebLinkExtension extends AbstractExtension
{
private $requestStack;
@@ -35,10 +33,8 @@ public function __construct(RequestStack $requestStack)
/**
* {@inheritdoc}
- *
- * @return TwigFunction[]
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('link', [$this, 'link']),
@@ -53,15 +49,14 @@ public function getFunctions()
/**
* Adds a "Link" HTTP header.
*
- * @param string $uri The relation URI
* @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch")
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The relation URI
*/
- public function link($uri, $rel, array $attributes = [])
+ public function link(string $uri, string $rel, array $attributes = []): string
{
- if (!$request = $this->requestStack->getMasterRequest()) {
+ if (!$request = $this->requestStack->getMainRequest()) {
return $uri;
}
@@ -79,12 +74,11 @@ public function link($uri, $rel, array $attributes = [])
/**
* Preloads a resource.
*
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']")
+ * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']")
*
* @return string The path of the asset
*/
- public function preload($uri, array $attributes = [])
+ public function preload(string $uri, array $attributes = []): string
{
return $this->link($uri, 'preload', $attributes);
}
@@ -92,12 +86,11 @@ public function preload($uri, array $attributes = [])
/**
* Resolves a resource origin as early as possible.
*
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
+ * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
- public function dnsPrefetch($uri, array $attributes = [])
+ public function dnsPrefetch(string $uri, array $attributes = []): string
{
return $this->link($uri, 'dns-prefetch', $attributes);
}
@@ -105,12 +98,11 @@ public function dnsPrefetch($uri, array $attributes = [])
/**
* Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
*
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
+ * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
- public function preconnect($uri, array $attributes = [])
+ public function preconnect(string $uri, array $attributes = []): string
{
return $this->link($uri, 'preconnect', $attributes);
}
@@ -118,12 +110,11 @@ public function preconnect($uri, array $attributes = [])
/**
* Indicates to the client that it should prefetch this resource.
*
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
+ * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
- public function prefetch($uri, array $attributes = [])
+ public function prefetch(string $uri, array $attributes = []): string
{
return $this->link($uri, 'prefetch', $attributes);
}
@@ -131,12 +122,11 @@ public function prefetch($uri, array $attributes = [])
/**
* Indicates to the client that it should prerender this resource .
*
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
+ * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
- public function prerender($uri, array $attributes = [])
+ public function prerender(string $uri, array $attributes = []): string
{
return $this->link($uri, 'prerender', $attributes);
}
diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
index b5f3badea88fd..3ee1ac3d6b6b2 100644
--- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
@@ -21,10 +21,9 @@
* WorkflowExtension.
*
* @author Grégoire Pineau
- *
- * @final since Symfony 4.4
+ * @author Carlos Pereira De Amorim
*/
-class WorkflowExtension extends AbstractExtension
+final class WorkflowExtension extends AbstractExtension
{
private $workflowRegistry;
@@ -34,13 +33,14 @@ public function __construct(Registry $workflowRegistry)
}
/**
- * @return TwigFunction[]
+ * {@inheritdoc}
*/
- public function getFunctions()
+ public function getFunctions(): array
{
return [
new TwigFunction('workflow_can', [$this, 'canTransition']),
new TwigFunction('workflow_transitions', [$this, 'getEnabledTransitions']),
+ new TwigFunction('workflow_transition', [$this, 'getEnabledTransition']),
new TwigFunction('workflow_has_marked_place', [$this, 'hasMarkedPlace']),
new TwigFunction('workflow_marked_places', [$this, 'getMarkedPlaces']),
new TwigFunction('workflow_metadata', [$this, 'getMetadata']),
@@ -50,14 +50,8 @@ public function getFunctions()
/**
* Returns true if the transition is enabled.
- *
- * @param object $subject A subject
- * @param string $transitionName A transition
- * @param string $name A workflow name
- *
- * @return bool true if the transition is enabled
*/
- public function canTransition($subject, $transitionName, $name = null)
+ public function canTransition(object $subject, string $transitionName, string $name = null): bool
{
return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName);
}
@@ -65,26 +59,22 @@ public function canTransition($subject, $transitionName, $name = null)
/**
* Returns all enabled transitions.
*
- * @param object $subject A subject
- * @param string $name A workflow name
- *
- * @return Transition[] All enabled transitions
+ * @return Transition[]
*/
- public function getEnabledTransitions($subject, $name = null)
+ public function getEnabledTransitions(object $subject, string $name = null): array
{
return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject);
}
+ public function getEnabledTransition(object $subject, string $transition, string $name = null): ?Transition
+ {
+ return $this->workflowRegistry->get($subject, $name)->getEnabledTransition($subject, $transition);
+ }
+
/**
* Returns true if the place is marked.
- *
- * @param object $subject A subject
- * @param string $placeName A place name
- * @param string $name A workflow name
- *
- * @return bool true if the transition is enabled
*/
- public function hasMarkedPlace($subject, $placeName, $name = null)
+ public function hasMarkedPlace(object $subject, string $placeName, string $name = null): bool
{
return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName);
}
@@ -92,13 +82,9 @@ public function hasMarkedPlace($subject, $placeName, $name = null)
/**
* Returns marked places.
*
- * @param object $subject A subject
- * @param bool $placesNameOnly If true, returns only places name. If false returns the raw representation
- * @param string $name A workflow name
- *
* @return string[]|int[]
*/
- public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null)
+ public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, string $name = null): array
{
$places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces();
@@ -112,12 +98,11 @@ public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null)
/**
* Returns the metadata for a specific subject.
*
- * @param object $subject A subject
* @param string|Transition|null $metadataSubject Use null to get workflow metadata
* Use a string (the place name) to get place metadata
* Use a Transition instance to get transition metadata
*/
- public function getMetadata($subject, string $key, $metadataSubject = null, string $name = null)
+ public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null)
{
return $this
->workflowRegistry
@@ -127,15 +112,10 @@ public function getMetadata($subject, string $key, $metadataSubject = null, stri
;
}
- public function buildTransitionBlockerList($subject, string $transitionName, string $name = null): TransitionBlockerList
+ public function buildTransitionBlockerList(object $subject, string $transitionName, string $name = null): TransitionBlockerList
{
$workflow = $this->workflowRegistry->get($subject, $name);
return $workflow->buildTransitionBlockerList($subject, $transitionName);
}
-
- public function getName()
- {
- return 'workflow';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php
index 02d19d8ae3d3b..919834e24a5d6 100644
--- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php
@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\Yaml\Dumper as YamlDumper;
-use Symfony\Component\Yaml\Yaml;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
@@ -20,17 +19,13 @@
* Provides integration of the Yaml component with Twig.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class YamlExtension extends AbstractExtension
+final class YamlExtension extends AbstractExtension
{
/**
* {@inheritdoc}
- *
- * @return TwigFilter[]
*/
- public function getFilters()
+ public function getFilters(): array
{
return [
new TwigFilter('yaml_encode', [$this, 'encode']),
@@ -38,7 +33,7 @@ public function getFilters()
];
}
- public function encode($input, $inline = 0, $dumpObjects = 0)
+ public function encode(mixed $input, int $inline = 0, int $dumpObjects = 0): string
{
static $dumper;
@@ -53,7 +48,7 @@ public function encode($input, $inline = 0, $dumpObjects = 0)
return $dumper->dump($input, $inline, 0, false, $dumpObjects);
}
- public function dump($value, $inline = 0, $dumpObjects = false)
+ public function dump(mixed $value, int $inline = 0, int $dumpObjects = 0): string
{
if (\is_resource($value)) {
return '%Resource%';
@@ -65,12 +60,4 @@ public function dump($value, $inline = 0, $dumpObjects = false)
return $this->encode($value, $inline, $dumpObjects);
}
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'yaml';
- }
}
diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php
index 1e97ce3371d1d..693ee81239924 100644
--- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php
+++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php
@@ -21,14 +21,7 @@
*/
class TwigRendererEngine extends AbstractRendererEngine
{
- /**
- * @var Environment
- */
private $environment;
-
- /**
- * @var Template
- */
private $template;
public function __construct(array $defaultThemes, Environment $environment)
@@ -40,7 +33,7 @@ public function __construct(array $defaultThemes, Environment $environment)
/**
* {@inheritdoc}
*/
- public function renderBlock(FormView $view, $resource, $blockName, array $variables = [])
+ public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string
{
$cacheKey = $view->vars[self::CACHE_KEY_VAR];
@@ -69,14 +62,8 @@ public function renderBlock(FormView $view, $resource, $blockName, array $variab
* case that the function "block()" is used in the Twig template.
*
* @see getResourceForBlock()
- *
- * @param string $cacheKey The cache key of the form view
- * @param FormView $view The form view for finding the applying themes
- * @param string $blockName The name of the block to load
- *
- * @return bool True if the resource could be loaded, false otherwise
*/
- protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName)
+ protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool
{
// The caller guarantees that $this->resources[$cacheKey][$block] is
// not set, but it doesn't have to check whether $this->resources[$cacheKey]
@@ -143,27 +130,23 @@ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockNam
/**
* Loads the resources for all blocks in a theme.
*
- * @param string $cacheKey The cache key for storing the resource
- * @param mixed $theme The theme to load the block from. This parameter
- * is passed by reference, because it might be necessary
- * to initialize the theme first. Any changes made to
- * this variable will be kept and be available upon
- * further calls to this method using the same theme.
+ * @param mixed $theme The theme to load the block from. This parameter
+ * is passed by reference, because it might be necessary
+ * to initialize the theme first. Any changes made to
+ * this variable will be kept and be available upon
+ * further calls to this method using the same theme.
*/
- protected function loadResourcesFromTheme($cacheKey, &$theme)
+ protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme)
{
if (!$theme instanceof Template) {
- /* @var Template $theme */
$theme = $this->environment->load($theme)->unwrap();
}
- if (null === $this->template) {
- // Store the first Template instance that we find so that
- // we can call displayBlock() later on. It doesn't matter *which*
- // template we use for that, since we pass the used blocks manually
- // anyway.
- $this->template = $theme;
- }
+ // Store the first Template instance that we find so that
+ // we can call displayBlock() later on. It doesn't matter *which*
+ // template we use for that, since we pass the used blocks manually
+ // anyway.
+ $this->template ??= $theme;
// Use a separate variable for the inheritance traversal, because
// theme is a reference and we don't want to change it.
diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
index 166b3c195ff17..e50efa06f6efe 100644
--- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
+++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
@@ -23,7 +23,7 @@
final class BodyRenderer implements BodyRendererInterface
{
private $twig;
- private $context;
+ private array $context;
private $converter;
public function __construct(Environment $twig, array $context = [])
@@ -55,7 +55,7 @@ public function render(Message $message): void
}
if (isset($messageContext['email'])) {
- throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', \get_class($message)));
+ throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message)));
}
$vars = array_merge($this->context, $messageContext, [
@@ -96,7 +96,7 @@ private function getFingerPrint(TemplatedEmail $message): string
private function convertHtmlToText(string $html): string
{
- if (null !== $this->converter) {
+ if (isset($this->converter)) {
return $this->converter->convert($html);
}
diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php
index 1a58aa5e5e5bc..8e71df1b0e206 100644
--- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php
+++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php
@@ -28,8 +28,8 @@ class NotificationEmail extends TemplatedEmail
public const IMPORTANCE_MEDIUM = 'medium';
public const IMPORTANCE_LOW = 'low';
- private $theme = 'default';
- private $context = [
+ private string $theme = 'default';
+ private array $context = [
'importance' => self::IMPORTANCE_LOW,
'content' => '',
'exception' => false,
@@ -37,6 +37,7 @@ class NotificationEmail extends TemplatedEmail
'action_url' => null,
'markdown' => false,
'raw' => false,
+ 'footer_text' => 'Notification e-mail sent by Symfony',
];
public function __construct(Headers $headers = null, AbstractPart $body = null)
@@ -57,10 +58,32 @@ public function __construct(Headers $headers = null, AbstractPart $body = null)
parent::__construct($headers, $body);
}
+ /**
+ * Creates a NotificationEmail instance that is appropriate to send to normal (non-admin) users.
+ */
+ public static function asPublicEmail(Headers $headers = null, AbstractPart $body = null): self
+ {
+ $email = new static($headers, $body);
+ $email->markAsPublic();
+
+ return $email;
+ }
+
/**
* @return $this
*/
- public function markdown(string $content)
+ public function markAsPublic(): static
+ {
+ $this->context['importance'] = null;
+ $this->context['footer_text'] = null;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function markdown(string $content): static
{
if (!class_exists(MarkdownExtension::class)) {
throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__));
@@ -74,7 +97,7 @@ public function markdown(string $content)
/**
* @return $this
*/
- public function content(string $content, bool $raw = false)
+ public function content(string $content, bool $raw = false): static
{
$this->context['content'] = $content;
$this->context['raw'] = $raw;
@@ -85,7 +108,7 @@ public function content(string $content, bool $raw = false)
/**
* @return $this
*/
- public function action(string $text, string $url)
+ public function action(string $text, string $url): static
{
$this->context['action_text'] = $text;
$this->context['action_url'] = $url;
@@ -96,7 +119,7 @@ public function action(string $text, string $url)
/**
* @return $this
*/
- public function importance(string $importance)
+ public function importance(string $importance): static
{
$this->context['importance'] = $importance;
@@ -104,16 +127,10 @@ public function importance(string $importance)
}
/**
- * @param \Throwable|FlattenException $exception
- *
* @return $this
*/
- public function exception($exception)
+ public function exception(\Throwable|FlattenException $exception): static
{
- if (!$exception instanceof \Throwable && !$exception instanceof FlattenException) {
- throw new \LogicException(sprintf('"%s" accepts "%s" or "%s" instances.', __METHOD__, \Throwable::class, FlattenException::class));
- }
-
$exceptionAsString = $this->getExceptionAsString($exception);
$this->context['exception'] = true;
@@ -130,7 +147,7 @@ public function exception($exception)
/**
* @return $this
*/
- public function theme(string $theme)
+ public function theme(string $theme): static
{
$this->theme = $theme;
@@ -166,7 +183,9 @@ public function getPreparedHeaders(): Headers
$importance = $this->context['importance'] ?? self::IMPORTANCE_LOW;
$this->priority($this->determinePriority($importance));
- $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
+ if ($this->context['importance']) {
+ $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
+ }
return $headers;
}
@@ -186,7 +205,7 @@ private function determinePriority(string $importance): int
}
}
- private function getExceptionAsString($exception): string
+ private function getExceptionAsString(\Throwable|FlattenException $exception): string
{
if (class_exists(FlattenException::class)) {
$exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception);
diff --git a/src/Symfony/Bridge/Twig/Mime/TemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/TemplatedEmail.php
index 6dd9202de8fc7..083b007726310 100644
--- a/src/Symfony/Bridge/Twig/Mime/TemplatedEmail.php
+++ b/src/Symfony/Bridge/Twig/Mime/TemplatedEmail.php
@@ -18,14 +18,14 @@
*/
class TemplatedEmail extends Email
{
- private $htmlTemplate;
- private $textTemplate;
- private $context = [];
+ private ?string $htmlTemplate = null;
+ private ?string $textTemplate = null;
+ private array $context = [];
/**
* @return $this
*/
- public function textTemplate(?string $template)
+ public function textTemplate(?string $template): static
{
$this->textTemplate = $template;
@@ -35,7 +35,7 @@ public function textTemplate(?string $template)
/**
* @return $this
*/
- public function htmlTemplate(?string $template)
+ public function htmlTemplate(?string $template): static
{
$this->htmlTemplate = $template;
@@ -55,7 +55,7 @@ public function getHtmlTemplate(): ?string
/**
* @return $this
*/
- public function context(array $context)
+ public function context(array $context): static
{
$this->context = $context;
diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php
index f1726914b490b..b567667883ef3 100644
--- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php
+++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php
@@ -60,7 +60,7 @@ public function attach(string $file, string $name = null, string $contentType =
/**
* @return $this
*/
- public function setSubject(string $subject): self
+ public function setSubject(string $subject): static
{
$this->message->subject($subject);
@@ -75,7 +75,7 @@ public function getSubject(): ?string
/**
* @return $this
*/
- public function setReturnPath(string $address): self
+ public function setReturnPath(string $address): static
{
$this->message->returnPath($address);
@@ -90,7 +90,7 @@ public function getReturnPath(): string
/**
* @return $this
*/
- public function addFrom(string $address, string $name = ''): self
+ public function addFrom(string $address, string $name = ''): static
{
$this->message->addFrom(new Address($address, $name));
@@ -108,7 +108,7 @@ public function getFrom(): array
/**
* @return $this
*/
- public function addReplyTo(string $address): self
+ public function addReplyTo(string $address): static
{
$this->message->addReplyTo($address);
@@ -126,7 +126,7 @@ public function getReplyTo(): array
/**
* @return $this
*/
- public function addTo(string $address, string $name = ''): self
+ public function addTo(string $address, string $name = ''): static
{
$this->message->addTo(new Address($address, $name));
@@ -144,7 +144,7 @@ public function getTo(): array
/**
* @return $this
*/
- public function addCc(string $address, string $name = ''): self
+ public function addCc(string $address, string $name = ''): static
{
$this->message->addCc(new Address($address, $name));
@@ -162,7 +162,7 @@ public function getCc(): array
/**
* @return $this
*/
- public function addBcc(string $address, string $name = ''): self
+ public function addBcc(string $address, string $name = ''): static
{
$this->message->addBcc(new Address($address, $name));
@@ -180,7 +180,7 @@ public function getBcc(): array
/**
* @return $this
*/
- public function setPriority(int $priority): self
+ public function setPriority(int $priority): static
{
$this->message->priority($priority);
diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php
index d82d9ade1feaf..8ce2bd8c4fa51 100644
--- a/src/Symfony/Bridge/Twig/Node/DumpNode.php
+++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php
@@ -16,14 +16,12 @@
/**
* @author Julien Galenski
- *
- * @final since Symfony 4.4
*/
-class DumpNode extends Node
+final class DumpNode extends Node
{
- private $varPrefix;
+ private string $varPrefix;
- public function __construct($varPrefix, Node $values = null, int $lineno, string $tag = null)
+ public function __construct(string $varPrefix, ?Node $values, int $lineno, string $tag = null)
{
$nodes = [];
if (null !== $values) {
@@ -34,10 +32,7 @@ public function __construct($varPrefix, Node $values = null, int $lineno, string
$this->varPrefix = $varPrefix;
}
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
$compiler
->write("if (\$this->env->isDebug()) {\n")
diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php
index b17243060f302..e37311267bb17 100644
--- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php
+++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php
@@ -17,20 +17,15 @@
/**
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class FormThemeNode extends Node
+final class FormThemeNode extends Node
{
public function __construct(Node $form, Node $resources, int $lineno, string $tag = null, bool $only = false)
{
parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag);
}
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
diff --git a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php
index 29402a8024fae..4d4cf61365772 100644
--- a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php
+++ b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php
@@ -21,15 +21,10 @@
* is "foo", the block "foo" will be rendered.
*
* @author Bernhard Schussek
- *
- * @final since Symfony 4.4
*/
-class RenderBlockNode extends FunctionExpression
+final class RenderBlockNode extends FunctionExpression
{
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
$arguments = iterator_to_array($this->getNode('arguments'));
diff --git a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php
index bf22c329d6a13..9967639d16636 100644
--- a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php
+++ b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php
@@ -18,15 +18,10 @@
/**
* @author Bernhard Schussek
- *
- * @final since Symfony 4.4
*/
-class SearchAndRenderBlockNode extends FunctionExpression
+final class SearchAndRenderBlockNode extends FunctionExpression
{
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
$compiler->raw('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(');
diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php
index b4dd8a9b37b4f..cfa4d8a197f9b 100644
--- a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php
+++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php
@@ -19,20 +19,15 @@
* Represents a stopwatch node.
*
* @author Wouter J
- *
- * @final since Symfony 4.4
*/
-class StopwatchNode extends Node
+final class StopwatchNode extends Node
{
public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, string $tag = null)
{
parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag);
}
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
diff --git a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php
index 49ceac1404c5e..df29f0a19931f 100644
--- a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php
+++ b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php
@@ -17,20 +17,15 @@
/**
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class TransDefaultDomainNode extends Node
+final class TransDefaultDomainNode extends Node
{
public function __construct(AbstractExpression $expr, int $lineno = 0, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
// noop as this node is just a marker for TranslationDefaultDomainNodeVisitor
}
diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php
index bc87d75bd7db2..8a126ba569172 100644
--- a/src/Symfony/Bridge/Twig/Node/TransNode.php
+++ b/src/Symfony/Bridge/Twig/Node/TransNode.php
@@ -19,15 +19,10 @@
use Twig\Node\Node;
use Twig\Node\TextNode;
-// BC/FC with namespaced Twig
-class_exists(ArrayExpression::class);
-
/**
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class TransNode extends Node
+final class TransNode extends Node
{
public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, int $lineno = 0, string $tag = null)
{
@@ -48,10 +43,7 @@ public function __construct(Node $body, Node $domain = null, AbstractExpression
parent::__construct($nodes, [], $lineno, $tag);
}
- /**
- * @return void
- */
- public function compile(Compiler $compiler)
+ public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
@@ -108,7 +100,7 @@ public function compile(Compiler $compiler)
$compiler->raw(");\n");
}
- protected function compileString(Node $body, ArrayExpression $vars, $ignoreStrictCheck = false)
+ private function compileString(Node $body, ArrayExpression $vars, bool $ignoreStrictCheck = false): array
{
if ($body instanceof ConstantExpression) {
$msg = $body->getAttribute('value');
diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php
index 642623f2a693c..efa354d03feac 100644
--- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php
+++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php
@@ -16,9 +16,9 @@
*/
class Scope
{
- private $parent;
- private $data = [];
- private $left = false;
+ private ?self $parent;
+ private array $data = [];
+ private bool $left = false;
public function __construct(self $parent = null)
{
@@ -27,20 +27,16 @@ public function __construct(self $parent = null)
/**
* Opens a new child scope.
- *
- * @return self
*/
- public function enter()
+ public function enter(): self
{
return new self($this);
}
/**
* Closes current scope and returns parent one.
- *
- * @return self|null
*/
- public function leave()
+ public function leave(): ?self
{
$this->left = true;
@@ -50,14 +46,11 @@ public function leave()
/**
* Stores data into current scope.
*
- * @param string $key
- * @param mixed $value
- *
* @return $this
*
* @throws \LogicException
*/
- public function set($key, $value)
+ public function set(string $key, mixed $value): static
{
if ($this->left) {
throw new \LogicException('Left scope is not mutable.');
@@ -70,12 +63,8 @@ public function set($key, $value)
/**
* Tests if a data is visible from current scope.
- *
- * @param string $key
- *
- * @return bool
*/
- public function has($key)
+ public function has(string $key): bool
{
if (\array_key_exists($key, $this->data)) {
return true;
@@ -90,13 +79,8 @@ public function has($key)
/**
* Returns data visible from current scope.
- *
- * @param string $key
- * @param mixed $default
- *
- * @return mixed
*/
- public function get($key, $default = null)
+ public function get(string $key, mixed $default = null): mixed
{
if (\array_key_exists($key, $this->data)) {
return $this->data[$key];
diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php
index 72badea2d2bd0..213365ed9f1ef 100644
--- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php
+++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php
@@ -27,10 +27,8 @@
/**
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
+final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
{
private $scope;
@@ -41,10 +39,8 @@ public function __construct()
/**
* {@inheritdoc}
- *
- * @return Node
*/
- protected function doEnterNode(Node $node, Environment $env)
+ protected function doEnterNode(Node $node, Environment $env): Node
{
if ($node instanceof BlockNode || $node instanceof ModuleNode) {
$this->scope = $this->scope->enter();
@@ -68,21 +64,18 @@ protected function doEnterNode(Node $node, Environment $env)
return $node;
}
- if ($node instanceof FilterExpression && \in_array($node->getNode('filter')->getAttribute('value'), ['trans', 'transchoice'])) {
+ if ($node instanceof FilterExpression && 'trans' === $node->getNode('filter')->getAttribute('value')) {
$arguments = $node->getNode('arguments');
- $ind = 'trans' === $node->getNode('filter')->getAttribute('value') ? 1 : 2;
if ($this->isNamedArguments($arguments)) {
- if (!$arguments->hasNode('domain') && !$arguments->hasNode($ind)) {
+ if (!$arguments->hasNode('domain') && !$arguments->hasNode(1)) {
$arguments->setNode('domain', $this->scope->get('domain'));
}
- } else {
- if (!$arguments->hasNode($ind)) {
- if (!$arguments->hasNode($ind - 1)) {
- $arguments->setNode($ind - 1, new ArrayExpression([], $node->getTemplateLine()));
- }
-
- $arguments->setNode($ind, $this->scope->get('domain'));
+ } elseif (!$arguments->hasNode(1)) {
+ if (!$arguments->hasNode(0)) {
+ $arguments->setNode(0, new ArrayExpression([], $node->getTemplateLine()));
}
+
+ $arguments->setNode(1, $this->scope->get('domain'));
}
} elseif ($node instanceof TransNode) {
if (!$node->hasNode('domain')) {
@@ -95,10 +88,8 @@ protected function doEnterNode(Node $node, Environment $env)
/**
* {@inheritdoc}
- *
- * @return Node|null
*/
- protected function doLeaveNode(Node $node, Environment $env)
+ protected function doLeaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof TransDefaultDomainNode) {
return null;
@@ -113,10 +104,8 @@ protected function doLeaveNode(Node $node, Environment $env)
/**
* {@inheritdoc}
- *
- * @return int
*/
- public function getPriority()
+ public function getPriority(): int
{
return -10;
}
diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
index b9b5959bbd766..c8bee150982df 100644
--- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
+++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
@@ -13,8 +13,10 @@
use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Environment;
+use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
+use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Node;
use Twig\NodeVisitor\AbstractNodeVisitor;
@@ -22,45 +24,35 @@
* TranslationNodeVisitor extracts translation messages.
*
* @author Fabien Potencier
- *
- * @final since Symfony 4.4
*/
-class TranslationNodeVisitor extends AbstractNodeVisitor
+final class TranslationNodeVisitor extends AbstractNodeVisitor
{
public const UNDEFINED_DOMAIN = '_undefined';
- private $enabled = false;
- private $messages = [];
+ private bool $enabled = false;
+ private array $messages = [];
- /**
- * @return void
- */
- public function enable()
+ public function enable(): void
{
$this->enabled = true;
$this->messages = [];
}
- /**
- * @return void
- */
- public function disable()
+ public function disable(): void
{
$this->enabled = false;
$this->messages = [];
}
- public function getMessages()
+ public function getMessages(): array
{
return $this->messages;
}
/**
* {@inheritdoc}
- *
- * @return Node
*/
- protected function doEnterNode(Node $node, Environment $env)
+ protected function doEnterNode(Node $node, Environment $env): Node
{
if (!$this->enabled) {
return $node;
@@ -77,21 +69,33 @@ protected function doEnterNode(Node $node, Environment $env)
$this->getReadDomainFromArguments($node->getNode('arguments'), 1),
];
} elseif (
- $node instanceof FilterExpression &&
- 'transchoice' === $node->getNode('filter')->getAttribute('value') &&
- $node->getNode('node') instanceof ConstantExpression
+ $node instanceof FunctionExpression &&
+ 't' === $node->getAttribute('name')
) {
- // extract constant nodes with a trans filter
- $this->messages[] = [
- $node->getNode('node')->getAttribute('value'),
- $this->getReadDomainFromArguments($node->getNode('arguments'), 2),
- ];
+ $nodeArguments = $node->getNode('arguments');
+
+ if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) {
+ $this->messages[] = [
+ $this->getReadMessageFromArguments($nodeArguments, 0),
+ $this->getReadDomainFromArguments($nodeArguments, 2),
+ ];
+ }
} elseif ($node instanceof TransNode) {
// extract trans nodes
$this->messages[] = [
$node->getNode('body')->getAttribute('data'),
$node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null,
];
+ } elseif (
+ $node instanceof FilterExpression &&
+ 'trans' === $node->getNode('filter')->getAttribute('value') &&
+ $node->getNode('node') instanceof ConcatBinary &&
+ $message = $this->getConcatValueFromNode($node->getNode('node'), null)
+ ) {
+ $this->messages[] = [
+ $message,
+ $this->getReadDomainFromArguments($node->getNode('arguments'), 1),
+ ];
}
return $node;
@@ -99,24 +103,42 @@ protected function doEnterNode(Node $node, Environment $env)
/**
* {@inheritdoc}
- *
- * @return Node|null
*/
- protected function doLeaveNode(Node $node, Environment $env)
+ protected function doLeaveNode(Node $node, Environment $env): ?Node
{
return $node;
}
/**
* {@inheritdoc}
- *
- * @return int
*/
- public function getPriority()
+ public function getPriority(): int
{
return 0;
}
+ private function getReadMessageFromArguments(Node $arguments, int $index): ?string
+ {
+ if ($arguments->hasNode('message')) {
+ $argument = $arguments->getNode('message');
+ } elseif ($arguments->hasNode($index)) {
+ $argument = $arguments->getNode($index);
+ } else {
+ return null;
+ }
+
+ return $this->getReadMessageFromNode($argument);
+ }
+
+ private function getReadMessageFromNode(Node $node): ?string
+ {
+ if ($node instanceof ConstantExpression) {
+ return $node->getAttribute('value');
+ }
+
+ return null;
+ }
+
private function getReadDomainFromArguments(Node $arguments, int $index): ?string
{
if ($arguments->hasNode('domain')) {
@@ -138,4 +160,28 @@ private function getReadDomainFromNode(Node $node): ?string
return self::UNDEFINED_DOMAIN;
}
+
+ private function getConcatValueFromNode(Node $node, ?string $value): ?string
+ {
+ if ($node instanceof ConcatBinary) {
+ foreach ($node as $nextNode) {
+ if ($nextNode instanceof ConcatBinary) {
+ $nextValue = $this->getConcatValueFromNode($nextNode, $value);
+ if (null === $nextValue) {
+ return null;
+ }
+ $value .= $nextValue;
+ } elseif ($nextNode instanceof ConstantExpression) {
+ $value .= $nextNode->getAttribute('value');
+ } else {
+ // this is a node we cannot process (variable, or translation in translation)
+ return null;
+ }
+ }
+ } elseif ($node instanceof ConstantExpression) {
+ $value .= $node->getAttribute('value');
+ }
+
+ return $value;
+ }
}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig
index 2f3a346df5903..0a52d36b374ed 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig
@@ -16,7 +16,7 @@
{% block lead %}
- {{ importance|upper }}
+ {% if importance is not null %}{{ importance|upper }}{% endif %}
{{ email.subject }}
@@ -49,13 +49,15 @@
{% block footer %}
+ {% if footer_text is defined and footer_text is not null %}
{% block footer_content %}
- Notification e-mail sent by Symfony
+ {{ footer_text }}
{% endblock %}
+ {% endif %}
{% endblock %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig
index db855829703e4..c98bb08a74c03 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig
@@ -8,7 +8,7 @@
{% block action %}
{% if action_url %}
-{{ action_url }}: {{ action_text }}
+{{ action_text }}: {{ action_url }}
{% endif %}
{% endblock %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
index 12d9545f3aaff..34cbc76074acd 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
@@ -54,6 +54,11 @@
{%- endif -%}
{%- endblock radio_widget %}
+{% block choice_widget_collapsed -%}
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%}
+ {{- parent() -}}
+{%- endblock choice_widget_collapsed %}
+
{# Labels #}
{% block form_label -%}
@@ -96,7 +101,7 @@
{%- endif -%}
{%- endif -%}
{%- endif -%}
{%- endblock checkbox_radio_label %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig
index f08fc8d20f9cc..0e80840541fa1 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig
@@ -121,6 +121,9 @@
{% block file_widget -%}
<{{ element|default('div') }} class="custom-file">
{%- set type = type|default('file') -%}
+ {%- set input_lang = 'en' -%}
+ {% if app is defined and app.request is defined %}{%- set input_lang = app.request.locale -%}{%- endif -%}
+ {%- set attr = {lang: input_lang} | merge(attr) -%}
{{- block('form_widget_simple') -}}
{%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim })|filter((value, key) => key != 'id') -%}