Skip to content

Commit 48feceb

Browse files
authored
Add optional $clock parameter to Manager constructor (#275)
1 parent 84795ff commit 48feceb

File tree

7 files changed

+62
-72
lines changed

7 files changed

+62
-72
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
## 2.0.1 under development
44

5-
Bug #264: Fix bug when default roles were not checked in `Manager::userHasPermission()` (@KovYu, @arogachev)
5+
- Bug #264: Fix bug when default roles were not checked in `Manager::userHasPermission()` (@KovYu, @arogachev)
6+
- New #275: Add optional `$clock` parameter to `Manager` constructor to get current time (@vjik)
67

78
## 2.0.0 March 07, 2024
89

composer.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,21 @@
3232
"yiisoft/friendly-exception": "^1.1"
3333
},
3434
"require-dev": {
35-
"ext-uopz": "*",
36-
"maglnet/composer-require-checker": "^4.3",
37-
"phpunit/phpunit": "^10.5.2",
38-
"rector/rector": "^2.0.3",
39-
"roave/infection-static-analysis-plugin": "^1.18",
40-
"slope-it/clock-mock": "^0.4.0",
41-
"spatie/phpunit-watcher": "^1.23",
35+
"maglnet/composer-require-checker": "^4.7.1",
36+
"phpunit/phpunit": "^10.5.45",
37+
"psr/clock": "^1.0",
38+
"rector/rector": "^2.0.10",
39+
"roave/infection-static-analysis-plugin": "^1.35",
40+
"spatie/phpunit-watcher": "^1.24",
4241
"vimeo/psalm": "^5.26.1",
43-
"yiisoft/di": "^1.2"
42+
"yiisoft/di": "^1.3"
4443
},
4544
"suggest": {
4645
"yiisoft/rbac-cycle-db": "For using Cycle as a storage",
4746
"yiisoft/rbac-db": "For using Yii Database as a storage",
4847
"yiisoft/rbac-php": "For using PHP files as a storage",
49-
"yiisoft/rbac-rules-container": "To create rules via Yii Factory"
48+
"yiisoft/rbac-rules-container": "To create rules via Yii Factory",
49+
"psr/clock": "For using custom clock"
5050
},
5151
"autoload": {
5252
"psr-4": {

src/Manager.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Closure;
88
use InvalidArgumentException;
9+
use Psr\Clock\ClockInterface;
910
use RuntimeException;
1011
use Stringable;
1112
use Yiisoft\Access\AccessCheckerInterface;
@@ -30,18 +31,21 @@ final class Manager implements ManagerInterface
3031
/**
3132
* @param ItemsStorageInterface $itemsStorage Items storage.
3233
* @param AssignmentsStorageInterface $assignmentsStorage Assignments storage.
33-
* @param RuleFactoryInterface $ruleFactory Rule factory.
34+
* @param RuleFactoryInterface|null $ruleFactory Rule factory.
3435
* @param bool $enableDirectPermissions Whether to enable assigning permissions directly to user. Prefer assigning
3536
* roles only.
3637
* @param bool $includeRolesInAccessChecks Whether to include roles (in addition to permissions) during access
3738
* checks in {@see Manager::userHasPermission()}.
39+
* @param ClockInterface|null $clock Instance of `ClockInterface` implementation to be used for getting current
40+
* time.
3841
*/
3942
public function __construct(
4043
private readonly ItemsStorageInterface $itemsStorage,
4144
private readonly AssignmentsStorageInterface $assignmentsStorage,
4245
?RuleFactoryInterface $ruleFactory = null,
4346
private readonly bool $enableDirectPermissions = false,
4447
private readonly bool $includeRolesInAccessChecks = false,
48+
private readonly ?ClockInterface $clock = null,
4549
) {
4650
$this->ruleFactory = $ruleFactory ?? new SimpleRuleFactory();
4751
}
@@ -70,7 +74,7 @@ public function userHasPermission(
7074
}
7175

7276
$hierarchy = $this->itemsStorage->getHierarchy($item->getName());
73-
$itemNames = array_map(static fn (array $treeItem): string => $treeItem['item']->getName(), $hierarchy);
77+
$itemNames = array_map(static fn(array $treeItem): string => $treeItem['item']->getName(), $hierarchy);
7478
$userItemNames = $guestRole !== null
7579
? [$guestRole->getName()]
7680
: $this->filterUserItemNames((string) $userId, $itemNames);
@@ -172,7 +176,7 @@ public function assign(string $itemName, int|Stringable|string $userId, ?int $cr
172176
return $this;
173177
}
174178

175-
$assignment = new Assignment($userId, $itemName, $createdAt ?? time());
179+
$assignment = new Assignment($userId, $itemName, $createdAt ?? $this->getCurrentTimestamp());
176180
$this->assignmentsStorage->add($assignment);
177181

178182
return $this;
@@ -391,7 +395,7 @@ private function addItem(Permission|Role $item): void
391395
throw new ItemAlreadyExistsException($item);
392396
}
393397

394-
$time = time();
398+
$time = $this->getCurrentTimestamp();
395399
if (!$item->hasCreatedAt()) {
396400
$item = $item->withCreatedAt($time);
397401
}
@@ -528,4 +532,11 @@ private function filterUserItemNames(string $userId, array $itemNames): array
528532
$this->defaultRoleNames,
529533
);
530534
}
535+
536+
private function getCurrentTimestamp(): int
537+
{
538+
return $this->clock === null
539+
? time()
540+
: $this->clock->now()->getTimestamp();
541+
}
531542
}

