Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TimezoneAwareInterface #300

Merged
merged 1 commit into from
May 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ jobs:
- uses: actions/checkout@master
- name: PHPStan
uses: "docker://oskarstark/phpstan-ga"
env:
REQUIRE_DEV: true
phansys marked this conversation as resolved.
Show resolved Hide resolved
with:
args: analyse
33 changes: 31 additions & 2 deletions UPGRADE-2.x.md
Original file line number Diff line number Diff line change
@@ -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).
3 changes: 0 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
11 changes: 11 additions & 0 deletions docs/reference/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
21 changes: 7 additions & 14 deletions src/DependencyInjection/SonataIntlExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}

Expand All @@ -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 {
Expand Down
22 changes: 22 additions & 0 deletions src/Timezone/TimezoneAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* 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 <phansys@gmail.com>
*/
interface TimezoneAwareInterface
phansys marked this conversation as resolved.
Show resolved Hide resolved
{
public function getTimezone(): ?string;
}
32 changes: 32 additions & 0 deletions src/Timezone/TimezoneAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* 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 <phansys@gmail.com>
*/
trait TimezoneAwareTrait
{
/**
* @var string|null
*/
private $timezone;

final public function getTimezone(): ?string
{
return $this->timezone;
}
}
2 changes: 1 addition & 1 deletion src/Timezone/TimezoneDetectorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface TimezoneDetectorInterface
/**
* Get the appropriate timezone.
*
* @return string
* @return string|null
*/
public function getTimezone();
}
18 changes: 16 additions & 2 deletions src/Timezone/UserBasedTimezoneDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
60 changes: 48 additions & 12 deletions tests/Timezone/UserBasedTimezoneDetectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,75 @@
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;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

/**
* @author Emmanuel Vella <vella.emmanuel@gmail.com>
*/
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'],
[null],
];
}

/**
* @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
*
* @expectedDeprecation Timezone inference based on the "Sonata\UserBundle\Model\User" class is deprecated since sonata-project/intl-bundle 2.x and will be dropped in 3.0 version. Implement "Sonata\IntlBundle\Timezone\TimezoneAwareInterface" explicitly in your user class instead.
*/
public function testDetectsTimezoneForUser($timezone)
public function testDetectsTimezoneForUser(?string $timezone): void
{
if (!class_exists(User::class)) {
$this->markTestSkipped(sprintf(
'"%s" class must be available to run this test. You should install sonata-project/user-bundle.',
User::class
));
}

$user = $this->createMock(User::class);
$user
->method('getTimezone')
Expand All @@ -70,7 +106,7 @@ public function testDetectsTimezoneForUser($timezone)
$this->assertSame($timezone, $timezoneDetector->getTimezone());
}

public function testTimezoneNotDetected()
public function testTimezoneNotDetected(): void
{
$storage = $this->createMock(TokenStorageInterface::class);

Expand Down