From 7959da65fedcfd73e03152390dd8b7462699d991 Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Thu, 28 May 2020 00:25:48 -0300 Subject: [PATCH] Add `TimezoneAwareInterface` --- .github/workflows/qa.yml | 2 + UPGRADE-2.x.md | 33 +++++++++++- composer.json | 3 -- docs/reference/configuration.rst | 11 ++++ .../SonataIntlExtension.php | 21 +++----- src/Timezone/TimezoneAwareInterface.php | 22 ++++++++ src/Timezone/TimezoneAwareTrait.php | 32 +++++++++++ src/Timezone/TimezoneDetectorInterface.php | 2 +- src/Timezone/UserBasedTimezoneDetector.php | 18 ++++++- .../UserBasedTimezoneDetectorTest.php | 54 +++++++++++++++---- 10 files changed, 165 insertions(+), 33 deletions(-) create mode 100644 src/Timezone/TimezoneAwareInterface.php create mode 100644 src/Timezone/TimezoneAwareTrait.php diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 1c94d708..a7963134 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -8,5 +8,7 @@ jobs: - uses: actions/checkout@master - name: PHPStan uses: "docker://oskarstark/phpstan-ga" + env: + REQUIRE_DEV: true with: args: analyse diff --git a/UPGRADE-2.x.md b/UPGRADE-2.x.md index 48fa125c..c02b94b0 100644 --- a/UPGRADE-2.x.md +++ b/UPGRADE-2.x.md @@ -1,11 +1,40 @@ UPGRADE 2.x =========== +UPGRADE FROM 2.7 to 2.8 +======================= + +### Timezone detector + +``Sonata\IntlBundle\Timezone\TimezoneAwareInterface`` was added in order to provide +timezone detection for any user class. + +Timezone inference based on the ``Sonata\UserBundle\Model\User::getTimezone()`` method +is deprecated and will be dropped in 3.0 version. +You MUST implement ``Sonata\IntlBundle\Timezone\TimezoneAwareInterface`` explicitly +in your user class. + +Before: +```php +class User +{ + // ... +} +``` + +After: +```php +class User implements \Sonata\IntlBundle\Timezone\TimezoneAwareInterface +{ + // ... +} +``` + UPGRADE FROM 2.3 to 2.4 ======================= ### Tests -All files under the ``Tests`` directory are now correctly handled as internal test classes. -You can't extend them anymore, because they are only loaded when running internal tests. +All files under the ``Tests`` directory are now correctly handled as internal test classes. +You can't extend them anymore, because they are only loaded when running internal tests. More information can be found in the [composer docs](https://getcomposer.org/doc/04-schema.md#autoload-dev). diff --git a/composer.json b/composer.json index a578dc36..5981ce41 100644 --- a/composer.json +++ b/composer.json @@ -40,9 +40,6 @@ "symfony/phpunit-bridge": "^5.0", "symfony/security-core": "^4.4" }, - "suggest": { - "sonata-project/user-bundle": "For user timezone detection" - }, "config": { "sort-packages": true }, diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst index 655c266f..645b00e9 100644 --- a/docs/reference/configuration.rst +++ b/docs/reference/configuration.rst @@ -10,6 +10,17 @@ Timezone detectors User timezone detector ^^^^^^^^^^^^^^^^^^^^^^ +.. versionadded:: 2.7 + + If the model class for the authenticated user implements ``Sonata\IntlBundle\Timezone\TimezoneAwareInterface``, + it returns the timezone from its ``getTimezone()`` method. + For convenience, the ``Sonata\IntlBundle\Timezone\TimezoneAwareTrait`` is available, + which provides a basic implementation. + +**DEPRECATED** +Relying on ``Sonata\UserBundle\Model\User`` is deprecated since 2.x in favor of +explicit implementation of ``Sonata\IntlBundle\Timezone\TimezoneAwareInterface``. + If the SonataUserBundle_ is enabled, it returns the timezone from the ``Sonata\UserBundle\Model\User::getTimezone()`` method. diff --git a/src/DependencyInjection/SonataIntlExtension.php b/src/DependencyInjection/SonataIntlExtension.php index 939caaee..7bd31a06 100644 --- a/src/DependencyInjection/SonataIntlExtension.php +++ b/src/DependencyInjection/SonataIntlExtension.php @@ -60,15 +60,12 @@ protected function configureTimezone(ContainerBuilder $container, array $config) $timezoneDetectors = $config['timezone']['detectors']; - $bundles = $container->getParameter('kernel.bundles'); - - if (0 === \count($timezoneDetectors)) { // no value define in the configuration, set one - // Support Sonata User Bundle. - if (isset($bundles['SonataUserBundle'])) { - $timezoneDetectors[] = 'sonata.intl.timezone_detector.user'; - } - - $timezoneDetectors[] = 'sonata.intl.timezone_detector.locale'; + if (0 === \count($timezoneDetectors)) { + // define default values if there is no value defined in configuration. + $timezoneDetectors = [ + 'sonata.intl.timezone_detector.user', + 'sonata.intl.timezone_detector.locale', + ]; } foreach ($timezoneDetectors as $id) { @@ -87,10 +84,6 @@ protected function configureTimezone(ContainerBuilder $container, array $config) ->replaceArgument(0, $config['timezone']['default']) ; - if (!isset($bundles['SonataUserBundle'])) { - $container->removeDefinition('sonata.intl.timezone_detector.user'); - } - $container->setParameter('sonata_intl.timezone.detectors', $timezoneDetectors); } @@ -105,7 +98,7 @@ protected function configureLocale(ContainerBuilder $container, array $config) * * @throws \RuntimeException If one of the locales is invalid */ - private function validateTimezones(array $timezones) + private function validateTimezones(array $timezones): void { foreach ($timezones as $timezone) { try { diff --git a/src/Timezone/TimezoneAwareInterface.php b/src/Timezone/TimezoneAwareInterface.php new file mode 100644 index 00000000..a7885db3 --- /dev/null +++ b/src/Timezone/TimezoneAwareInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\IntlBundle\Timezone; + +/** + * @author Javier Spagnoletti + */ +interface TimezoneAwareInterface +{ + public function getTimezone(): ?string; +} diff --git a/src/Timezone/TimezoneAwareTrait.php b/src/Timezone/TimezoneAwareTrait.php new file mode 100644 index 00000000..41d8ab62 --- /dev/null +++ b/src/Timezone/TimezoneAwareTrait.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\IntlBundle\Timezone; + +/** + * Basic Implementation of TimezoneAwareInterface. + * + * @author Javier Spagnoletti + */ +trait TimezoneAwareTrait +{ + /** + * @var string|null + */ + private $timezone; + + final public function getTimezone(): ?string + { + return $this->timezone; + } +} diff --git a/src/Timezone/TimezoneDetectorInterface.php b/src/Timezone/TimezoneDetectorInterface.php index da858b8d..f2a0ee98 100644 --- a/src/Timezone/TimezoneDetectorInterface.php +++ b/src/Timezone/TimezoneDetectorInterface.php @@ -23,7 +23,7 @@ interface TimezoneDetectorInterface /** * Get the appropriate timezone. * - * @return string + * @return string|null */ public function getTimezone(); } diff --git a/src/Timezone/UserBasedTimezoneDetector.php b/src/Timezone/UserBasedTimezoneDetector.php index 8f347cb4..cbac2ed1 100644 --- a/src/Timezone/UserBasedTimezoneDetector.php +++ b/src/Timezone/UserBasedTimezoneDetector.php @@ -36,15 +36,29 @@ public function __construct(TokenStorageInterface $securityContext) public function getTimezone() { if (!$token = $this->securityContext->getToken()) { - return; + return null; } if (!$user = $token->getUser()) { - return; + return null; } + if ($user instanceof TimezoneAwareInterface) { + return $user->getTimezone(); + } + + // NEXT_MAJOR: Remove this check and the related documentation at `docs/reference/configuration.rst`. if ($user instanceof User) { + @trigger_error(sprintf( + 'Timezone inference based on the %s class is deprecated since sonata-project/intl-bundle 2.x and will be dropped in 3.0 version.' + .' Implement %s explicitly in your user class instead.', + User::class, + TimezoneAwareInterface::class + ), E_USER_DEPRECATED); + return $user->getTimezone(); } + + return null; } } diff --git a/tests/Timezone/UserBasedTimezoneDetectorTest.php b/tests/Timezone/UserBasedTimezoneDetectorTest.php index d9917aad..27438eb2 100644 --- a/tests/Timezone/UserBasedTimezoneDetectorTest.php +++ b/tests/Timezone/UserBasedTimezoneDetectorTest.php @@ -14,6 +14,8 @@ namespace Sonata\IntlBundle\Tests\Timezone; use PHPUnit\Framework\TestCase; +use Sonata\IntlBundle\Timezone\TimezoneAwareInterface; +use Sonata\IntlBundle\Timezone\TimezoneAwareTrait; use Sonata\IntlBundle\Timezone\UserBasedTimezoneDetector; use Sonata\UserBundle\Model\User; use Sonata\UserBundle\SonataUserBundle; @@ -23,16 +25,9 @@ /** * @author Emmanuel Vella */ -class UserBasedTimezoneDetectorTest extends TestCase +final class UserBasedTimezoneDetectorTest extends TestCase { - protected function setUp(): void - { - if (!class_exists(SonataUserBundle::class)) { - $this->markTestSkipped('SonataUserBundle must be installed to run this test.'); - } - } - - public static function timezoneProvider() + public static function timezoneProvider(): iterable { return [ ['Europe/Paris'], @@ -40,13 +35,50 @@ public static function timezoneProvider() ]; } + /** + * @dataProvider timezoneProvider + */ + public function testUserTimezoneDetection(?string $timezone): void + { + $user = new class($timezone) implements TimezoneAwareInterface { + use TimezoneAwareTrait; + + public function __construct(?string $timezone) + { + $this->timezone = $timezone; + } + }; + + $token = $this->createMock(TokenInterface::class); + $token + ->expects($this->once()) + ->method('getUser') + ->willReturn($user) + ; + + $storage = $this->createMock(TokenStorageInterface::class); + + $storage + ->expects($this->once()) + ->method('getToken') + ->willReturn($token) + ; + + $timezoneDetector = new UserBasedTimezoneDetector($storage); + $this->assertSame($timezone, $timezoneDetector->getTimezone()); + } + /** * @dataProvider timezoneProvider * * @group legacy */ - public function testDetectsTimezoneForUser($timezone) + public function testDetectsTimezoneForUser(?string $timezone): void { + if (!class_exists(SonataUserBundle::class)) { + $this->markTestSkipped('SonataUserBundle must be installed to run this test.'); + } + $user = $this->createMock(User::class); $user ->method('getTimezone') @@ -70,7 +102,7 @@ public function testDetectsTimezoneForUser($timezone) $this->assertSame($timezone, $timezoneDetector->getTimezone()); } - public function testTimezoneNotDetected() + public function testTimezoneNotDetected(): void { $storage = $this->createMock(TokenStorageInterface::class);