1
0
mirror of https://github.com/moodle/moodle.git synced 2025-04-23 09:23:09 +02:00

MDL-80838 core: Add PSR-20/Clock support

This commit adds the PSR-20 ClockInterface to core, with a
moodle-specific extension to the Interface at `\core\clock`, and a
standard clock at `\core\system_clock`.

Further clocks are provided as `\incrementing_clock` and `\frozen_clock`
which are available to unit tests using:

- `$this->mock_clock_with_incrementing(?int $starttime = null);`
- `$this->mock_clock_with_frozen(?int $time = null);`

For the incrementing clock, every call to fetch the time will bump the
current time by one second.

For the frozen clock the time will not change, but can be modified with:
- `$clock->set_to(int $time);`; and
- `$clock->bump(int $seconds = 1);`
This commit is contained in:
Andrew Nicols 2024-02-06 13:05:49 +08:00
parent c895def59b
commit 298c13ac3b
No known key found for this signature in database
GPG Key ID: 6D1E3157C8CFBF14
15 changed files with 564 additions and 0 deletions

33
lib/classes/clock.php Normal file

@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
/**
* Moodle Clock interface.
*
* @package core
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface clock extends \Psr\Clock\ClockInterface {
/**
* Return the unix time stamp for the current representation of the time.
*
* @return int
*/
public function time(): int;
}

@ -116,6 +116,7 @@ class core_component {
'lib/psr/http-factory/src',
],
'Psr\\EventDispatcher' => 'lib/psr/event-dispatcher/src',
'Psr\\Clock' => 'lib/psr/clock/src',
'Psr\\Container' => 'lib/psr/container/src',
'GuzzleHttp\\Psr7' => 'lib/guzzlehttp/psr7/src',
'GuzzleHttp\\Promise' => 'lib/guzzlehttp/promises/src',

@ -117,6 +117,11 @@ class di {
// The string manager.
\core_string_manager::class => fn() => get_string_manager(),
// The Moodle Clock implementation, which itself is an extension of PSR-20.
// Alias the PSR-20 clock interface to the Moodle clock. They are compatible.
\core\clock::class => fn() => new \core\system_clock(),
\Psr\Clock\ClockInterface::class => \DI\get(\core\clock::class),
]);
// Add any additional definitions using hooks.

@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
/**
* Standard system clock implementation.
*
* @package core
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_clock implements clock {
public function now(): \DateTimeImmutable {
return new \DateTimeImmutable();
}
public function time(): int {
return $this->now()->getTimestamp();
}
}

@ -729,4 +729,38 @@ abstract class advanced_testcase extends base_testcase {
\core\task\manager::adhoc_task_complete($task);
}
}
/**
* Mock the clock with an incrementing clock.
*
* @param null|int $starttime
* @return \incrementing_clock
*/
public function mock_clock_with_incrementing(
?int $starttime = null,
): \incrementing_clock {
require_once(dirname(__DIR__, 2) . '/testing/classes/incrementing_clock.php');
$clock = new \incrementing_clock($starttime);
\core\di::set(\core\clock::class, $clock);
return $clock;
}
/**
* Mock the clock with a frozen clock.
*
* @param null|int $time
* @return \frozen_clock
*/
public function mock_clock_with_frozen(
?int $time = null,
): \frozen_clock {
require_once(dirname(__DIR__, 2) . '/testing/classes/frozen_clock.php');
$clock = new \frozen_clock($time);
\core\di::set(\core\clock::class, $clock);
return $clock;
}
}

