From 253d0a683b3e5390fa5944f709f2d077af028673 Mon Sep 17 00:00:00 2001 From: Franz Wilding Date: Mon, 14 May 2018 10:16:24 +0200 Subject: [PATCH 01/12] [Form] Fix DateTimeType html5 input format --- ...ateTimeToHtml5DateTimeLocalTransformer.php | 97 +++++++++++++ .../Form/Extension/Core/Type/DateTimeType.php | 21 +-- .../Tests/AbstractBootstrap3LayoutTest.php | 4 +- .../Form/Tests/AbstractLayoutTest.php | 4 +- ...meToHtml5DateTimeLocaleTransformerTest.php | 129 ++++++++++++++++++ .../Extension/Core/Type/DateTimeTypeTest.php | 12 +- 6 files changed, 240 insertions(+), 27 deletions(-) create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php new file mode 100644 index 0000000000..10b2e9aa7f --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Franz Wilding + * @author Bernhard Schussek + */ +class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer +{ + const HTML5_FORMAT = 'Y-m-d\\TH:i:s'; + + /** + * Transforms a normalized date into a localized date without trailing timezone. + * + * According to the HTML standard, the input string of a datetime-local + * input is a RFC3339 date followed by 'T', followed by a RFC3339 time. + * http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local + * + * @param \DateTime|\DateTimeInterface $dateTime A DateTime object + * + * @return string The formatted date + * + * @throws TransformationFailedException If the given value is not an + * instance of \DateTime or \DateTimeInterface + */ + public function transform($dateTime) + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTime && !$dateTime instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTime or \DateTimeInterface.'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + if (!$dateTime instanceof \DateTimeImmutable) { + $dateTime = clone $dateTime; + } + + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + return $dateTime->format(self::HTML5_FORMAT); + } + + /** + * Transforms a formatted datetime-local string into a normalized date. + * + * @param string $dateTimeLocal Formatted string + * + * @return \DateTime Normalized date + * + * @throws TransformationFailedException If the given value is not a string, + * if the value could not be transformed + */ + public function reverseTransform($dateTimeLocal) + { + if (!\is_string($dateTimeLocal)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $dateTimeLocal) { + return; + } + + try { + $dateTime = new \DateTime($dateTimeLocal, new \DateTimeZone($this->outputTimezone)); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) { + $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $dateTimeLocal, $m)) { + if (!checkdate($m[2], $m[3], $m[1])) { + throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $m[1], $m[2], $m[3])); + } + } + + return $dateTime; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index e6598a3f10..28df7eb7ed 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -15,8 +15,8 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\FormBuilderInterface; @@ -33,21 +33,8 @@ class DateTimeType extends AbstractType const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM; /** - * This is not quite the HTML5 format yet, because ICU lacks the - * capability of parsing and generating RFC 3339 dates. - * - * For more information see: - * - * http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax - * https://www.w3.org/TR/html5/sec-forms.html#local-date-and-time-state-typedatetimelocal - * http://tools.ietf.org/html/rfc3339 - * - * An ICU ticket was created: - * http://icu-project.org/trac/ticket/9421 - * - * It was supposedly fixed, but is not available in all PHP installations - * yet. To temporarily circumvent this issue, DateTimeToRfc3339Transformer - * is used when the format matches this constant. + * The HTML5 datetime-local format as defined in + * http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local. */ const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; @@ -88,7 +75,7 @@ class DateTimeType extends AbstractType if ('single_text' === $options['widget']) { if (self::HTML5_FORMAT === $pattern) { - $builder->addViewTransformer(new DateTimeToRfc3339Transformer( + $builder->addViewTransformer(new DateTimeToHtml5DateTimeLocalTransformer( $options['model_timezone'], $options['view_timezone'] )); diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php index 576b29e526..ef3c17a4c6 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -1603,7 +1603,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest [@type="datetime-local"] [@name="name"] [@class="my&class form-control"] - [@value="2011-02-03T04:05:06Z"] + [@value="2011-02-03T04:05:06"] ' ); } @@ -1624,7 +1624,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest [@type="datetime-local"] [@name="name"] [@class="my&class form-control"] - [@value="2011-02-03T04:05:06Z"] + [@value="2011-02-03T04:05:06"] ' ); } diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index fbeab91092..1d5ebc96a2 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -1502,7 +1502,7 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase '/input [@type="datetime-local"] [@name="name"] - [@value="2011-02-03T04:05:06Z"] + [@value="2011-02-03T04:05:06"] ' ); } @@ -1522,7 +1522,7 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase '/input [@type="datetime-local"] [@name="name"] - [@value="2011-02-03T04:05:06Z"] + [@value="2011-02-03T04:05:06"] ' ); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php new file mode 100644 index 0000000000..f2645ac0c5 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer; + +class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase +{ + public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) + { + if ($expected instanceof \DateTime && $actual instanceof \DateTime) { + $expected = $expected->format('c'); + $actual = $actual->format('c'); + } + + parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); + } + + public function transformProvider() + { + return array( + array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'), + array('UTC', 'UTC', null, ''), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06'), + array('America/New_York', 'Asia/Hong_Kong', null, ''), + array('UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06'), + array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'), + ); + } + + public function reverseTransformProvider() + { + return array( + // format without seconds, as appears in some browsers + array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'), + array('UTC', 'UTC', null, ''), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06'), + array('America/New_York', 'Asia/Hong_Kong', null, ''), + array('UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06'), + array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'), + array('UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05'), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05'), + array('Europe/Amsterdam', 'Europe/Amsterdam', '2013-08-21 10:30:00 Europe/Amsterdam', '2013-08-21T10:30:00'), + ); + } + + /** + * @dataProvider transformProvider + */ + public function testTransform($fromTz, $toTz, $from, $to) + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz); + + $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null)); + } + + /** + * @dataProvider transformProvider + * @requires PHP 5.5 + */ + public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to) + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz); + + $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testTransformRequiresValidDateTime() + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer(); + $transformer->transform('2010-01-01'); + } + + /** + * @dataProvider reverseTransformProvider + */ + public function testReverseTransform($toTz, $fromTz, $to, $from) + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer($toTz, $fromTz); + + if (null !== $to) { + $this->assertEquals(new \DateTime($to), $transformer->reverseTransform($from)); + } else { + $this->assertNull($transformer->reverseTransform($from)); + } + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformRequiresString() + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer(); + $transformer->reverseTransform(12345); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNonExistingDate() + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC'); + + $transformer->reverseTransform('2010-04-31T04:05'); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformExpectsValidDateString() + { + $transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC'); + + $transformer->reverseTransform('2010-2010-2010'); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index f4bc3f8827..27c6581f0b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -221,12 +221,12 @@ class DateTimeTypeTest extends BaseTypeTest $outputTime = new \DateTime('2010-06-02 03:04:00 Pacific/Tahiti'); - $form->submit('2010-06-02T03:04:00-10:00'); + $form->submit('2010-06-02T03:04:00'); $outputTime->setTimezone(new \DateTimeZone('America/New_York')); $this->assertEquals($outputTime, $form->getData()); - $this->assertEquals('2010-06-02T03:04:00-10:00', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04:00', $form->getViewData()); } public function testSubmitStringSingleText() @@ -238,10 +238,10 @@ class DateTimeTypeTest extends BaseTypeTest 'widget' => 'single_text', )); - $form->submit('2010-06-02T03:04:00Z'); + $form->submit('2010-06-02T03:04:00'); $this->assertEquals('2010-06-02 03:04:00', $form->getData()); - $this->assertEquals('2010-06-02T03:04:00Z', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04:00', $form->getViewData()); } public function testSubmitStringSingleTextWithSeconds() @@ -254,10 +254,10 @@ class DateTimeTypeTest extends BaseTypeTest 'with_seconds' => true, )); - $form->submit('2010-06-02T03:04:05Z'); + $form->submit('2010-06-02T03:04:05'); $this->assertEquals('2010-06-02 03:04:05', $form->getData()); - $this->assertEquals('2010-06-02T03:04:05Z', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04:05', $form->getViewData()); } public function testSubmitDifferentPattern() From d6f5d6bccd398f4da7df8d3cae9bb105540ba713 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 14 Sep 2018 18:15:55 +0200 Subject: [PATCH 02/12] Fix symfony/console (optional) dependency for MonologBridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 278c26f589fa41506e13aa70a15c065d193f04d2, `ConsoleHandler` tries to pass a verbosity level into `Output::write()`. In order to make this work, the change 749fba54f91283041ea0d59322ac2016ef38e071 is required which was first released in 2.8.0. When using MonologBridge ^3.3 with a lower version of symfony/console than 2.8, an `InvalidArgumentException` with the message `Unknown output type given` will be thrown. Not sure how to add a test for this... 🤷‍♂️ : --- src/Symfony/Bridge/Monolog/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 1deb3ca9d3..d2791d4ec5 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -27,11 +27,12 @@ "symfony/var-dumper": "~3.3|~4.0" }, "conflict": { + "symfony/console": "<2.8", "symfony/http-foundation": "<3.3" }, "suggest": { "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", - "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", + "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ^2.8 of the console for it.", "symfony/event-dispatcher": "Needed when using log messages in console commands.", "symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler." }, From 4f06f1524d41497b5c07a302c2457410ece9a523 Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Sat, 15 Sep 2018 14:25:16 +0300 Subject: [PATCH 03/12] Add stricter checking for valid date time string --- .../DateTimeToHtml5DateTimeLocalTransformer.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php index 10b2e9aa7f..041e516483 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php @@ -76,6 +76,10 @@ class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer return; } + if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2})?$/', $dateTimeLocal, $matches)) { + throw new TransformationFailedException(sprintf('The date "%s" is not a valid date.', $dateTimeLocal)); + } + try { $dateTime = new \DateTime($dateTimeLocal, new \DateTimeZone($this->outputTimezone)); } catch (\Exception $e) { @@ -86,10 +90,8 @@ class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); } - if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $dateTimeLocal, $m)) { - if (!checkdate($m[2], $m[3], $m[1])) { - throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $m[1], $m[2], $m[3])); - } + if (!checkdate($matches[2], $matches[3], $matches[1])) { + throw new TransformationFailedException(sprintf('The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3])); } return $dateTime; From e21a1a4df1203086c090e7884f2f7232f12ad025 Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Sat, 15 Sep 2018 14:32:16 +0300 Subject: [PATCH 04/12] Added relevent links for parsing to the phpdoc --- ...ateTimeToHtml5LocalDateTimeTransformer.php} | 13 +++++++++---- .../Form/Extension/Core/Type/DateTimeType.php | 4 ++-- ...imeToHtml5LocalDateTimeTransformerTest.php} | 18 +++++++++--------- 3 files changed, 20 insertions(+), 15 deletions(-) rename src/Symfony/Component/Form/Extension/Core/DataTransformer/{DateTimeToHtml5DateTimeLocalTransformer.php => DateTimeToHtml5LocalDateTimeTransformer.php} (83%) rename src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/{DateTimeToHtml5DateTimeLocaleTransformerTest.php => DateTimeToHtml5LocalDateTimeTransformerTest.php} (88%) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php similarity index 83% rename from src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php rename to src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php index 041e516483..ed90331266 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocalTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php @@ -16,17 +16,18 @@ use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Franz Wilding * @author Bernhard Schussek + * @author Fred Cox */ -class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer +class DateTimeToHtml5LocalDateTimeTransformer extends BaseDateTimeTransformer { const HTML5_FORMAT = 'Y-m-d\\TH:i:s'; /** - * Transforms a normalized date into a localized date without trailing timezone. + * Transforms a \DateTime into a local date and time string. * * According to the HTML standard, the input string of a datetime-local * input is a RFC3339 date followed by 'T', followed by a RFC3339 time. - * http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local + * https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string * * @param \DateTime|\DateTimeInterface $dateTime A DateTime object * @@ -57,7 +58,11 @@ class DateTimeToHtml5DateTimeLocalTransformer extends BaseDateTimeTransformer } /** - * Transforms a formatted datetime-local string into a normalized date. + * Transforms a local date and time string into a \DateTime. + * + * When transforming back to DateTime the regex is slightly laxer, taking into + * account rules for parsing a local date and time string + * https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-local-date-and-time-string * * @param string $dateTimeLocal Formatted string * diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 28df7eb7ed..4913957117 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -15,7 +15,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5LocalDateTimeTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; @@ -75,7 +75,7 @@ class DateTimeType extends AbstractType if ('single_text' === $options['widget']) { if (self::HTML5_FORMAT === $pattern) { - $builder->addViewTransformer(new DateTimeToHtml5DateTimeLocalTransformer( + $builder->addViewTransformer(new DateTimeToHtml5LocalDateTimeTransformer( $options['model_timezone'], $options['view_timezone'] )); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php similarity index 88% rename from src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php rename to src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php index f2645ac0c5..5dac998ad1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5DateTimeLocaleTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php @@ -12,9 +12,9 @@ namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5DateTimeLocalTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5LocalDateTimeTransformer; -class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase +class DateTimeToHtml5LocalDateTimeTransformerTest extends TestCase { public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { @@ -59,7 +59,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testTransform($fromTz, $toTz, $from, $to) { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz); $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null)); } @@ -70,7 +70,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to) { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer($fromTz, $toTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz); $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null)); } @@ -80,7 +80,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testTransformRequiresValidDateTime() { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer(); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer(); $transformer->transform('2010-01-01'); } @@ -89,7 +89,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testReverseTransform($toTz, $fromTz, $to, $from) { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer($toTz, $fromTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($toTz, $fromTz); if (null !== $to) { $this->assertEquals(new \DateTime($to), $transformer->reverseTransform($from)); @@ -103,7 +103,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testReverseTransformRequiresString() { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer(); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer(); $transformer->reverseTransform(12345); } @@ -112,7 +112,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testReverseTransformWithNonExistingDate() { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC'); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer('UTC', 'UTC'); $transformer->reverseTransform('2010-04-31T04:05'); } @@ -122,7 +122,7 @@ class DateTimeToHtml5DateTimeLocaleTransformerTest extends TestCase */ public function testReverseTransformExpectsValidDateString() { - $transformer = new DateTimeToHtml5DateTimeLocalTransformer('UTC', 'UTC'); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer('UTC', 'UTC'); $transformer->reverseTransform('2010-2010-2010'); } From 9ef7f7038d87f25211edc2f5366e699c38c0c435 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 16 Sep 2018 21:41:33 +0200 Subject: [PATCH 05/12] [HttpFoundation] don't override StreamedResponse::setNotModified() --- .../Component/HttpFoundation/StreamedResponse.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/StreamedResponse.php b/src/Symfony/Component/HttpFoundation/StreamedResponse.php index 8552d71305..ed1c5ff3ec 100644 --- a/src/Symfony/Component/HttpFoundation/StreamedResponse.php +++ b/src/Symfony/Component/HttpFoundation/StreamedResponse.php @@ -123,6 +123,8 @@ class StreamedResponse extends Response if (null !== $content) { throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); } + + $this->streamed = true; } /** @@ -134,16 +136,4 @@ class StreamedResponse extends Response { return false; } - - /** - * {@inheritdoc} - * - * @return $this - */ - public function setNotModified() - { - $this->setCallback(function () {}); - - return parent::setNotModified(); - } } From 0c1484b849d57a768c09d0a2917e8cd936458974 Mon Sep 17 00:00:00 2001 From: Rudy Onfroy Date: Thu, 13 Sep 2018 15:16:37 +0200 Subject: [PATCH 06/12] [OptionsResolver] remove dead code and useless else --- .../OptionsResolver/OptionsResolver.php | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index b6bda0708e..83d81969cb 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -770,7 +770,9 @@ class OptionsResolver implements Options // Don't include closures in the exception message continue; - } elseif ($value === $allowedValue) { + } + + if ($value === $allowedValue) { $success = true; break; } @@ -1060,20 +1062,4 @@ class OptionsResolver implements Options { return (\function_exists($isFunction = 'is_'.$type) && $isFunction($value)) || $value instanceof $type; } - - /** - * @return array - */ - private function getInvalidValues(array $arrayValues, $type) - { - $invalidValues = array(); - - foreach ($arrayValues as $key => $value) { - if (!self::isValueValidType($type, $value)) { - $invalidValues[$key] = $value; - } - } - - return $invalidValues; - } } From 63671d16330a24aef5d8576332a35b93ec23af73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Mon, 17 Sep 2018 22:24:45 +0200 Subject: [PATCH 07/12] Implement startTest rather than startTestSuite Passing a TestSuite instance to CoverageListenerTrait::testStart() will have no effect. --- .../Bridge/PhpUnit/Legacy/CoverageListenerForV5.php | 4 ++-- .../Bridge/PhpUnit/Legacy/CoverageListenerForV6.php | 4 ++-- .../Bridge/PhpUnit/Legacy/CoverageListenerForV7.php | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php index 4bf19223f3..9d754eebc8 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\PhpUnit\Legacy; /** - * CoverageListener adds `@covers ` on each test suite when possible - * to make the code coverage more accurate. + * CoverageListener adds `@covers ` on each test when possible to + * make the code coverage more accurate. * * @author Grégoire Pineau * diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php index f0dfc7577c..0917ea4710 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php @@ -15,8 +15,8 @@ use PHPUnit\Framework\BaseTestListener; use PHPUnit\Framework\Test; /** - * CoverageListener adds `@covers ` on each test suite when possible - * to make the code coverage more accurate. + * CoverageListener adds `@covers ` on each test when possible to + * make the code coverage more accurate. * * @author Grégoire Pineau * diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php index 1d29a88441..a35034c48b 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php @@ -11,13 +11,13 @@ namespace Symfony\Bridge\PhpUnit\Legacy; +use PHPUnit\Framework\Test; use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestListenerDefaultImplementation; -use PHPUnit\Framework\TestSuite; /** - * CoverageListener adds `@covers ` on each test suite when possible - * to make the code coverage more accurate. + * CoverageListener adds `@covers ` on each test when possible to + * make the code coverage more accurate. * * @author Grégoire Pineau * @@ -34,8 +34,8 @@ class CoverageListenerForV7 implements TestListener $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); } - public function startTestSuite(TestSuite $suite): void + public function startTest(Test $test): void { - $this->trait->startTest($suite); + $this->trait->startTest($test); } } From af54189dfc2590b92358a1ece3528a3eeb7b3770 Mon Sep 17 00:00:00 2001 From: Andras Debreczeni Date: Tue, 18 Sep 2018 09:27:02 +0200 Subject: [PATCH 08/12] [Ldap] Use shut up operator on connection errors at ldap_start_tls --- src/Symfony/Component/Ldap/LdapClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Ldap/LdapClient.php b/src/Symfony/Component/Ldap/LdapClient.php index de341e06d2..92a7be9970 100644 --- a/src/Symfony/Component/Ldap/LdapClient.php +++ b/src/Symfony/Component/Ldap/LdapClient.php @@ -136,7 +136,7 @@ class LdapClient implements LdapClientInterface ldap_set_option($this->connection, LDAP_OPT_REFERRALS, $this->optReferrals); if ($this->useStartTls) { - ldap_start_tls($this->connection); + @ldap_start_tls($this->connection); } } } From 0def211b9b67a815fb70476ab5297e57a02daf82 Mon Sep 17 00:00:00 2001 From: Patrick Carlo-Hickman Date: Tue, 18 Sep 2018 03:36:25 +0000 Subject: [PATCH 09/12] [VarDumper] Fix global dump function return value for PHP7 --- .../VarDumper/Resources/functions/dump.php | 4 +- .../VarDumper/Tests/Dumper/FunctionsTest.php | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php diff --git a/src/Symfony/Component/VarDumper/Resources/functions/dump.php b/src/Symfony/Component/VarDumper/Resources/functions/dump.php index 95d8bb5980..0e0e4d0435 100644 --- a/src/Symfony/Component/VarDumper/Resources/functions/dump.php +++ b/src/Symfony/Component/VarDumper/Resources/functions/dump.php @@ -17,8 +17,8 @@ if (!function_exists('dump')) { */ function dump($var) { - foreach (func_get_args() as $var) { - VarDumper::dump($var); + foreach (func_get_args() as $v) { + VarDumper::dump($v); } if (1 < func_num_args()) { diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php new file mode 100644 index 0000000000..81ecdddc56 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\VarDumper; + +class FunctionsTest extends TestCase +{ + public function testDumpReturnsFirstArg() + { + $this->setupVarDumper(); + + $var1 = 'a'; + + ob_start(); + $return = dump($var1); + $out = ob_get_clean(); + + $this->assertEquals($var1, $return); + } + + public function testDumpReturnsAllArgsInArray() + { + $this->setupVarDumper(); + + $var1 = 'a'; + $var2 = 'b'; + $var3 = 'c'; + + ob_start(); + $return = dump($var1, $var2, $var3); + $out = ob_get_clean(); + + $this->assertEquals(array($var1, $var2, $var3), $return); + } + + protected function setupVarDumper() + { + $cloner = new VarCloner(); + $dumper = new CliDumper('php://output'); + VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }); + } +} From 2a59c8e3e6294d01d458038d9264aacb6374f8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20ALFAIATE?= Date: Sun, 16 Sep 2018 19:04:45 +0700 Subject: [PATCH 10/12] [DI] Detect circular references with ChildDefinition parent --- .../Compiler/ResolveChildDefinitionsPass.php | 13 +++++++++++++ .../ResolveChildDefinitionsPassTest.php | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php index 8cbccdaa3b..d647eda237 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ExceptionInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; /** * This replaces all ChildDefinition instances with their equivalent fully @@ -25,6 +26,8 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class ResolveChildDefinitionsPass extends AbstractRecursivePass { + private $currentPath; + protected function processValue($value, $isRoot = false) { if (!$value instanceof Definition) { @@ -36,6 +39,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass $value = $this->container->getDefinition($this->currentId); } if ($value instanceof ChildDefinition) { + $this->currentPath = array(); $value = $this->resolveDefinition($value); if ($isRoot) { $this->container->setDefinition($this->currentId, $value); @@ -56,6 +60,8 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass { try { return $this->doResolveDefinition($definition); + } catch (ServiceCircularReferenceException $e) { + throw $e; } catch (ExceptionInterface $e) { $r = new \ReflectionProperty($e, 'message'); $r->setAccessible(true); @@ -71,6 +77,13 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent)); } + $searchKey = array_search($parent, $this->currentPath); + $this->currentPath[] = $parent; + + if (false !== $searchKey) { + throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey)); + } + $parentDef = $this->container->findDefinition($parent); if ($parentDef instanceof ChildDefinition) { $id = $this->currentId; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php index d30728a48b..1575bd7b07 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php @@ -431,4 +431,21 @@ class ResolveChildDefinitionsPassTest extends TestCase $pass = new ResolveChildDefinitionsPass(); $pass->process($container); } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + * @expectedExceptionMessageRegExp /^Circular reference detected for service "c", path: "c -> b -> a -> c"./ + */ + public function testProcessDetectsChildDefinitionIndirectCircularReference() + { + $container = new ContainerBuilder(); + + $container->register('a'); + + $container->setDefinition('b', new ChildDefinition('a')); + $container->setDefinition('c', new ChildDefinition('b')); + $container->setDefinition('a', new ChildDefinition('c')); + + $this->process($container); + } } From 7cb340a2dbf7015bcad466830ebfa51a39392085 Mon Sep 17 00:00:00 2001 From: Mponos George Date: Fri, 14 Sep 2018 00:01:37 +0300 Subject: [PATCH 11/12] KernelInterface can return null container --- src/Symfony/Component/HttpKernel/KernelInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/KernelInterface.php b/src/Symfony/Component/HttpKernel/KernelInterface.php index 18b128c296..670e0269db 100644 --- a/src/Symfony/Component/HttpKernel/KernelInterface.php +++ b/src/Symfony/Component/HttpKernel/KernelInterface.php @@ -139,7 +139,7 @@ interface KernelInterface extends HttpKernelInterface, \Serializable /** * Gets the current container. * - * @return ContainerInterface A ContainerInterface instance + * @return ContainerInterface|null A ContainerInterface instance or null when the Kernel is shutdown */ public function getContainer(); From 0b4373816230aa2c2e3d864fbf8a83a116f65c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Stamenkovi=C4=87?= Date: Thu, 13 Sep 2018 20:01:11 +0200 Subject: [PATCH 12/12] Think positive --- src/Symfony/Component/Lock/Lock.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index 77fd032bf1..9cd15dd40b 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -69,10 +69,10 @@ final class Lock implements LockInterface, LoggerAwareInterface public function acquire($blocking = false) { try { - if (!$blocking) { - $this->store->save($this->key); - } else { + if ($blocking) { $this->store->waitAndSave($this->key); + } else { + $this->store->save($this->key); } $this->dirty = true;