tests/Common/AssignmentsStorageTestTrait.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
namespace Yiisoft\Rbac\Tests\Common;
66

7-
use DateTime;
8-
use SlopeIt\ClockMock\ClockMock;
97
use Yiisoft\Rbac\Assignment;
108
use Yiisoft\Rbac\AssignmentsStorageInterface;
119
use Yiisoft\Rbac\Item;
@@ -22,20 +20,12 @@ trait AssignmentsStorageTestTrait
2220

2321
protected function setUp(): void
2422
{
25-
if ($this->name() === 'testAddWithCurrentTimestamp') {
26-
ClockMock::freeze(new DateTime('2023-05-10 08:24:39'));
27-
}
28-
2923
$this->populateItemsStorage();
3024
$this->populateAssignmentsStorage();
3125
}
3226

3327
protected function tearDown(): void
3428
{
35-
if ($this->name() === 'testAddWithCurrentTimestamp') {
36-
ClockMock::reset();
37-
}
38-
3929
$this->getItemsStorage()->clear();
4030
$this->getAssignmentsStorage()->clear();
4131
}
@@ -263,7 +253,7 @@ public function testAddWithCurrentTimestamp(): void
263253
{
264254
$testStorage = $this->getAssignmentsStorageForModificationAssertions();
265255
$actionStorage = $this->getAssignmentsStorage();
266-
$actionStorage->add(new Assignment(userId: 'john', itemName: 'Operator', createdAt: time()));
256+
$actionStorage->add(new Assignment(userId: 'john', itemName: 'Operator', createdAt: 1_683_707_079));
267257

268258
$this->assertEquals(
269259
new Assignment(userId: 'john', itemName: 'Operator', createdAt: 1_683_707_079),

tests/Common/ItemsStorageTestTrait.php

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Yiisoft\Rbac\Tests\Common;
66

77
use DateTime;
8-
use SlopeIt\ClockMock\ClockMock;
8+
use PHPUnit\Framework\Attributes\DataProvider;
99
use Yiisoft\Rbac\Item;
1010
use Yiisoft\Rbac\ItemsStorageInterface;
1111
use Yiisoft\Rbac\Permission;
@@ -25,23 +25,11 @@ trait ItemsStorageTestTrait
2525

2626
protected function setUp(): void
2727
{
28-
if ($this->name() === 'testAddWithCurrentTimestamps') {
29-
ClockMock::freeze(new DateTime('2023-05-10 08:24:39'));
30-
}
31-
32-
if ($this->name() === 'testGetHierarchy') {
33-
ClockMock::freeze(new DateTime('2023-12-24 17:51:18'));
34-
}
35-
3628
$this->populateItemsStorage();
3729
}
3830

3931
protected function tearDown(): void
4032
{
41-
if (in_array($this->name(), ['testAddWithCurrentTimestamps', 'testGetHierarchy'], strict: true)) {
42-
ClockMock::reset();
43-
}
44-
4533
$this->getItemsStorage()->clear();
4634
}
4735

@@ -489,9 +477,7 @@ public static function dataGetHierarchy(): array
489477
];
490478
}
491479