@ -735,4 +735,69 @@ class advanced_test extends \advanced_testcase {
$this->runAdhocTasks();
$this->expectOutputRegex("/Task was run as {$user->id}/");
}
/**
* Test the incrementing mock clock.
*
* @covers ::mock_clock_with_incrementing
* @covers \incrementing_clock
*/
public function test_mock_clock_with_incrementing(): void {
$standard = \core\di::get(\core\clock::class);
$this->assertInstanceOf(\Psr\Clock\ClockInterface::class, $standard);
$this->assertInstanceOf(\core\clock::class, $standard);
$newclock = $this->mock_clock_with_incrementing(0);
$mockedclock = \core\di::get(\core\clock::class);
$this->assertInstanceOf(\incrementing_clock::class, $newclock);
$this->assertSame($newclock, $mockedclock);
// Test the functionality.
$this->assertEquals(0, $mockedclock->now()->getTimestamp());
$this->assertEquals(1, $newclock->now()->getTimestamp());
$this->assertEquals(2, $mockedclock->now()->getTimestamp());
// Specify a specific start time.
$newclock = $this->mock_clock_with_incrementing(12345);
$mockedclock = \core\di::get(\core\clock::class);
$this->assertSame($newclock, $mockedclock);
$this->assertEquals(12345, $mockedclock->now()->getTimestamp());
$this->assertEquals(12346, $newclock->now()->getTimestamp());
$this->assertEquals(12347, $mockedclock->now()->getTimestamp());
$this->assertEquals($newclock->time, $mockedclock->now()->getTimestamp());
}
/**
* Test the incrementing mock clock.
*
* @covers ::mock_clock_with_frozen
* @covers \frozen_clock
*/
public function test_mock_clock_with_frozen(): void {
$standard = \core\di::get(\core\clock::class);
$this->assertInstanceOf(\Psr\Clock\ClockInterface::class, $standard);
$this->assertInstanceOf(\core\clock::class, $standard);
$newclock = $this->mock_clock_with_frozen(0);
$mockedclock = \core\di::get(\core\clock::class);
$this->assertInstanceOf(\frozen_clock::class, $newclock);
$this->assertSame($newclock, $mockedclock);
// Test the functionality.
$initialtime = $mockedclock->now()->getTimestamp();
$this->assertEquals($initialtime, $newclock->now()->getTimestamp());
$this->assertEquals($initialtime, $mockedclock->now()->getTimestamp());
// Specify a specific start time.
$newclock = $this->mock_clock_with_frozen(12345);
$mockedclock = \core\di::get(\core\clock::class);
$this->assertSame($newclock, $mockedclock);
$initialtime = $mockedclock->now();
$this->assertEquals($initialtime, $mockedclock->now());
$this->assertEquals($initialtime, $newclock->now());
$this->assertEquals($initialtime, $mockedclock->now());
}
}

19
lib/psr/clock/LICENSE Normal file

@ -0,0 +1,19 @@
Copyright (c) 2017 PHP Framework Interoperability Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

61
lib/psr/clock/README.md Normal file

@ -0,0 +1,61 @@
# PSR Clock
This repository holds the interface for [PSR-20][psr-url].
Note that this is not a clock of its own. It is merely an interface that
describes a clock. See the specification for more details.
## Installation
```bash
composer require psr/clock
```
## Usage
If you need a clock, you can use the interface like this:
```php
<?php
use Psr\Clock\ClockInterface;
class Foo
{
private ClockInterface $clock;
public function __construct(ClockInterface $clock)
{
$this->clock = $clock;
}
public function doSomething()
{
/** @var DateTimeImmutable $currentDateAndTime */
$currentDateAndTime = $this->clock->now();
// do something useful with that information
}
}
```
You can then pick one of the [implementations][implementation-url] of the interface to get a clock.
If you want to implement the interface, you can require this package and
implement `Psr\Clock\ClockInterface` in your code.
Don't forget to add `psr/clock-implementation` to your `composer.json`s `provide`-section like this:
```json
{
"provide": {
"psr/clock-implementation": "1.0"
}
}
```
And please read the [specification text][specification-url] for details on the interface.
[psr-url]: https://www.php-fig.org/psr/psr-20
[package-url]: https://packagist.org/packages/psr/clock
[implementation-url]: https://packagist.org/providers/psr/clock-implementation
[specification-url]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-20-clock.md

@ -0,0 +1,12 @@
# PSR-20 Clock
## Installation
1. Visit https://github.com/php-fig/clock
2. Download the latest release
3. Unzip in this folder
4. Update `thirdpartylibs.xml`
5. Remove any unnecessary files, including:
- Any tests
- CHANGELOG.md
- composer.json

@ -0,0 +1,13 @@
<?php
namespace Psr\Clock;
use DateTimeImmutable;
interface ClockInterface
{
/**
* Returns the current time as a DateTimeImmutable Object
*/
public function now(): DateTimeImmutable;
}

@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Frozen clock for testing purposes.
*
* @package core
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @property-read \DateTimeImmutable $time The current time of the clock
*/
class frozen_clock implements \core\clock {
/** @var DateTimeImmutable The next time of the clock */
public DateTimeImmutable $time;
/**
* Create a new instance of the frozen clock.
*
* @param null|int $time The initial time to use. If not specified, the current time is used.
*/
public function __construct(
?int $time = null,
) {
if ($time) {
$this->time = new \DateTimeImmutable("@{$time}");
} else {
$this->time = new \DateTimeImmutable();
}
}
public function now(): \DateTimeImmutable {
return $this->time;
}
public function time(): int {
return $this->time->getTimestamp();
}
/**
* Set the time of the clock.
*
* @param int $time
*/
public function set_to(int $time): void {
$this->time = new \DateTimeImmutable("@{$time}");
}
/**
* Bump the time by a number of seconds.
*
* @param int $seconds
*/
public function bump(int $seconds = 1): void {
$this->time = $this->time->modify("+{$seconds} seconds");
}
}

