icanhazstring / symfony-time-machine
Symfony bundle that adds the capability to change the datetime of an application
Fund package maintenance!
Other
icanhazstring
Installs: 1 438
Dependents: 0
Suggesters: 0
Security: 0
Stars: 7
Watchers: 2
Forks: 0
Open Issues: 9
Type:symfony-bundle
Requires
- php: ^8.1
- symfony/clock: ^6.2.x-dev
- symfony/dependency-injection: ^5.4 || ^6.1
- symfony/http-kernel: ^5.4 || ^6.1
Requires (Dev)
- ergebnis/composer-normalize: ^2.28
- phpstan/phpstan: ^1.8.9
- phpunit/phpunit: ^9.5.25
- roave/security-advisories: dev-master
- squizlabs/php_codesniffer: ^3.7.1
- symfony/framework-bundle: ^6.1
- symfony/yaml: ^6.1
- dev-main
- 0.1.1
- 0.1.0
- dev-dependabot/composer/symfony/dependency-injection-tw-6.3.8
- dev-dependabot/composer/symfony/http-kernel-tw-6.3.8
- dev-dependabot/github_actions/shivammathur/setup-php-2.27.1
- dev-dependabot/composer/phpstan/phpstan-tw-1.10.41
- dev-dependabot/composer/phpunit/phpunit-tw-10.4.2
- dev-dependabot/composer/ergebnis/composer-normalize-tw-2.39
- dev-dependabot/github_actions/actions/checkout-4
- dev-dependabot/composer/symfony/clock-tw-6.3.4
This package is auto-updated.
Last update: 2024-12-10 23:49:02 UTC
README
Symfony bundle that adds the capability to change the datetime of an application
Motivation
Writing tests for your application is somewhat nice and easy. But what about
testing your application that heavily is based upon DateTime
objects?
There is symfony/clock
which already provides a ClockInterface
which you can
use to inject a NativeClock
or MockClock
into your services.
But that only works for tests. What if you want to test end-to-end?
You could change the system time of you server, true, but there is an easier way using
this symfony-time-machine
.
Installation
composer require icanhazstring/symfony-time-machine
If the autoregister doesn't work, add the bundle into your config/bundles.php
.
<?php return [ Icanhazstring\SymfonyTimeMachine\TimeMachineBundle => ['all' => true], ];
How it works
This bundle relies on the presence of ClockInterface
as a service in your application.
Therefor you need to have something like this in your services.yaml
:
services: Symfony\Component\Clock\ClockInterface: class: Symfony\Component\Clock\NativeClock
This bundle will set this service to public: true
as this is needed to change the
service on request.
Now everytime you boot the TimeKernel
, it will replace the ClockInterface
with a MockClock
with a parsed datetime string from either a request query/cookie time-machine
or environment
variable TIME_MACHINE
.
Usage
Web usage
For web usage you have to alter your public/index.php
a little.
<?php use App\Kernel; use Icanhazstring\SymfonyTimeMachine\TimeKernel; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (array $context) { $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); (new TimeKernel($kernel))->fromContext($context)->boot(); return $kerneL; };
Console usage
For console usage alter the bin/console
.
return function (array $context) { $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); (new TimeKernel($kernel))->fromContext($context)->boot(); return new Application($kernel); };
Register additional TimeMachineHandler
Sometimes you need to alter some other services in your application along with the time-machine.
For this you can implement services implementing the TimeMachineHandler
interface.
These will be called right after the ClockInterface
was changed in the container.
The TimeKernel
will use the HandlerRegistry
to call TimeMachineHandler::handle()
on all
available services.
For example, you could add the time-machine
query parameter to all openapi paths
in your application, as shown here using api-platform:
final class PathModifier implements OpenApiPathModifier, TimeMachineHandler { public function __construct(private readonly OpenApiFactory $openApiFactory) { } public function handles(string $path, PathItem $pathItem): bool { return true; } public function modify(PathItem $pathItem): PathItem { /** @var array<string, null|Operation> $operations */ $operations = [ 'get' => $pathItem->getGet(), 'post' => $pathItem->getPost(), ]; foreach ($operations as $method => $operation) { if ($operation === null) { continue; } $queryParameters = $operation->getParameters(); $queryParameters[] = new Parameter( name: 'time-machine', in: 'query', description: 'Sets the applications datetime to a specific value.', required: false, schema: ['type' => 'string', 'default' => 'now'] ); $wither = 'with'.ucfirst($method); /** @var PathItem $pathItem */ $pathItem = $pathItem->{$wither}($operation->withParameters($queryParameters)); } return $pathItem; } public function handle(): void { $this->openApiFactory->addModifier($this); } }