diff --git a/docs/cli.md b/docs/cli.md index 43ba954c7..cfb980a88 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,6 +1,8 @@ # CLI This library provides a `cli` to manage the `event-sourcing` functionalities. +The commands are [symfony console](https://symfony.com/doc/current/components/console.html) commands, +so you can register them in any symfony console application. You can: @@ -10,51 +12,342 @@ You can: * Inspect your `aggregates`, `events` and `subscribers` * Migrate events from one `store` to another +:::note +The examples on this page use `bin/console` as the entry point. +How to build such a cli file is shown in the [CLI example](#cli-example). +::: + ## Database commands -There are two commands for creating and deleting a database. +The database commands create and delete the database configured in your doctrine connection. +They are mostly useful for local development and test environments. + +### Create database + +The `event-sourcing:database:create` command creates the database. + +```bash +bin/console event-sourcing:database:create +``` +With the `--if-not-exists` option, the command does not fail if the database already exists. + +```bash +bin/console event-sourcing:database:create --if-not-exists +``` +### Drop database + +The `event-sourcing:database:drop` command deletes the database. +Without the `--force` option, the command only prints which database would be dropped. + +```bash +bin/console event-sourcing:database:drop --force +``` +With the `--if-exists` option, the command does not fail if the database does not exist. -* DatabaseCreateCommand: `event-sourcing:database:create` -* DatabaseDropCommand: `event-sourcing:database:drop` +:::danger +Dropping the database deletes all your events. They cannot be restored. +::: ## Schema commands -The database schema can also be created, updated and dropped. +The schema commands manage the tables the library needs, +for example the event store table and the subscription store table. +They use the [schema director](store.md#schema), which knows all tables of the configured stores. -* SchemaCreateCommand: `event-sourcing:schema:create` -* SchemaUpdateCommand: `event-sourcing:schema:update` -* SchemaDropCommand: `event-sourcing:schema:drop` +All schema commands support the `--dry-run` option. +Instead of executing anything, the command prints the SQL queries it would run, +so you can review the changes before applying them. -:::note -You can also register doctrine migration commands. +```bash +bin/console event-sourcing:schema:update --dry-run +``` +### Create schema + +The `event-sourcing:schema:create` command creates all tables. +Use it once when you set up a new database. + +```bash +bin/console event-sourcing:schema:create +``` +### Update schema + +The `event-sourcing:schema:update` command compares the existing tables with the expected schema +and applies the differences, for example after a library update. +Without the `--force` option, the command does nothing. + +```bash +bin/console event-sourcing:schema:update --force +``` +### Drop schema + +The `event-sourcing:schema:drop` command deletes all tables of the library. +Without the `--force` option, the command does nothing. + +```bash +bin/console event-sourcing:schema:drop --force +``` +:::danger +Dropping the schema deletes all your events. They cannot be restored. +::: + +:::tip +In production, we recommend [doctrine migrations](#doctrine-migrations) instead of the schema commands, +so that every schema change is versioned and reviewed. ::: ## Subscription commands -To manage your subscriptions there are the following cli commands. +The subscription commands control the [subscription engine](subscription.md). +They move [subscriptions](subscription.md) through their lifecycle and show their current status. -* SubscriptionBootCommand: `event-sourcing:subscription:boot` -* SubscriptionPauseCommand: `event-sourcing:subscription:pause` -* SubscriptionReactivateCommand: `event-sourcing:subscription:reactivate` -* SubscriptionRefreshCommand: `event-sourcing:subscription:refresh` -* SubscriptionRemoveCommand: `event-sourcing:subscription:remove` -* SubscriptionRunCommand: `event-sourcing:subscription:run` -* SubscriptionSetupCommand: `event-sourcing:subscription:setup` -* SubscriptionStatusCommand: `event-sourcing:subscription:status` -* SubscriptionTeardownCommand: `event-sourcing:subscription:teardown` +### Filter subscriptions +Most subscription commands can be limited to certain subscriptions. +Use `--id` to select subscriptions by their subscriber ID and `--group` to select them by their group. +Both options can be passed multiple times. + +```bash +bin/console event-sourcing:subscription:boot --id=profile_1 --id=welcome_email +bin/console event-sourcing:subscription:run --group=projector +``` :::note -You can find out more about [subscriptions](subscription.md). +Ids are combined with `OR`, groups are combined with `OR`, and both options together are combined with `AND`. +Without any filter, the command applies to all subscriptions. +::: + +### Setup + +The `event-sourcing:subscription:setup` command sets up all new subscriptions. +The subscription engine calls the `setup` method of the subscriber, if available, +for example to create the table of a projection. +Afterwards the subscription is booting or, depending on the run mode, already active. + +```bash +bin/console event-sourcing:subscription:setup +``` +With `--skip-booting`, the subscriptions skip the booting status and become active right away. +They then catch up with the event store in the [run command](#run) instead of the boot command. + +### Boot + +The `event-sourcing:subscription:boot` command lets all booting subscriptions catch up with the event store. +After that, they are active, or finished for subscribers with `RunMode::Once`. +The command stops as soon as all subscriptions have caught up. + +```bash +bin/console event-sourcing:subscription:boot +``` +With `--setup`, the command first sets up new subscriptions, so you can do both steps in one call. + +```bash +bin/console event-sourcing:subscription:boot --setup +``` +The command processes the events in chunks. The `--message-limit` option (default `1000`) +defines how many events are processed per run. +If the boot is aborted by a [worker limit](#worker-options) before all subscriptions caught up, +the command exits with code `1`. + +### Run + +The `event-sourcing:subscription:run` command is the long-running worker. +It keeps the active subscriptions up to date by processing new events as they are recorded. + +```bash +bin/console event-sourcing:subscription:run +``` +By default, the command looks for new events every second (`--sleep=1000`) +and processes up to 100 events per run (`--message-limit=100`). +If your store supports it (`DoctrineDbalStore` and `StreamDoctrineDbalStore` with PostgreSQL), +the worker does not poll but waits for a notification from the database, so new events are processed right away. + +:::warning +The run command only handles active subscriptions. +Subscriptions that are new or booting have to be set up and booted first. +After adding a new subscriber, run the setup and boot step and restart the run command. +::: + +### Worker options + +The boot and run commands are workers and support the following options: + +| Option | Description | +|-------------------|-----------------------------------------------------------------------------| +| `--run-limit` | Stop the worker after this number of runs. | +| `--message-limit` | How many events are processed per run. | +| `--memory-limit` | Stop the worker if it uses more memory than this, e.g. `250MB`. | +| `--time-limit` | Stop the worker after this number of seconds. | +| `--sleep` | How many milliseconds the worker waits between two runs. | + +```bash +bin/console event-sourcing:subscription:run --memory-limit=250MB --time-limit=3600 +``` +:::tip +Use the memory and time limits together with a process manager like supervisor or systemd, +which restarts the worker after it stopped. This prevents memory leaks from piling up +and makes sure that a deployment is picked up. +::: + +### Status + +The `event-sourcing:subscription:status` command shows a table of all subscriptions +with their group, run mode, position, status and error message. +Use it to check whether your subscriptions are up to date or have an error. + +```bash +bin/console event-sourcing:subscription:status +bin/console event-sourcing:subscription:status --group=projector +``` +Pass a subscription ID to show the details of one subscription. +If the subscription has an error, the command also prints the error message and the stack trace. + +```bash +bin/console event-sourcing:subscription:status profile_1 +``` +### Pause + +The `event-sourcing:subscription:pause` command pauses subscriptions. +The subscription engine no longer processes events for them until they are reactivated. +This is useful, for example, if a third party system used by a processor is down. + +```bash +bin/console event-sourcing:subscription:pause --id=welcome_email +``` +### Reactivate + +The `event-sourcing:subscription:reactivate` command reactivates paused, finished, detached, +error or failed subscriptions. The subscription then continues from its current position. +A subscription with an error returns to the status it had before the error, +all others become active. +Use it after you fixed the cause of an error. + +```bash +bin/console event-sourcing:subscription:reactivate --id=welcome_email +``` +:::note +Only subscriptions whose subscriber exists in the code can be reactivated. +::: + +### Refresh + +The `event-sourcing:subscription:refresh` command updates the stored subscriptions +if you changed the metadata of a subscriber in the code, for example the run mode or the group. +The position and status of the subscriptions stay untouched. + +```bash +bin/console event-sourcing:subscription:refresh +``` +:::note +This command needs a subscription engine that supports refreshing, like the `DefaultSubscriptionEngine`. +::: + +### Teardown + +The `event-sourcing:subscription:teardown` command cleans up detached subscriptions. +A subscription is detached if its subscriber no longer exists in the code, +for example because you deleted it or created a new version with a new subscriber ID. +The subscription engine calls the `teardown` method or runs the cleanup tasks of the subscriber, if available, +and removes the subscription afterwards. + +```bash +bin/console event-sourcing:subscription:teardown +``` +:::note +Without [cleanup tasks](subscription.md#cleanup), a teardown is only possible if the code of the subscriber still exists. +Otherwise, the subscription stays detached and you can remove it with the [remove command](#remove). +::: + +### Remove + +The `event-sourcing:subscription:remove` command removes subscriptions regardless of their status. +The subscription engine tries to call the `teardown` method, if available, +but removes the subscription even if this fails. +If the subscriber still exists, the subscription is new again and can be set up and booted from scratch. +This is the way to rebuild a projection. + +```bash +bin/console event-sourcing:subscription:remove --id=profile_1 +``` +Without any filter, the command asks for confirmation before it removes all subscriptions. +Use `--force` to skip the question. + +:::danger +Removing a subscription deletes the data of its projection, if the subscriber has a `teardown` method. +::: + +### Typical workflow + +On every deployment, stop the old run command, set up and boot new subscriptions and start the run command again. +Once the old version of your application no longer serves requests, clean up the outdated subscriptions. + +```bash +bin/console event-sourcing:subscription:boot --setup +# start bin/console event-sourcing:subscription:run +# wait until the old application is gone +bin/console event-sourcing:subscription:teardown +``` +If a subscription has an error, look at the details and reactivate it after you fixed the problem. + +```bash +bin/console event-sourcing:subscription:status welcome_email +bin/console event-sourcing:subscription:reactivate --id=welcome_email +``` +:::note +You can find out more about the [subscription lifecycle](subscription.md#subscription-status). ::: ## Inspector commands -The inspector is a tool to inspect the event streams. +The inspector commands display the events in your store. +They help you to debug your application and to understand what happened in an aggregate. + +### Show events -* ShowCommand: `event-sourcing:show` -* ShowAggregateCommand: `event-sourcing:show-aggregate` -* WatchCommand: `event-sourcing:watch` +The `event-sourcing:show` command shows the latest events of the store, newest first. +After each page, it asks whether to show the next events. +```bash +bin/console event-sourcing:show +``` +| Option | Description | +|-------------|-----------------------------------------------------------------------------| +| `--limit` | How many events are displayed per page (default `10`, `0` shows all). | +| `--forward` | Start with the oldest event instead of the newest. | +| `--stream` | Only show events of the given stream, wildcards like `profile-*` are allowed. | + +```bash +bin/console event-sourcing:show --stream="profile-*" --limit=20 +``` +:::note +The `--stream` option needs the [StreamDoctrineDbalStore](store.md#streamdoctrinedbalstore). +::: + +### Show aggregate + +The `event-sourcing:show-aggregate` command shows all events of one aggregate. +Pass the aggregate name and the aggregate ID as arguments. +If you leave them out, the command asks for them. + +```bash +bin/console event-sourcing:show-aggregate profile 018d6a1c-5f2b-7f3e-9c4a-2b6a1f0e8d7c +``` +### Watch events + +The `event-sourcing:watch` command prints new events live as they are saved. +It is a worker like the subscription run command and supports the same +[worker options](#worker-options), except `--message-limit`. + +```bash +bin/console event-sourcing:watch +``` +You can limit the output to one stream, or to an aggregate name and aggregate ID. +The stream and the aggregate options cannot be combined. +The `--stream` option needs the [StreamDoctrineDbalStore](store.md#streamdoctrinedbalstore), +the `--aggregate` and `--aggregate-id` options need the `DoctrineDbalStore`. + +```bash +bin/console event-sourcing:watch --stream="profile-*" +bin/console event-sourcing:watch --aggregate=profile --aggregate-id=018d6a1c-5f2b-7f3e-9c4a-2b6a1f0e8d7c +``` ## Debug command The debug command prints everything the library knows about your application: @@ -143,9 +436,14 @@ A cli php file can look like this: use Doctrine\DBAL\Connection; use Patchlevel\EventSourcing\Console\Command; use Patchlevel\EventSourcing\Console\DoctrineHelper; +use Patchlevel\EventSourcing\Message\Serializer\HeadersSerializer; +use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry; +use Patchlevel\EventSourcing\Metadata\Event\EventRegistry; use Patchlevel\EventSourcing\Schema\DoctrineSchemaDirector; +use Patchlevel\EventSourcing\Serializer\EventSerializer; use Patchlevel\EventSourcing\Store\Store; use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngine; +use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberAccessorRepository; use Symfony\Component\Console\Application; $cli = new Application('Event-Sourcing CLI'); @@ -159,25 +457,42 @@ $doctrineHelper = new DoctrineHelper(); */ $schemaDirector = new DoctrineSchemaDirector($connection, $store); -/** @var SubscriptionEngine $subscriptionEngine */ +/** + * @var SubscriptionEngine $subscriptionEngine + * @var EventSerializer $eventSerializer + * @var HeadersSerializer $headersSerializer + * @var AggregateRootRegistry $aggregateRootRegistry + * @var EventRegistry $eventRegistry + * @var SubscriberAccessorRepository $subscriberAccessorRepository + */ $cli->addCommands([ new Command\DatabaseCreateCommand($connection, $doctrineHelper), new Command\DatabaseDropCommand($connection, $doctrineHelper), + new Command\SchemaCreateCommand($schemaDirector), + new Command\SchemaDropCommand($schemaDirector), + new Command\SchemaUpdateCommand($schemaDirector), + new Command\SubscriptionSetupCommand($subscriptionEngine), new Command\SubscriptionBootCommand($subscriptionEngine), - new Command\SubscriptionPauseCommand($subscriptionEngine), new Command\SubscriptionRunCommand($subscriptionEngine, $store), + new Command\SubscriptionStatusCommand($subscriptionEngine), + new Command\SubscriptionPauseCommand($subscriptionEngine), + new Command\SubscriptionReactivateCommand($subscriptionEngine), + new Command\SubscriptionRefreshCommand($subscriptionEngine), new Command\SubscriptionTeardownCommand($subscriptionEngine), new Command\SubscriptionRemoveCommand($subscriptionEngine), - new Command\SubscriptionReactivateCommand($subscriptionEngine), - new Command\SubscriptionSetupCommand($subscriptionEngine), - new Command\SubscriptionStatusCommand($subscriptionEngine), - new Command\SchemaCreateCommand($schemaDirector), - new Command\SchemaDropCommand($schemaDirector), - new Command\SchemaUpdateCommand($schemaDirector), + new Command\ShowCommand($store, $eventSerializer, $headersSerializer), + new Command\ShowAggregateCommand($store, $eventSerializer, $headersSerializer, $aggregateRootRegistry), + new Command\WatchCommand($store, $eventSerializer, $headersSerializer), + new Command\DebugCommand($aggregateRootRegistry, $eventRegistry, $subscriberAccessorRepository), ]); $cli->run(); ``` +:::note +The `SubscriptionBootCommand` and `SubscriptionRunCommand` accept a PSR-14 event dispatcher as an optional last argument. +It receives the worker events, for example to reset services between two runs. +::: + ### Doctrine Migrations If you want to use doctrine migrations, you can register the commands like this: @@ -233,3 +548,4 @@ Here you can find more information on how to * [How to configure store](store.md) * [How to configure subscription engine](subscription.md) +* [How to use subscriptions](subscription.md#usage) diff --git a/docs/message.md b/docs/message.md index fbd0f5324..1108e726b 100644 --- a/docs/message.md +++ b/docs/message.md @@ -86,7 +86,7 @@ $message->header(EventIdHeader::class)->eventId; // 'a4a4a4a4-4a4a-...' ``` :::warning The `PlayheadHeader` is only added if the stream is playhead based. -Streams that are written without a playhead, for example custom streams, +Streams that are written without a playhead, for example [custom streams](store.md#custom-streams), do not have this header. Use `hasHeader` before you access it. ::: diff --git a/docs/store.md b/docs/store.md index e59a6eced..9a7b33270 100644 --- a/docs/store.md +++ b/docs/store.md @@ -494,6 +494,91 @@ The store locks the table for writing during each save by default. Use the transactional method if you want to call multiple save methods in one transaction. ::: +#### Custom streams + +The `StreamDoctrineDbalStore` is not limited to aggregates. +You can save any event into your own stream, for example integration events +that are derived from your domain events and consumed by other bounded contexts. +For this, the message needs a `StreamNameHeader` with the name of the stream. + +```php +use Patchlevel\EventSourcing\Attribute\Event; + +#[Event('hotel.integration.guest_checked_in')] +final class GuestCheckedInIntegrationEvent +{ + public function __construct( + public readonly string $hotelId, + public readonly string $guestName, + ) { + } +} +``` +```php +use Patchlevel\EventSourcing\Attribute\Processor; +use Patchlevel\EventSourcing\Attribute\Subscribe; +use Patchlevel\EventSourcing\Message\Message; +use Patchlevel\EventSourcing\Store\Header\StreamNameHeader; +use Patchlevel\EventSourcing\Store\Store; + +#[Processor('hotel_integration_publisher')] +final class HotelIntegrationPublisher +{ + public function __construct( + private readonly Store $store, + ) { + } + + #[Subscribe(GuestIsCheckedIn::class)] + public function onGuestIsCheckedIn(GuestIsCheckedIn $event): void + { + $this->store->save( + Message::create(new GuestCheckedInIntegrationEvent( + $event->hotelId->toString(), + $event->guestName, + ))->withHeader(new StreamNameHeader('integration-hotel')), + ); + } +} +``` +These messages are part of the store like any other message. +Every [subscriber](subscription.md) can subscribe to them, +and you can load them with the `StreamCriterion`. + +```php +use Patchlevel\EventSourcing\Store\Criteria\Criteria; +use Patchlevel\EventSourcing\Store\Criteria\StreamCriterion; +use Patchlevel\EventSourcing\Store\Store; + +/** @var Store $store */ +$stream = $store->load( + new Criteria(new StreamCriterion('integration-hotel')), +); +``` +Only the `StreamNameHeader` is required, the other headers are optional: + +| Header | If missing | +|--------------------|-------------------------------------------------| +| `StreamNameHeader` | A `MissingDataForStorage` exception is thrown | +| `PlayheadHeader` | The playhead is saved as `null` | +| `EventIdHeader` | A new UUIDv7 is generated | +| `RecordedOnHeader` | The current time of the clock is used | + +:::warning +Choose a stream name that cannot collide with your aggregate streams, which are named +like `profile-e3e3e3e3-3e3e-3e3e-3e3e-3e3e3e3e3e3e` by default. A prefix like `integration-` helps. +::: + +:::note +Without a `PlayheadHeader` there is no optimistic locking for this stream, +because the unique index on `stream` and `playhead` does not apply to `null` values. +::: + +:::note +Custom streams are only supported by the `StreamDoctrineDbalStore`. +The event class still needs the `#[Event]` attribute and must be known by the event serializer. +::: + ### Update It is not possible to update events. diff --git a/src/Console/Command/SubscriptionStatusCommand.php b/src/Console/Command/SubscriptionStatusCommand.php index 42176d167..648de136a 100644 --- a/src/Console/Command/SubscriptionStatusCommand.php +++ b/src/Console/Command/SubscriptionStatusCommand.php @@ -6,6 +6,7 @@ use Patchlevel\EventSourcing\Console\InputHelper; use Patchlevel\EventSourcing\Console\OutputStyle; +use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngineCriteria; use Patchlevel\EventSourcing\Subscription\Store\SubscriptionNotFound; use Patchlevel\EventSourcing\Subscription\Subscription; use Patchlevel\EventSourcing\Subscription\SubscriptionError; @@ -41,9 +42,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new OutputStyle($input, $output); $id = InputHelper::nullableString($input->getArgument('id')); - $subscriptions = $this->engine->subscriptions(); if ($id === null) { + $subscriptions = $this->engine->subscriptions($this->subscriptionEngineCriteria($input)); + $io->table( [ 'id', @@ -69,7 +71,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - $subscription = $this->findSubscription($subscriptions, $id); + $subscription = $this->findSubscription( + $this->engine->subscriptions(new SubscriptionEngineCriteria([$id])), + $id, + ); $io->horizontalTable( [ diff --git a/src/Subscription/Engine/GapResolverStoreMessageLoader.php b/src/Subscription/Engine/GapResolverStoreMessageLoader.php index 1eb809944..8b628b972 100644 --- a/src/Subscription/Engine/GapResolverStoreMessageLoader.php +++ b/src/Subscription/Engine/GapResolverStoreMessageLoader.php @@ -66,7 +66,7 @@ private function generator(int $startIndex, int $retry = 0): Generator } $retry = 0; - $currentIndex++; + $currentIndex = $stream->index() + 1; yield $stream->index() => $message; } diff --git a/tests/Integration/Subscription/EventFilteredStoreMessageLoaderTest.php b/tests/Integration/Subscription/EventFilteredStoreMessageLoaderTest.php new file mode 100644 index 000000000..35642ceac --- /dev/null +++ b/tests/Integration/Subscription/EventFilteredStoreMessageLoaderTest.php @@ -0,0 +1,275 @@ +connection = DbalManager::createConnection(); + } + + public function tearDown(): void + { + $this->connection->close(); + } + + public function testLoadOnlySubscribedEvents(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jim')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(3)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-2')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new EventFilteredStoreMessageLoader( + $store, + new AttributeEventMetadataFactory(), + new MetadataSubscriberAccessorRepository([new ProfileProjection($this->connection)]), + ); + + $stream = $loader->load(0, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([1, 4], $indexes); + } + + public function testLoadOnlySubscribedEventsFromStartIndex(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-2')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new EventFilteredStoreMessageLoader( + $store, + new AttributeEventMetadataFactory(), + new MetadataSubscriberAccessorRepository([new ProfileProjection($this->connection)]), + ); + + $stream = $loader->load(1, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([3], $indexes); + } + + public function testLoadSubscribedEventsOfMultipleSubscriptions(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new AdminPromoted(ProfileId::generate())) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(3)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new EventFilteredStoreMessageLoader( + $store, + new AttributeEventMetadataFactory(), + new MetadataSubscriberAccessorRepository([ + new ProfileProjection($this->connection), + new LookupSubscriber($this->connection), + ]), + ); + + $stream = $loader->load(0, [new Subscription('profile_1'), new Subscription('lookup')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([1, 3], $indexes); + } + + public function testLoadAllEventsIfSubscriberListensToAll(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new AdminPromoted(ProfileId::generate())) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(3)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new EventFilteredStoreMessageLoader( + $store, + new AttributeEventMetadataFactory(), + new MetadataSubscriberAccessorRepository([ + new ProfileProjection($this->connection), + new ErrorProducerSubscriber(), + ]), + ); + + $stream = $loader->load(0, [new Subscription('profile_1'), new Subscription('error_producer')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([1, 2, 3], $indexes); + } + + public function testLastIndexIgnoresSubscribedEvents(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new EventFilteredStoreMessageLoader( + $store, + new AttributeEventMetadataFactory(), + new MetadataSubscriberAccessorRepository([new ProfileProjection($this->connection)]), + ); + + self::assertSame(2, $loader->lastIndex()); + } + + public function testLastIndexOnEmptyStore(): void + { + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $loader = new EventFilteredStoreMessageLoader( + $store, + new AttributeEventMetadataFactory(), + new MetadataSubscriberAccessorRepository([new ProfileProjection($this->connection)]), + ); + + self::assertSame(0, $loader->lastIndex()); + } +} diff --git a/tests/Integration/Subscription/GapResolverStoreMessageLoaderTest.php b/tests/Integration/Subscription/GapResolverStoreMessageLoaderTest.php new file mode 100644 index 000000000..165c614d3 --- /dev/null +++ b/tests/Integration/Subscription/GapResolverStoreMessageLoaderTest.php @@ -0,0 +1,239 @@ +connection = DbalManager::createConnection(); + } + + public function tearDown(): void + { + $this->connection->close(); + } + + public function testLoadAllEventsWithoutGaps(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-2')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new GapResolverStoreMessageLoader( + $store, + new FrozenClock($recordedOn), + [0, 0], + ); + + $stream = $loader->load(1, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([2, 3], $indexes); + } + + public function testEventWrittenIntoGapIsNotSkipped(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + config: ['keep_index' => true], + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(1)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(2)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-4')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(4)), + ); + + $loader = new GapResolverStoreMessageLoader( + $store, + new FrozenClock($recordedOn), + [0, 0], + ); + + $stream = $loader->load(0, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + + if ($stream->index() !== 2) { + continue; + } + + // simulates a transaction that commits after the stream was loaded + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'Late')) + ->withHeader(new StreamNameHeader('profile-3')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(3)), + ); + } + + self::assertSame([1, 2, 3, 4], $indexes); + } + + public function testPermanentGapIsAcceptedWithoutDuplicates(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + config: ['keep_index' => true], + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(1)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(2)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-4')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(4)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Anna')) + ->withHeader(new StreamNameHeader('profile-5')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(5)), + ); + + $loader = new GapResolverStoreMessageLoader( + $store, + new FrozenClock($recordedOn), + [0, 0], + ); + + $stream = $loader->load(0, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([1, 2, 4, 5], $indexes); + } + + public function testLastIndex(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + config: ['keep_index' => true], + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(1)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)) + ->withHeader(new IndexHeader(5)), + ); + + $loader = new GapResolverStoreMessageLoader($store, new FrozenClock($recordedOn)); + + self::assertSame(5, $loader->lastIndex()); + } + + public function testLastIndexOnEmptyStore(): void + { + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $loader = new GapResolverStoreMessageLoader($store, new FrozenClock(new DateTimeImmutable('2020-01-01 00:00:00'))); + + self::assertSame(0, $loader->lastIndex()); + } +} diff --git a/tests/Integration/Subscription/StoreMessageLoaderTest.php b/tests/Integration/Subscription/StoreMessageLoaderTest.php new file mode 100644 index 000000000..dd497eb15 --- /dev/null +++ b/tests/Integration/Subscription/StoreMessageLoaderTest.php @@ -0,0 +1,157 @@ +connection = DbalManager::createConnection(); + } + + public function tearDown(): void + { + $this->connection->close(); + } + + public function testLoadAllEvents(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-2')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new StoreMessageLoader($store); + + $stream = $loader->load(0, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([1, 2, 3], $indexes); + } + + public function testLoadFromStartIndex(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new ProfileCreated(ProfileId::generate(), 'Tom')) + ->withHeader(new StreamNameHeader('profile-2')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new StoreMessageLoader($store); + + $stream = $loader->load(1, [new Subscription('profile_1')]); + + $indexes = []; + + foreach ($stream as $message) { + $indexes[] = $stream->index(); + } + + self::assertSame([2, 3], $indexes); + } + + public function testLastIndex(): void + { + $recordedOn = new DateTimeImmutable('2020-01-01 00:00:00'); + + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $store->save( + Message::create(new ProfileCreated(ProfileId::generate(), 'John')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(1)) + ->withHeader(new RecordedOnHeader($recordedOn)), + Message::create(new NameChanged(ProfileId::generate(), 'Jane')) + ->withHeader(new StreamNameHeader('profile-1')) + ->withHeader(new PlayheadHeader(2)) + ->withHeader(new RecordedOnHeader($recordedOn)), + ); + + $loader = new StoreMessageLoader($store); + + self::assertSame(2, $loader->lastIndex()); + } + + public function testLastIndexOnEmptyStore(): void + { + $store = new StreamDoctrineDbalStore( + $this->connection, + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events']), + ); + + (new DoctrineSchemaDirector($this->connection, $store))->create(); + + $loader = new StoreMessageLoader($store); + + self::assertSame(0, $loader->lastIndex()); + } +} diff --git a/tests/Unit/Console/Command/SubscriptionStatusCommandTest.php b/tests/Unit/Console/Command/SubscriptionStatusCommandTest.php new file mode 100644 index 000000000..7cbe3e8ee --- /dev/null +++ b/tests/Unit/Console/Command/SubscriptionStatusCommandTest.php @@ -0,0 +1,124 @@ +createMock(SubscriptionEngine::class); + $engine + ->expects($this->once()) + ->method('subscriptions') + ->with(new SubscriptionEngineCriteria()) + ->willReturn([ + new Subscription('profile_1', 'projector', status: Status::Active, position: 10), + new Subscription('welcome_email', 'processor', status: Status::Paused, position: 5), + ]); + + $command = new SubscriptionStatusCommand($engine); + + $input = new ArrayInput([]); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('profile_1', $content); + self::assertStringContainsString('active', $content); + self::assertStringContainsString('welcome_email', $content); + self::assertStringContainsString('paused', $content); + } + + public function testListFiltered(): void + { + $engine = $this->createMock(SubscriptionEngine::class); + $engine + ->expects($this->once()) + ->method('subscriptions') + ->with(new SubscriptionEngineCriteria(['profile_1', 'profile_2'], ['projector'])) + ->willReturn([ + new Subscription('profile_1', 'projector', status: Status::Active, position: 10), + ]); + + $command = new SubscriptionStatusCommand($engine); + + $input = new ArrayInput([ + '--id' => ['profile_1', 'profile_2'], + '--group' => ['projector'], + ]); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('profile_1', $content); + self::assertStringNotContainsString('welcome_email', $content); + } + + public function testShowOne(): void + { + $engine = $this->createMock(SubscriptionEngine::class); + $engine + ->expects($this->once()) + ->method('subscriptions') + ->with(new SubscriptionEngineCriteria(['profile_1'])) + ->willReturn([ + new Subscription('profile_1', 'projector', status: Status::Active, position: 10), + ]); + + $command = new SubscriptionStatusCommand($engine); + + $input = new ArrayInput(['id' => 'profile_1']); + $output = new BufferedOutput(); + + $exitCode = $command->run($input, $output); + + self::assertSame(0, $exitCode); + + $content = $output->fetch(); + + self::assertStringContainsString('profile_1', $content); + self::assertStringContainsString('projector', $content); + self::assertStringContainsString('active', $content); + } + + public function testShowOneNotFound(): void + { + $engine = $this->createMock(SubscriptionEngine::class); + $engine + ->expects($this->once()) + ->method('subscriptions') + ->with(new SubscriptionEngineCriteria(['profile_1'])) + ->willReturn([]); + + $command = new SubscriptionStatusCommand($engine); + + $input = new ArrayInput(['id' => 'profile_1']); + $output = new BufferedOutput(); + + $this->expectException(SubscriptionNotFound::class); + + $command->run($input, $output); + } +} diff --git a/tools/composer.json b/tools/composer.json index 74d1aab33..473bd65cc 100644 --- a/tools/composer.json +++ b/tools/composer.json @@ -1,6 +1,6 @@ { "require": { "php": "~8.5.0", - "roave/backward-compatibility-check": "^8.20.0" + "roave/backward-compatibility-check": "^8.23.0" } } diff --git a/tools/composer.lock b/tools/composer.lock index 8e6c0e394..15818ea2e 100644 --- a/tools/composer.lock +++ b/tools/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "115628ecf995f1bedf175c263987938a", + "content-hash": "5dab92cb5a7b23839a5caf9e41d4dbb7", "packages": [ { "name": "beberlei/assert", - "version": "v3.3.3", + "version": "v3.3.4", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" + "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", - "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "url": "https://api.github.com/repos/beberlei/assert/zipball/f193f4613c7d7fbcee2c05e4daff4061d49c040e", + "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e", "shasum": "" }, "require": { @@ -69,22 +69,22 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.3" + "source": "https://github.com/beberlei/assert/tree/v3.3.4" }, - "time": "2024-07-15T13:18:35+00:00" + "time": "2026-06-10T19:47:05+00:00" }, { "name": "composer/ca-bundle", - "version": "1.5.12", + "version": "1.5.14", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" + "reference": "0c8abba0634f637bd78c4e451981da368d403463" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", - "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c8abba0634f637bd78c4e451981da368d403463", + "reference": "0c8abba0634f637bd78c4e451981da368d403463", "shasum": "" }, "require": { @@ -131,7 +131,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.12" + "source": "https://github.com/composer/ca-bundle/tree/1.5.14" }, "funding": [ { @@ -143,20 +143,20 @@ "type": "github" } ], - "time": "2026-05-19T11:26:22+00:00" + "time": "2026-08-21T14:57:29+00:00" }, { "name": "composer/class-map-generator", - "version": "1.7.3", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f" + "reference": "c0efbeb2f3fd7e23c7f180a59765c0b48a31fbc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/86d8208fc3c649a3a999daf1a63c25201be2990f", - "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/c0efbeb2f3fd7e23c7f180a59765c0b48a31fbc4", + "reference": "c0efbeb2f3fd7e23c7f180a59765c0b48a31fbc4", "shasum": "" }, "require": { @@ -200,7 +200,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.7.3" + "source": "https://github.com/composer/class-map-generator/tree/1.8.0" }, "funding": [ { @@ -212,20 +212,20 @@ "type": "github" } ], - "time": "2026-05-05T09:17:07+00:00" + "time": "2026-09-25T15:24:19+00:00" }, { "name": "composer/composer", - "version": "2.10.0", + "version": "2.10.3", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "c13824d95608b15913a7c0def0a3dea4474b71fc" + "reference": "f0de0bf90226853b841672f086d8b58b02332504" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/c13824d95608b15913a7c0def0a3dea4474b71fc", - "reference": "c13824d95608b15913a7c0def0a3dea4474b71fc", + "url": "https://api.github.com/repos/composer/composer/zipball/f0de0bf90226853b841672f086d8b58b02332504", + "reference": "f0de0bf90226853b841672f086d8b58b02332504", "shasum": "" }, "require": { @@ -236,6 +236,8 @@ "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "justinrainbow/json-schema": "^6.5.1", "php": "^7.2.5 || ^8.0", @@ -313,7 +315,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.10.0" + "source": "https://github.com/composer/composer/tree/2.10.3" }, "funding": [ { @@ -325,20 +327,20 @@ "type": "github" } ], - "time": "2026-05-28T09:22:08+00:00" + "time": "2026-08-27T11:34:23+00:00" }, { "name": "composer/metadata-minifier", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/composer/metadata-minifier.git", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + "reference": "8e86142e3ade750b837d55e55f0cdc786d8da006" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/8e86142e3ade750b837d55e55f0cdc786d8da006", + "reference": "8e86142e3ade750b837d55e55f0cdc786d8da006", "shasum": "" }, "require": { @@ -346,8 +348,8 @@ }, "require-dev": { "composer/composer": "^2", - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1", + "symfony/phpunit-bridge": "^4.2 || ^5 || ^6 || ^7" }, "type": "library", "extra": { @@ -378,7 +380,7 @@ ], "support": { "issues": "https://github.com/composer/metadata-minifier/issues", - "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + "source": "https://github.com/composer/metadata-minifier/tree/1.0.1" }, "funding": [ { @@ -388,38 +390,35 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2021-04-07T13:37:33+00:00" + "time": "2026-06-30T11:34:59+00:00" }, { "name": "composer/pcre", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.10" + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { @@ -457,7 +456,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { @@ -467,26 +466,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2026-06-07T11:47:49+00:00" }, { "name": "composer/semver", - "version": "3.4.4", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + "reference": "f7a296f4c4cf8cb8bb83e35d6951a406bb11afa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "url": "https://api.github.com/repos/composer/semver/zipball/f7a296f4c4cf8cb8bb83e35d6951a406bb11afa5", + "reference": "f7a296f4c4cf8cb8bb83e35d6951a406bb11afa5", "shasum": "" }, "require": { @@ -528,7 +523,7 @@ "homepage": "http://robbast.nl" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", "semver", @@ -538,7 +533,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" + "source": "https://github.com/composer/semver/tree/3.5.0" }, "funding": [ { @@ -550,7 +545,7 @@ "type": "github" } ], - "time": "2025-08-20T19:15:30+00:00" + "time": "2026-09-24T14:38:51+00:00" }, { "name": "composer/spdx-licenses", @@ -696,16 +691,16 @@ }, { "name": "jetbrains/phpstorm-stubs", - "version": "v2026.1", + "version": "v2026.2", "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs", - "reference": "2cdd054c4109dfb76667c9198bf9427606354243" + "reference": "709e512210784a7c0a677b3a89d35def844a59b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/2cdd054c4109dfb76667c9198bf9427606354243", - "reference": "2cdd054c4109dfb76667c9198bf9427606354243", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/709e512210784a7c0a677b3a89d35def844a59b9", + "reference": "709e512210784a7c0a677b3a89d35def844a59b9", "shasum": "" }, "require-dev": { @@ -736,23 +731,24 @@ "stubs", "type" ], - "time": "2026-02-19T20:12:01+00:00" + "time": "2026-06-12T13:19:10+00:00" }, { "name": "justinrainbow/json-schema", - "version": "6.8.2", + "version": "6.13.1", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" + "reference": "e54aacc4c0657b3a4af4f628428d2702146828c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", - "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/e54aacc4c0657b3a4af4f628428d2702146828c6", + "reference": "e54aacc4c0657b3a4af4f628428d2702146828c6", "shasum": "" }, "require": { + "ext-filter": "*", "ext-json": "*", "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" @@ -785,20 +781,9 @@ ], "authors": [ { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" + "name": "Danny van der Sluijs", + "email": "danny.vandersluijs@icloud.com", + "role": "Maintainer" } ], "description": "A library to validate a json schema.", @@ -809,9 +794,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.13.1" }, - "time": "2026-05-05T05:39:01+00:00" + "time": "2026-09-30T07:42:28+00:00" }, { "name": "marc-mabe/php-enum", @@ -888,20 +873,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.9.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -940,9 +924,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-09-13T18:51:52+00:00" }, { "name": "nikolaposa/version", @@ -1070,16 +1054,16 @@ }, { "name": "php-standard-library/async", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/async.git", - "reference": "8c0c63d3e304318a7c78846044edc1dfef398d1d" + "reference": "3d2fbaf391cd256f5543f96889e9e738840d321a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/async/zipball/8c0c63d3e304318a7c78846044edc1dfef398d1d", - "reference": "8c0c63d3e304318a7c78846044edc1dfef398d1d", + "url": "https://api.github.com/repos/php-standard-library/async/zipball/3d2fbaf391cd256f5543f96889e9e738840d321a", + "reference": "3d2fbaf391cd256f5543f96889e9e738840d321a", "shasum": "" }, "require": { @@ -1102,7 +1086,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1136,22 +1120,22 @@ "cooperative-multitasking" ], "support": { - "source": "https://github.com/php-standard-library/async/tree/6.2.1" + "source": "https://github.com/php-standard-library/async/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/channel", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/channel.git", - "reference": "1d90ea4e262978a583ad24c2c9a97c9daa26b6f1" + "reference": "f2f5f0ee4d04dccc57d33e6620671c6296e881e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/channel/zipball/1d90ea4e262978a583ad24c2c9a97c9daa26b6f1", - "reference": "1d90ea4e262978a583ad24c2c9a97c9daa26b6f1", + "url": "https://api.github.com/repos/php-standard-library/channel/zipball/f2f5f0ee4d04dccc57d33e6620671c6296e881e2", + "reference": "f2f5f0ee4d04dccc57d33e6620671c6296e881e2", "shasum": "" }, "require": { @@ -1171,7 +1155,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1205,22 +1189,22 @@ "message-passing" ], "support": { - "source": "https://github.com/php-standard-library/channel/tree/6.2.1" + "source": "https://github.com/php-standard-library/channel/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/collection", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/collection.git", - "reference": "0e8b757a16ccb1f68a70f4ada8565099743d50b9" + "reference": "d31f6a015958ae4e13e2c1d47621d9786ee1ec1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/collection/zipball/0e8b757a16ccb1f68a70f4ada8565099743d50b9", - "reference": "0e8b757a16ccb1f68a70f4ada8565099743d50b9", + "url": "https://api.github.com/repos/php-standard-library/collection/zipball/d31f6a015958ae4e13e2c1d47621d9786ee1ec1a", + "reference": "d31f6a015958ae4e13e2c1d47621d9786ee1ec1a", "shasum": "" }, "require": { @@ -1239,7 +1223,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1271,22 +1255,22 @@ "vector" ], "support": { - "source": "https://github.com/php-standard-library/collection/tree/6.2.1" + "source": "https://github.com/php-standard-library/collection/tree/6.3.0" }, - "time": "2026-05-18T22:19:21+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/comparison", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/comparison.git", - "reference": "1884e2218c231c285b6039e9b2010f5dd956ae20" + "reference": "e77454e109feaa13732a3d556e1f8855b216b4fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/comparison/zipball/1884e2218c231c285b6039e9b2010f5dd956ae20", - "reference": "1884e2218c231c285b6039e9b2010f5dd956ae20", + "url": "https://api.github.com/repos/php-standard-library/comparison/zipball/e77454e109feaa13732a3d556e1f8855b216b4fd", + "reference": "e77454e109feaa13732a3d556e1f8855b216b4fd", "shasum": "" }, "require": { @@ -1303,7 +1287,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1336,22 +1320,22 @@ "ordering" ], "support": { - "source": "https://github.com/php-standard-library/comparison/tree/6.2.1" + "source": "https://github.com/php-standard-library/comparison/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/date-time", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/date-time.git", - "reference": "1e5e2b51eb2b27c2933859872e7c4a45abc5226e" + "reference": "4d280c6192e1b17ee8a59e07ed0fc29129bcd373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/date-time/zipball/1e5e2b51eb2b27c2933859872e7c4a45abc5226e", - "reference": "1e5e2b51eb2b27c2933859872e7c4a45abc5226e", + "url": "https://api.github.com/repos/php-standard-library/date-time/zipball/4d280c6192e1b17ee8a59e07ed0fc29129bcd373", + "reference": "4d280c6192e1b17ee8a59e07ed0fc29129bcd373", "shasum": "" }, "require": { @@ -1374,7 +1358,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1408,22 +1392,22 @@ "timezone" ], "support": { - "source": "https://github.com/php-standard-library/date-time/tree/6.2.1" + "source": "https://github.com/php-standard-library/date-time/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/default", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/default.git", - "reference": "89f05ec6e6a29e8c07de7b6755d14d05b06048e2" + "reference": "6f7daf0864b4f319dee645cb0ffd7c4be0c9907c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/default/zipball/89f05ec6e6a29e8c07de7b6755d14d05b06048e2", - "reference": "89f05ec6e6a29e8c07de7b6755d14d05b06048e2", + "url": "https://api.github.com/repos/php-standard-library/default/zipball/6f7daf0864b4f319dee645cb0ffd7c4be0c9907c", + "reference": "6f7daf0864b4f319dee645cb0ffd7c4be0c9907c", "shasum": "" }, "require": { @@ -1438,7 +1422,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1467,22 +1451,22 @@ "interface" ], "support": { - "source": "https://github.com/php-standard-library/default/tree/6.2.1" + "source": "https://github.com/php-standard-library/default/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/dict", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/dict.git", - "reference": "e06a4b7dea0f870b909e9f7d81d9dc70395f0eeb" + "reference": "0c3f7546c4dd5887e085e21663eedaa3df78a00d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/dict/zipball/e06a4b7dea0f870b909e9f7d81d9dc70395f0eeb", - "reference": "e06a4b7dea0f870b909e9f7d81d9dc70395f0eeb", + "url": "https://api.github.com/repos/php-standard-library/dict/zipball/0c3f7546c4dd5887e085e21663eedaa3df78a00d", + "reference": "0c3f7546c4dd5887e085e21663eedaa3df78a00d", "shasum": "" }, "require": { @@ -1502,7 +1486,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1536,22 +1520,22 @@ "map" ], "support": { - "source": "https://github.com/php-standard-library/dict/tree/6.2.1" + "source": "https://github.com/php-standard-library/dict/tree/6.3.0" }, - "time": "2026-03-31T16:59:27+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/either-or-both", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/either-or-both.git", - "reference": "a1f4d80ee5ee616272688941390d2eae2d1948e6" + "reference": "cd443b661dae1dc87f309e17953e9a7426690195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/either-or-both/zipball/a1f4d80ee5ee616272688941390d2eae2d1948e6", - "reference": "a1f4d80ee5ee616272688941390d2eae2d1948e6", + "url": "https://api.github.com/repos/php-standard-library/either-or-both/zipball/cd443b661dae1dc87f309e17953e9a7426690195", + "reference": "cd443b661dae1dc87f309e17953e9a7426690195", "shasum": "" }, "require": { @@ -1570,7 +1554,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1605,22 +1589,22 @@ "union-type" ], "support": { - "source": "https://github.com/php-standard-library/either-or-both/tree/6.2.1" + "source": "https://github.com/php-standard-library/either-or-both/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/env", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/env.git", - "reference": "1c6d72eef53e904506daa021141a5981a30c4403" + "reference": "093b68fbe9f1104142735c2e2a5ca42931c9a8f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/env/zipball/1c6d72eef53e904506daa021141a5981a30c4403", - "reference": "1c6d72eef53e904506daa021141a5981a30c4403", + "url": "https://api.github.com/repos/php-standard-library/env/zipball/093b68fbe9f1104142735c2e2a5ca42931c9a8f1", + "reference": "093b68fbe9f1104142735c2e2a5ca42931c9a8f1", "shasum": "" }, "require": { @@ -1637,7 +1621,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1669,22 +1653,22 @@ "environment" ], "support": { - "source": "https://github.com/php-standard-library/env/tree/6.2.1" + "source": "https://github.com/php-standard-library/env/tree/6.3.0" }, - "time": "2026-03-31T16:59:27+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/file", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/file.git", - "reference": "64c064f8a599cb6621e7b9329a4f1b7c5bc04152" + "reference": "8e6c84b64060278c833e64fb8fb3298a34d2d3af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/file/zipball/64c064f8a599cb6621e7b9329a4f1b7c5bc04152", - "reference": "64c064f8a599cb6621e7b9329a4f1b7c5bc04152", + "url": "https://api.github.com/repos/php-standard-library/file/zipball/8e6c84b64060278c833e64fb8fb3298a34d2d3af", + "reference": "8e6c84b64060278c833e64fb8fb3298a34d2d3af", "shasum": "" }, "require": { @@ -1707,7 +1691,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1740,22 +1724,22 @@ "io" ], "support": { - "source": "https://github.com/php-standard-library/file/tree/6.2.1" + "source": "https://github.com/php-standard-library/file/tree/6.3.0" }, - "time": "2026-04-06T03:33:20+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/filesystem", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/filesystem.git", - "reference": "d1e87eaee4d8180842f38d010747ee5d963a8d5b" + "reference": "65a13875f9f918a10b072a3f6975dfd9e4e412e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/filesystem/zipball/d1e87eaee4d8180842f38d010747ee5d963a8d5b", - "reference": "d1e87eaee4d8180842f38d010747ee5d963a8d5b", + "url": "https://api.github.com/repos/php-standard-library/filesystem/zipball/65a13875f9f918a10b072a3f6975dfd9e4e412e6", + "reference": "65a13875f9f918a10b072a3f6975dfd9e4e412e6", "shasum": "" }, "require": { @@ -1777,7 +1761,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1809,22 +1793,22 @@ "filesystem" ], "support": { - "source": "https://github.com/php-standard-library/filesystem/tree/6.2.1" + "source": "https://github.com/php-standard-library/filesystem/tree/6.3.0" }, - "time": "2026-03-31T16:59:27+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/foundation", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/foundation.git", - "reference": "7f65cec48c8ed3d53dc8dd7643796e2ba6de6008" + "reference": "9b464388f93b5c496bd6a17912e5cdc846b9df1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/foundation/zipball/7f65cec48c8ed3d53dc8dd7643796e2ba6de6008", - "reference": "7f65cec48c8ed3d53dc8dd7643796e2ba6de6008", + "url": "https://api.github.com/repos/php-standard-library/foundation/zipball/9b464388f93b5c496bd6a17912e5cdc846b9df1e", + "reference": "9b464388f93b5c496bd6a17912e5cdc846b9df1e", "shasum": "" }, "require": { @@ -1839,7 +1823,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1872,22 +1856,22 @@ "invariant" ], "support": { - "source": "https://github.com/php-standard-library/foundation/tree/6.2.1" + "source": "https://github.com/php-standard-library/foundation/tree/6.3.0" }, - "time": "2026-04-28T06:28:49+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/interoperability", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/interoperability.git", - "reference": "09ae66cc3e6463538c3a4592bb8762cf60113821" + "reference": "d95ae8d2502d05b7aa14e456b2c7f5e9c1510228" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/interoperability/zipball/09ae66cc3e6463538c3a4592bb8762cf60113821", - "reference": "09ae66cc3e6463538c3a4592bb8762cf60113821", + "url": "https://api.github.com/repos/php-standard-library/interoperability/zipball/d95ae8d2502d05b7aa14e456b2c7f5e9c1510228", + "reference": "d95ae8d2502d05b7aa14e456b2c7f5e9c1510228", "shasum": "" }, "require": { @@ -1902,7 +1886,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -1931,22 +1915,22 @@ "interoperability" ], "support": { - "source": "https://github.com/php-standard-library/interoperability/tree/6.2.1" + "source": "https://github.com/php-standard-library/interoperability/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/io", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/io.git", - "reference": "0b61f6b5e0a392c52d3cb2b386d8bba379b91f3c" + "reference": "cd55c50f5158871c9a77b3fc6a1fca14f3d4d082" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/io/zipball/0b61f6b5e0a392c52d3cb2b386d8bba379b91f3c", - "reference": "0b61f6b5e0a392c52d3cb2b386d8bba379b91f3c", + "url": "https://api.github.com/repos/php-standard-library/io/zipball/cd55c50f5158871c9a77b3fc6a1fca14f3d4d082", + "reference": "cd55c50f5158871c9a77b3fc6a1fca14f3d4d082", "shasum": "" }, "require": { @@ -1969,7 +1953,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2003,22 +1987,22 @@ "stream" ], "support": { - "source": "https://github.com/php-standard-library/io/tree/6.2.1" + "source": "https://github.com/php-standard-library/io/tree/6.3.0" }, - "time": "2026-04-28T03:11:45+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/iter", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/iter.git", - "reference": "d3f2db3adec4cbe5129f3428969ae8508a54aeb7" + "reference": "9a53e9e4614b6e30ac23df21bd9bf7540dad79f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/iter/zipball/d3f2db3adec4cbe5129f3428969ae8508a54aeb7", - "reference": "d3f2db3adec4cbe5129f3428969ae8508a54aeb7", + "url": "https://api.github.com/repos/php-standard-library/iter/zipball/9a53e9e4614b6e30ac23df21bd9bf7540dad79f8", + "reference": "9a53e9e4614b6e30ac23df21bd9bf7540dad79f8", "shasum": "" }, "require": { @@ -2041,7 +2025,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2075,22 +2059,22 @@ "iterator" ], "support": { - "source": "https://github.com/php-standard-library/iter/tree/6.2.1" + "source": "https://github.com/php-standard-library/iter/tree/6.3.0" }, - "time": "2026-04-28T06:28:49+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/json", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/json.git", - "reference": "03e00062efdd704d874012b5412340f3003a043d" + "reference": "ba52bbe747e91b2a1c905d3c51b8fc0b9f5af27d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/json/zipball/03e00062efdd704d874012b5412340f3003a043d", - "reference": "03e00062efdd704d874012b5412340f3003a043d", + "url": "https://api.github.com/repos/php-standard-library/json/zipball/ba52bbe747e91b2a1c905d3c51b8fc0b9f5af27d", + "reference": "ba52bbe747e91b2a1c905d3c51b8fc0b9f5af27d", "shasum": "" }, "require": { @@ -2111,7 +2095,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2144,22 +2128,22 @@ "json" ], "support": { - "source": "https://github.com/php-standard-library/json/tree/6.2.1" + "source": "https://github.com/php-standard-library/json/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/locale", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/locale.git", - "reference": "0771cfb19757e8923c66b7f46ab90280775caecd" + "reference": "9687409700fc2392046f43cc4c1494253f8cf178" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/locale/zipball/0771cfb19757e8923c66b7f46ab90280775caecd", - "reference": "0771cfb19757e8923c66b7f46ab90280775caecd", + "url": "https://api.github.com/repos/php-standard-library/locale/zipball/9687409700fc2392046f43cc4c1494253f8cf178", + "reference": "9687409700fc2392046f43cc4c1494253f8cf178", "shasum": "" }, "require": { @@ -2176,7 +2160,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2206,22 +2190,22 @@ "locale" ], "support": { - "source": "https://github.com/php-standard-library/locale/tree/6.2.1" + "source": "https://github.com/php-standard-library/locale/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/option", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/option.git", - "reference": "afe2059dbb3a9e2e3ba6edd371ccbb7d87355a66" + "reference": "d6cfc43ae2d0ce122d861065f5e6a433737b9e93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/option/zipball/afe2059dbb3a9e2e3ba6edd371ccbb7d87355a66", - "reference": "afe2059dbb3a9e2e3ba6edd371ccbb7d87355a66", + "url": "https://api.github.com/repos/php-standard-library/option/zipball/d6cfc43ae2d0ce122d861065f5e6a433737b9e93", + "reference": "d6cfc43ae2d0ce122d861065f5e6a433737b9e93", "shasum": "" }, "require": { @@ -2238,7 +2222,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2272,22 +2256,22 @@ "some" ], "support": { - "source": "https://github.com/php-standard-library/option/tree/6.2.1" + "source": "https://github.com/php-standard-library/option/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/process", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/process.git", - "reference": "ab41e8b8de07289971f447ce289ec1effde321f3" + "reference": "46895e2c1b19cdd6c48688d1c631f1c23bc524e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/process/zipball/ab41e8b8de07289971f447ce289ec1effde321f3", - "reference": "ab41e8b8de07289971f447ce289ec1effde321f3", + "url": "https://api.github.com/repos/php-standard-library/process/zipball/46895e2c1b19cdd6c48688d1c631f1c23bc524e5", + "reference": "46895e2c1b19cdd6c48688d1c631f1c23bc524e5", "shasum": "" }, "require": { @@ -2309,7 +2293,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2339,22 +2323,22 @@ "subprocess" ], "support": { - "source": "https://github.com/php-standard-library/process/tree/6.2.1" + "source": "https://github.com/php-standard-library/process/tree/6.3.0" }, - "time": "2026-05-23T20:46:16+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/promise", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/promise.git", - "reference": "a38a2694e06609874e856e68951bca24ad00009c" + "reference": "97e5c6ffac07058f0ae408c747b5f5c363f3673b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/promise/zipball/a38a2694e06609874e856e68951bca24ad00009c", - "reference": "a38a2694e06609874e856e68951bca24ad00009c", + "url": "https://api.github.com/repos/php-standard-library/promise/zipball/97e5c6ffac07058f0ae408c747b5f5c363f3673b", + "reference": "97e5c6ffac07058f0ae408c747b5f5c363f3673b", "shasum": "" }, "require": { @@ -2369,7 +2353,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2399,22 +2383,22 @@ "promise" ], "support": { - "source": "https://github.com/php-standard-library/promise/tree/6.2.1" + "source": "https://github.com/php-standard-library/promise/tree/6.3.0" }, - "time": "2026-04-26T16:26:10+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/range", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/range.git", - "reference": "34befd85fa3e048972aabac7cdf6a86e2f4b833f" + "reference": "9e08b73482c2c8ee6065e4eb1c6d138ceba56376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/range/zipball/34befd85fa3e048972aabac7cdf6a86e2f4b833f", - "reference": "34befd85fa3e048972aabac7cdf6a86e2f4b833f", + "url": "https://api.github.com/repos/php-standard-library/range/zipball/9e08b73482c2c8ee6065e4eb1c6d138ceba56376", + "reference": "9e08b73482c2c8ee6065e4eb1c6d138ceba56376", "shasum": "" }, "require": { @@ -2432,7 +2416,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2464,22 +2448,22 @@ "range" ], "support": { - "source": "https://github.com/php-standard-library/range/tree/6.2.1" + "source": "https://github.com/php-standard-library/range/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/regex", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/regex.git", - "reference": "150533c75ee7aaf83a1254837690603b22bc0191" + "reference": "199b6e8f3321e7e5978a3f55963d4bd6fbc57317" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/regex/zipball/150533c75ee7aaf83a1254837690603b22bc0191", - "reference": "150533c75ee7aaf83a1254837690603b22bc0191", + "url": "https://api.github.com/repos/php-standard-library/regex/zipball/199b6e8f3321e7e5978a3f55963d4bd6fbc57317", + "reference": "199b6e8f3321e7e5978a3f55963d4bd6fbc57317", "shasum": "" }, "require": { @@ -2496,7 +2480,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2529,22 +2513,22 @@ "regular-expression" ], "support": { - "source": "https://github.com/php-standard-library/regex/tree/6.2.1" + "source": "https://github.com/php-standard-library/regex/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/result", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/result.git", - "reference": "242bbf872662f5091a37d7db9d45f119225a01f5" + "reference": "8489291e58eb926d3ffc9e48b23d8fa6678a0ff3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/result/zipball/242bbf872662f5091a37d7db9d45f119225a01f5", - "reference": "242bbf872662f5091a37d7db9d45f119225a01f5", + "url": "https://api.github.com/repos/php-standard-library/result/zipball/8489291e58eb926d3ffc9e48b23d8fa6678a0ff3", + "reference": "8489291e58eb926d3ffc9e48b23d8fa6678a0ff3", "shasum": "" }, "require": { @@ -2562,7 +2546,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2595,22 +2579,22 @@ "result" ], "support": { - "source": "https://github.com/php-standard-library/result/tree/6.2.1" + "source": "https://github.com/php-standard-library/result/tree/6.3.0" }, - "time": "2026-04-26T16:26:10+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/shell", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/shell.git", - "reference": "6d57e1192d0ecce76e8c33e7507505b03c1bc539" + "reference": "e87e4720403c748036166102df5b825f7bb9ecd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/shell/zipball/6d57e1192d0ecce76e8c33e7507505b03c1bc539", - "reference": "6d57e1192d0ecce76e8c33e7507505b03c1bc539", + "url": "https://api.github.com/repos/php-standard-library/shell/zipball/e87e4720403c748036166102df5b825f7bb9ecd1", + "reference": "e87e4720403c748036166102df5b825f7bb9ecd1", "shasum": "" }, "require": { @@ -2634,7 +2618,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2667,22 +2651,22 @@ "shell" ], "support": { - "source": "https://github.com/php-standard-library/shell/tree/6.2.1" + "source": "https://github.com/php-standard-library/shell/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/str", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/str.git", - "reference": "e88e7c79e1c07227aa3c50005a3a91e88425e56a" + "reference": "ab722aecf6c45531cdf0bf8ee11aa2370d71dff7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/str/zipball/e88e7c79e1c07227aa3c50005a3a91e88425e56a", - "reference": "e88e7c79e1c07227aa3c50005a3a91e88425e56a", + "url": "https://api.github.com/repos/php-standard-library/str/zipball/ab722aecf6c45531cdf0bf8ee11aa2370d71dff7", + "reference": "ab722aecf6c45531cdf0bf8ee11aa2370d71dff7", "shasum": "" }, "require": { @@ -2701,7 +2685,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2735,22 +2719,22 @@ "utf8" ], "support": { - "source": "https://github.com/php-standard-library/str/tree/6.2.1" + "source": "https://github.com/php-standard-library/str/tree/6.3.0" }, - "time": "2026-04-29T06:23:35+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/type", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/type.git", - "reference": "ffc050e32bc5def0e03aaae3d52c78dfc746e143" + "reference": "80986810f49ba476d90dfeaa9c41bee0997675eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/type/zipball/ffc050e32bc5def0e03aaae3d52c78dfc746e143", - "reference": "ffc050e32bc5def0e03aaae3d52c78dfc746e143", + "url": "https://api.github.com/repos/php-standard-library/type/zipball/80986810f49ba476d90dfeaa9c41bee0997675eb", + "reference": "80986810f49ba476d90dfeaa9c41bee0997675eb", "shasum": "" }, "require": { @@ -2764,6 +2748,7 @@ "azjezz/psl": "*" }, "require-dev": { + "ext-bcmath": "*", "php-standard-library/dict": "^6.0", "php-standard-library/math": "^6.0", "php-standard-library/result": "^6.0", @@ -2774,7 +2759,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2808,22 +2793,22 @@ "validation" ], "support": { - "source": "https://github.com/php-standard-library/type/tree/6.2.1" + "source": "https://github.com/php-standard-library/type/tree/6.3.0" }, - "time": "2026-05-23T20:26:52+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "php-standard-library/vec", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/php-standard-library/vec.git", - "reference": "ac119fa952177c10eeb3d5d6bca04130491c7f2e" + "reference": "5251a921c3d06b2045b4371c191bb60ff0ce47d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-standard-library/vec/zipball/ac119fa952177c10eeb3d5d6bca04130491c7f2e", - "reference": "ac119fa952177c10eeb3d5d6bca04130491c7f2e", + "url": "https://api.github.com/repos/php-standard-library/vec/zipball/5251a921c3d06b2045b4371c191bb60ff0ce47d3", + "reference": "5251a921c3d06b2045b4371c191bb60ff0ce47d3", "shasum": "" }, "require": { @@ -2843,7 +2828,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-next": "6.2.x-dev" + "dev-next": "6.3.x-dev" } }, "autoload": { @@ -2877,9 +2862,9 @@ "vector" ], "support": { - "source": "https://github.com/php-standard-library/vec/tree/6.2.1" + "source": "https://github.com/php-standard-library/vec/tree/6.3.0" }, - "time": "2026-03-28T18:39:47+00:00" + "time": "2026-09-25T15:54:11+00:00" }, { "name": "psr/container", @@ -3131,16 +3116,16 @@ }, { "name": "roave/backward-compatibility-check", - "version": "8.21.0", + "version": "8.23.0", "source": { "type": "git", "url": "https://github.com/Roave/BackwardCompatibilityCheck.git", - "reference": "ee85fb8879cfe939814d84e18ec6cb5c33904b75" + "reference": "f7573e3d1e3d012096f04aa7fe4de99b42aaf01d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/BackwardCompatibilityCheck/zipball/ee85fb8879cfe939814d84e18ec6cb5c33904b75", - "reference": "ee85fb8879cfe939814d84e18ec6cb5c33904b75", + "url": "https://api.github.com/repos/Roave/BackwardCompatibilityCheck/zipball/f7573e3d1e3d012096f04aa7fe4de99b42aaf01d", + "reference": "f7573e3d1e3d012096f04aa7fe4de99b42aaf01d", "shasum": "" }, "require": { @@ -3213,36 +3198,36 @@ "description": "Tool to compare two revisions of a public API to check for BC breaks", "support": { "issues": "https://github.com/Roave/BackwardCompatibilityCheck/issues", - "source": "https://github.com/Roave/BackwardCompatibilityCheck/tree/8.21.0" + "source": "https://github.com/Roave/BackwardCompatibilityCheck/tree/8.23.0" }, - "time": "2026-05-15T14:19:15+00:00" + "time": "2026-09-30T20:43:24+00:00" }, { "name": "roave/better-reflection", - "version": "6.71.0", + "version": "6.73.0", "source": { "type": "git", "url": "https://github.com/Roave/BetterReflection.git", - "reference": "3ec176d4a161b4e8764edc625d69ff624ca390b1" + "reference": "a6a93a3bd7681627f2750bfa8f41e586d32dc67d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/BetterReflection/zipball/3ec176d4a161b4e8764edc625d69ff624ca390b1", - "reference": "3ec176d4a161b4e8764edc625d69ff624ca390b1", + "url": "https://api.github.com/repos/Roave/BetterReflection/zipball/a6a93a3bd7681627f2750bfa8f41e586d32dc67d", + "reference": "a6a93a3bd7681627f2750bfa8f41e586d32dc67d", "shasum": "" }, "require": { "ext-json": "*", - "jetbrains/phpstorm-stubs": "2026.1", - "nikic/php-parser": "^5.7.0", + "jetbrains/phpstorm-stubs": "2026.2", + "nikic/php-parser": "^5.8.0", "php": "~8.4.1 || ~8.5.0" }, "conflict": { "thecodingmachine/safe": "<1.1.3" }, "require-dev": { - "phpbench/phpbench": "^1.6.1", - "phpunit/phpunit": "^13.1.8" + "phpbench/phpbench": "^1.7.0", + "phpunit/phpunit": "^13.3.2" }, "suggest": { "composer/composer": "Required to use the ComposerSourceLocator" @@ -3282,22 +3267,22 @@ "description": "Better Reflection - an improved code reflection API", "support": { "issues": "https://github.com/Roave/BetterReflection/issues", - "source": "https://github.com/Roave/BetterReflection/tree/6.71.0" + "source": "https://github.com/Roave/BetterReflection/tree/6.73.0" }, - "time": "2026-05-02T10:56:00+00:00" + "time": "2026-09-09T19:57:09+00:00" }, { "name": "seld/jsonlint", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", - "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", "shasum": "" }, "require": { @@ -3336,7 +3321,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" }, "funding": [ { @@ -3348,20 +3333,20 @@ "type": "tidelift" } ], - "time": "2024-07-11T14:55:45+00:00" + "time": "2026-06-12T11:32:29+00:00" }, { "name": "seld/phar-utils", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + "reference": "65fb381613fd31745d39a740a112a233f978d2d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", - "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/65fb381613fd31745d39a740a112a233f978d2d9", + "reference": "65fb381613fd31745d39a740a112a233f978d2d9", "shasum": "" }, "require": { @@ -3394,9 +3379,9 @@ ], "support": { "issues": "https://github.com/Seldaek/phar-utils/issues", - "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.3" }, - "time": "2022-08-31T10:31:18+00:00" + "time": "2026-10-01T08:16:23+00:00" }, { "name": "seld/signal-handler", @@ -3461,16 +3446,16 @@ }, { "name": "symfony/console", - "version": "v7.4.13", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217" + "reference": "d7bca2fe05241dc7732d0c8b6d551b879f5d0a7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217", - "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217", + "url": "https://api.github.com/repos/symfony/console/zipball/d7bca2fe05241dc7732d0c8b6d551b879f5d0a7d", + "reference": "d7bca2fe05241dc7732d0c8b6d551b879f5d0a7d", "shasum": "" }, "require": { @@ -3535,7 +3520,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.13" + "source": "https://github.com/symfony/console/tree/v7.4.20" }, "funding": [ { @@ -3555,20 +3540,20 @@ "type": "tidelift" } ], - "time": "2026-05-24T08:56:14+00:00" + "time": "2026-09-18T15:55:00+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -3606,7 +3591,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -3626,24 +3611,25 @@ "type": "tidelift" } ], - "time": "2026-04-13T15:52:40+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v8.0.11", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7" + "reference": "7599ebb855fede59413ddb7f15198d67bfcae7ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/224db910898ce1317b892a9a1338f1f8f17eb7c7", - "reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7599ebb855fede59413ddb7f15198d67bfcae7ba", + "reference": "7599ebb855fede59413ddb7f15198d67bfcae7ba", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, @@ -3676,7 +3662,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.11" + "source": "https://github.com/symfony/filesystem/tree/v8.1.6" }, "funding": [ { @@ -3696,24 +3682,24 @@ "type": "tidelift" } ], - "time": "2026-05-11T16:39:47+00:00" + "time": "2026-08-23T10:06:25+00:00" }, { "name": "symfony/finder", - "version": "v8.0.8", + "version": "v8.1.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8da41214757b87d97f181e3d14a4179286151007" + "reference": "bd4fed193a3132c91c5c93595cbfc78988915822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007", - "reference": "8da41214757b87d97f181e3d14a4179286151007", + "url": "https://api.github.com/repos/symfony/finder/zipball/bd4fed193a3132c91c5c93595cbfc78988915822", + "reference": "bd4fed193a3132c91c5c93595cbfc78988915822", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/filesystem": "^7.4|^8.0" @@ -3744,7 +3730,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.0.8" + "source": "https://github.com/symfony/finder/tree/v8.1.8" }, "funding": [ { @@ -3764,7 +3750,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-09-26T06:33:49+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3851,16 +3837,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", "shasum": "" }, "require": { @@ -3909,7 +3895,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" }, "funding": [ { @@ -3929,20 +3915,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T05:58:03+00:00" + "time": "2026-09-25T15:46:47+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { @@ -3994,7 +3980,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { @@ -4014,20 +4000,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:48:31+00:00" + "time": "2026-09-23T22:02:09+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.1", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { @@ -4079,7 +4065,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { @@ -4099,7 +4085,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-09-24T15:19:04+00:00" }, { "name": "symfony/polyfill-php73", @@ -4183,16 +4169,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.37.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { @@ -4243,7 +4229,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { @@ -4263,7 +4249,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { "name": "symfony/polyfill-php81", @@ -4347,16 +4333,16 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.38.1", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", - "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", "shasum": "" }, "require": { @@ -4403,7 +4389,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" }, "funding": [ { @@ -4423,24 +4409,24 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-09-23T22:01:09+00:00" }, { "name": "symfony/process", - "version": "v8.0.13", + "version": "v8.1.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4f23d9c7637ead9ed19f697fe93cb87fd9b379d4" + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4f23d9c7637ead9ed19f697fe93cb87fd9b379d4", - "reference": "4f23d9c7637ead9ed19f697fe93cb87fd9b379d4", + "url": "https://api.github.com/repos/symfony/process/zipball/10823b09358e690df4ff943e24e8492bb1019fc3", + "reference": "10823b09358e690df4ff943e24e8492bb1019fc3", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -4468,7 +4454,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.0.13" + "source": "https://github.com/symfony/process/tree/v8.1.7" }, "funding": [ { @@ -4488,20 +4474,20 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-09-02T12:40:29+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -4555,7 +4541,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -4575,24 +4561,24 @@ "type": "tidelift" } ], - "time": "2026-03-28T09:44:51+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "symfony/string", - "version": "v8.0.13", + "version": "v8.1.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f2e3e4d33579350d1b12001ef2872f86b27ed3dc" + "reference": "d950140b5f56f31901e5b7a0c04ffc3a3deb943c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f2e3e4d33579350d1b12001ef2872f86b27ed3dc", - "reference": "f2e3e4d33579350d1b12001ef2872f86b27ed3dc", + "url": "https://api.github.com/repos/symfony/string/zipball/d950140b5f56f31901e5b7a0c04ffc3a3deb943c", + "reference": "d950140b5f56f31901e5b7a0c04ffc3a3deb943c", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-grapheme": "^1.33", "symfony/polyfill-intl-normalizer": "^1.0", @@ -4645,7 +4631,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.13" + "source": "https://github.com/symfony/string/tree/v8.1.7" }, "funding": [ { @@ -4665,7 +4651,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-09-11T14:51:16+00:00" } ], "packages-dev": [],