@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Incrementing clock for testing purposes.
*
* @package core
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @property-read int $time The current time of the clock
*/
class incrementing_clock implements \core\clock {
/** @var int The next time of the clock */
public int $time;
/**
* Create a new instance of the incrementing clock.
*
* @param null|int $starttime The initial time to use. If not specified, the current time is used.
*/
public function __construct(
?int $starttime = null,
) {
$this->time = $starttime ?? time();
}
public function now(): \DateTimeImmutable {
return new \DateTimeImmutable('@' . $this->time++);
}
public function time(): int {
return $this->now()->getTimestamp();
}
/**
* Set the time of the clock.
*
* @param int $time
*/
public function set_to(int $time): void {
$this->time = $time;
}
/**
* Bump the time by a number of seconds.
*
* Note: The act of fetching the time will also bump the time by one second.
*
* @param int $seconds
*/
public function bump(int $seconds = 1): void {
$this->time += $seconds;
}
}

@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
use frozen_clock;
use incrementing_clock;
/**
* Tests for testing clocks.
*
* @package core
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class clock_test extends \advanced_testcase {
/**
* Test the incrementing mock clock.
*
* @covers \incrementing_clock
*/
public function test_clock_with_incrementing(): void {
require_once(__DIR__ . '/../classes/incrementing_clock.php');
$clock = new incrementing_clock();
$this->assertInstanceOf(\incrementing_clock::class, $clock);
$initialtime = $clock->now()->getTimestamp();
// Test the functionality.
$this->assertEquals($initialtime + 1, $clock->now()->getTimestamp());
$this->assertEquals($initialtime + 2, $clock->time());
$this->assertEquals($initialtime + 3, $clock->now()->getTimestamp());
// Specify a specific start time.
$clock = new incrementing_clock(12345);
$this->assertEquals(12345, $clock->now()->getTimestamp());
$this->assertEquals(12346, $clock->time());
$this->assertEquals(12347, $clock->now()->getTimestamp());
$clock->set_to(12345);
$this->assertEquals(12345, $clock->time());
$this->assertEquals(12346, $clock->time());
$clock->bump();
$this->assertEquals(12348, $clock->time());
$clock->bump();
$this->assertEquals(12350, $clock->time());
$clock->bump(5);
$this->assertEquals(12356, $clock->time());
}
/**
* Test the incrementing mock clock.
*
* @covers \frozen_clock
*/
public function test_mock_clock_with_frozen(): void {
require_once(__DIR__ . '/../classes/frozen_clock.php');
$clock = new frozen_clock();
// Test the functionality.
$initialtime = $clock->now()->getTimestamp();
$this->assertEquals($initialtime, $clock->now()->getTimestamp());
$this->assertEquals($initialtime, $clock->now()->getTimestamp());
$this->assertEquals($initialtime, $clock->now()->getTimestamp());
$this->assertEquals($initialtime, $clock->time());
// Specify a specific start time.
$clock = new frozen_clock(12345);
$initialtime = $clock->now();
$this->assertEquals($initialtime, $clock->now());
$this->assertEquals($initialtime, $clock->now());
$this->assertEquals($initialtime, $clock->now());
$clock->set_to(12345);
$this->assertEquals(12345, $clock->now()->getTimestamp());
$this->assertEquals(12345, $clock->now()->getTimestamp());
$this->assertEquals(12345, $clock->now()->getTimestamp());
$this->assertEquals(12345, $clock->time());
$clock->bump();
$this->assertEquals(12346, $clock->time());
$clock->bump();
$this->assertEquals(12347, $clock->time());
$clock->bump(5);
$this->assertEquals(12352, $clock->time());
}
}

@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
/**
* Tests for the standard ClockInterface implementation.
*
* @package core
* @category test
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core\system_clock
*/
final class system_clock_test extends \advanced_testcase {
public function test_now(): void {
$starttime = time();
$clock = new system_clock();
$now = $clock->now();
$this->assertInstanceOf(\DateTimeImmutable::class, $now);
$this->assertGreaterThanOrEqual($starttime, $now->getTimestamp());
}
}

@ -612,6 +612,14 @@ All rights reserved.</copyright>
<license>MIT</license>
<repository>https://github.com/php-fig/container</repository>
</library>
<library>
<location>psr/clock</location>
<name>clock</name>
<description>Clock Interface (PHP FIG PSR-20).</description>
<version>1.0.0</version>
<license>MIT</license>
<repository>https://github.com/php-fig/clock</repository>
</library>
<library>
<location>psr/http-client</location>
<name>http-client</name>