492-
/**
493-
* @dataProvider dataGetHierarchy
494-
*/
480+
#[DataProvider('dataGetHierarchy')]
495481
public function testGetHierarchy(string $name, array $expectedHierarchy): void
496482
{
497483
$this->assertEquals($expectedHierarchy, $this->getItemsStorage()->getHierarchy($name));
@@ -531,7 +517,7 @@ public function testAddWithCurrentTimestamps(): void
531517
{
532518
$testStorage = $this->getItemsStorageForModificationAssertions();
533519

534-
$time = time();
520+
$time = 1_683_707_079;
535521
$newItem = (new Permission('Delete post'))->withCreatedAt($time)->withUpdatedAt($time);
536522

537523
$actionStorage = $this->getItemsStorage();
@@ -765,7 +751,7 @@ protected function getFixtures(): array
765751
'posts.update' => Item::TYPE_PERMISSION,
766752
'posts.delete' => Item::TYPE_PERMISSION,
767753
];
768-
$time = time();
754+
$time = 1703440278;
769755

770756
$items = [];
771757
foreach ($itemsMap as $name => $type) {

tests/Common/ManagerConfigurationTestTrait.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Yiisoft\Rbac\Tests\Common;
66

7+
use DateTimeImmutable;
8+
use Psr\Clock\ClockInterface;
79
use Yiisoft\Rbac\AssignmentsStorageInterface;
810
use Yiisoft\Rbac\ItemsStorageInterface;
911
use Yiisoft\Rbac\Manager;
@@ -21,10 +23,23 @@ protected function createManager(
2123
?AssignmentsStorageInterface $assignmentsStorage = null,
2224
?bool $enableDirectPermissions = null,
2325
?bool $includeRolesInAccessChecks = null,
26+
?DateTimeImmutable $currentDateTime = null,
2427
): ManagerInterface {
2528
$arguments = [
2629
'itemsStorage' => $itemsStorage ?? $this->createItemsStorage(),
2730
'assignmentsStorage' => $assignmentsStorage ?? $this->createAssignmentsStorage(),
31+
'clock' => $currentDateTime === null
32+
? null
33+
: new class ($currentDateTime) implements ClockInterface {
34+
public function __construct(private readonly DateTimeImmutable $dateTime)
35+
{
36+
}
37+
38+
public function now(): DateTimeImmutable
39+
{
40+
return $this->dateTime;
41+
}
42+
},
2843
];
2944
if ($enableDirectPermissions !== null) {
3045
$arguments['enableDirectPermissions'] = $enableDirectPermissions;

tests/Common/ManagerLogicTestTrait.php

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
namespace Yiisoft\Rbac\Tests\Common;
66

77
use Closure;
8-
use DateTime;
8+
use DateTimeImmutable;
99
use InvalidArgumentException;
1010
use RuntimeException;
11-
use SlopeIt\ClockMock\ClockMock;
1211
use Yiisoft\Rbac\Assignment;
1312
use Yiisoft\Rbac\Exception\DefaultRolesNotFoundException;
1413
use Yiisoft\Rbac\Exception\ItemAlreadyExistsException;
@@ -30,22 +29,6 @@
3029

3130
trait ManagerLogicTestTrait
3231
{
33-
private static array $frozenTimeTests = ['testAssign', 'testDataPersistency'];
34-
35-
protected function setUp(): void
36-
{
37-
if (in_array($this->name(), self::$frozenTimeTests, strict: true)) {
38-
ClockMock::freeze(new DateTime('2023-05-10 08:24:39'));
39-
}
40-
}
41-
42-
protected function tearDown(): void
43-
{
44-
if (in_array($this->name(), self::$frozenTimeTests, strict: true)) {
45-
ClockMock::reset();
46-
}
47-
}
48-
4932
public static function dataUserHasPermissionGeneric(): array
5033
{
5134
return [
@@ -543,15 +526,15 @@ public function testHasChild(): void
543526
$this->assertFalse($manager->hasChild('reader', 'createPost'));
544527
}
545528

546-
/**
547-
* Relies on {@see ClockMock} for testing timestamp. When using with other PHPUnit classes / traits, make sure to
548-
* call {@see setUp} and {@see tearDown} methods explicitly.
549-
*/
550529
public function testAssign(): void
551530
{
552531
$itemsStorage = $this->createItemsStorage();
553532
$assignmentsStorage = $this->createAssignmentsStorage();
554-
$manager = $this->createManager($itemsStorage, $assignmentsStorage)
533+
$manager = $this->createManager(
534+
$itemsStorage,
535+
$assignmentsStorage,
536+
currentDateTime: new DateTimeImmutable('2023-05-10 08:24:39')
537+
)
555538
->addRole(new Role('author'))
556539
->addRole(new Role('reader'))
557540
->addRole(new Role('writer'))
@@ -895,12 +878,12 @@ public static function dataSetDefaultRoleNamesException(): array
895878
return [
896879
[['test1', 2, 'test3'], InvalidArgumentException::class, 'Each role name must be a string.'],
897880
[
898-
static fn (): string => 'test',
881+
static fn(): string => 'test',
899882
InvalidArgumentException::class,
900883
'Default role names closure must return an array.',
901884
],
902885
[
903-
static fn (): array => ['test1', 2, 'test3'],
886+
static fn(): array => ['test1', 2, 'test3'],
904887
InvalidArgumentException::class,
905888
'Each role name must be a string.',
906889
],
@@ -925,10 +908,10 @@ public static function dataSetDefaultRoleNames(): array
925908
return [
926909
[['defaultRole1'], ['defaultRole1']],
927910
[['defaultRole1', 'defaultRole2'], ['defaultRole1', 'defaultRole2']],
928-
[static fn (): array => ['defaultRole1'], ['defaultRole1']],
929-
[static fn (): array => ['defaultRole1', 'defaultRole2'], ['defaultRole1', 'defaultRole2']],
911+
[static fn(): array => ['defaultRole1'], ['defaultRole1']],
912+
[static fn(): array => ['defaultRole1', 'defaultRole2'], ['defaultRole1', 'defaultRole2']],
930913
[[], []],
931-
[static fn (): array => [], []],
914+
[static fn(): array => [], []],
932915
];
933916
}
934917

@@ -1111,7 +1094,11 @@ public function testDataPersistency(): void
11111094
{
11121095
$itemsStorage = new FakeItemsStorage();
11131096
$assignmentsStorage = new FakeAssignmentsStorage();
1114-
$manager = $this->createManager($itemsStorage, $assignmentsStorage);
1097+
$manager = $this->createManager(
1098+
$itemsStorage,
1099+
$assignmentsStorage,
1100+
currentDateTime: new DateTimeImmutable('2023-05-10 08:24:39'),
1101+
);
11151102
$manager
11161103
->addRole((new Role('role1'))->withCreatedAt(1_694_502_936)->withUpdatedAt(1_694_502_936))
11171104
->addRole((new Role('role2'))->withCreatedAt(1_694_502_976)->withUpdatedAt(1_694_502_976))

0 commit comments

Comments
 (0)