From 2c7962af2148583168fe079eefac91e956f6194f Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Wed, 15 Jul 2026 17:54:25 +1000 Subject: [PATCH 01/10] Add table prefix support to TableIdentifier with configurable factory Adds optional prefix and separator parameters to TableIdentifier. When a prefix is set, getTable() and getTableAndSchema() return the prefixed table name (prefix + separator + table), so the prefix flows through all SQL generation unchanged. The separator defaults to '_' and the original name remains available via getUnprefixedTable(). Adds the callable Sql\TableIdentifierFactory, which produces identifiers carrying a preconfigured prefix/separator, and the container factory Container\TableIdentifierFactoryFactory, which reads both from the 'config' service so the prefix can be set once globally (e.g. to create backup_* tables during a migration). Registered in ConfigProvider. Closes php-db/phpdb#147 --- src/ConfigProvider.php | 3 +- .../TableIdentifierFactoryFactory.php | 43 +++++++ src/Sql/TableIdentifier.php | 52 ++++++++- src/Sql/TableIdentifierFactory.php | 57 ++++++++++ test/unit/ConfigProviderTest.php | 4 +- .../TableIdentifierFactoryFactoryTest.php | 84 ++++++++++++++ test/unit/Sql/TableIdentifierFactoryTest.php | 105 ++++++++++++++++++ test/unit/Sql/TableIdentifierTest.php | 85 ++++++++++++++ 8 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 src/Container/TableIdentifierFactoryFactory.php create mode 100644 src/Sql/TableIdentifierFactory.php create mode 100644 test/unit/Container/TableIdentifierFactoryFactoryTest.php create mode 100644 test/unit/Sql/TableIdentifierFactoryTest.php diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index eb1758d9..4fea5d94 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -25,7 +25,8 @@ public function getDependencies(): array Adapter\AdapterInterface::class => Adapter\Adapter::class, ], 'factories' => [ - Adapter\Adapter::class => Container\AdapterInterfaceFactory::class, + Adapter\Adapter::class => Container\AdapterInterfaceFactory::class, + Sql\TableIdentifierFactory::class => Container\TableIdentifierFactoryFactory::class, ], ]; } diff --git a/src/Container/TableIdentifierFactoryFactory.php b/src/Container/TableIdentifierFactoryFactory.php new file mode 100644 index 00000000..c48f0989 --- /dev/null +++ b/src/Container/TableIdentifierFactoryFactory.php @@ -0,0 +1,43 @@ + + * return [ + * TableIdentifierFactory::class => [ + * 'prefix' => 'backup', + * 'separator' => '_', + * ], + * ]; + * + * + * When no configuration is present the factory is created without a prefix. + * The separator defaults to '_' when not configured. + */ +final class TableIdentifierFactoryFactory +{ + public function __invoke(ContainerInterface $container): TableIdentifierFactory + { + /** @var array $config */ + $config = $container->has('config') ? $container->get('config') ?? [] : []; + + /** @var array{prefix?: string|null, separator?: string} $factoryConfig */ + $factoryConfig = $config[TableIdentifierFactory::class] ?? []; + + return new TableIdentifierFactory( + $factoryConfig['prefix'] ?? null, + $factoryConfig['separator'] ?? '_', + ); + } +} diff --git a/src/Sql/TableIdentifier.php b/src/Sql/TableIdentifier.php index eb5daee8..ec3f815e 100644 --- a/src/Sql/TableIdentifier.php +++ b/src/Sql/TableIdentifier.php @@ -10,8 +10,16 @@ class TableIdentifier protected ?string $schema = null; - public function __construct(string $table, ?string $schema = null) - { + protected ?string $prefix = null; + + protected string $separator = '_'; + + public function __construct( + string $table, + ?string $schema = null, + ?string $prefix = null, + string $separator = '_', + ) { if ('' === $table) { throw new Exception\InvalidArgumentException( '$table must be a valid table name, empty string given' @@ -29,13 +37,51 @@ public function __construct(string $table, ?string $schema = null) $this->schema = $schema; } + + if ($prefix !== null) { + if ('' === $prefix) { + throw new Exception\InvalidArgumentException( + '$prefix must be a valid table prefix or null, empty string given' + ); + } + + $this->prefix = $prefix; + } + + $this->separator = $separator; } + /** + * Returns the table name with the prefix and separator applied, when a + * prefix is set. + */ public function getTable(): string + { + if ($this->prefix === null) { + return $this->table; + } + + return $this->prefix . $this->separator . $this->table; + } + + /** + * Returns the table name as given, without the prefix applied. + */ + public function getUnprefixedTable(): string { return $this->table; } + public function getPrefix(): ?string + { + return $this->prefix; + } + + public function getSeparator(): string + { + return $this->separator; + } + public function getSchema(): ?string { return $this->schema; @@ -44,6 +90,6 @@ public function getSchema(): ?string /** @return array{0: string, 1: null|string} */ public function getTableAndSchema(): array { - return [$this->table, $this->schema]; + return [$this->getTable(), $this->schema]; } } diff --git a/src/Sql/TableIdentifierFactory.php b/src/Sql/TableIdentifierFactory.php new file mode 100644 index 00000000..28807092 --- /dev/null +++ b/src/Sql/TableIdentifierFactory.php @@ -0,0 +1,57 @@ +prefix = $prefix; + } + + public function getPrefix(): ?string + { + return $this->prefix; + } + + public function getSeparator(): string + { + return $this->separator; + } + + /** + * Creates a TableIdentifier carrying the configured prefix and separator. + * + * A prefix or separator passed at call time takes precedence over the + * configured one. + */ + public function __invoke( + string $table, + ?string $schema = null, + ?string $prefix = null, + ?string $separator = null, + ): TableIdentifier { + return new TableIdentifier($table, $schema, $prefix ?? $this->prefix, $separator ?? $this->separator); + } +} diff --git a/test/unit/ConfigProviderTest.php b/test/unit/ConfigProviderTest.php index 9fc3a0e2..ab643db7 100644 --- a/test/unit/ConfigProviderTest.php +++ b/test/unit/ConfigProviderTest.php @@ -7,6 +7,7 @@ use PhpDb\Adapter; use PhpDb\ConfigProvider; use PhpDb\Container; +use PhpDb\Sql; use PHPUnit\Framework\TestCase; class ConfigProviderTest extends TestCase @@ -29,7 +30,8 @@ class ConfigProviderTest extends TestCase Adapter\AdapterInterface::class => Adapter\Adapter::class, ], 'factories' => [ - Adapter\Adapter::class => Container\AdapterInterfaceFactory::class, + Adapter\Adapter::class => Container\AdapterInterfaceFactory::class, + Sql\TableIdentifierFactory::class => Container\TableIdentifierFactoryFactory::class, ], ], ]; diff --git a/test/unit/Container/TableIdentifierFactoryFactoryTest.php b/test/unit/Container/TableIdentifierFactoryFactoryTest.php new file mode 100644 index 00000000..791884fd --- /dev/null +++ b/test/unit/Container/TableIdentifierFactoryFactoryTest.php @@ -0,0 +1,84 @@ +getPrefix()); + } + + public function testInvokeCreatesFactoryWithoutPrefixWhenConfigIsEmpty(): void + { + $container = new ServiceManager(); + $container->setService('config', []); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertNull($result->getPrefix()); + } + + public function testInvokeCreatesFactoryWithConfiguredPrefix(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + ], + ]); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertSame('backup', $result->getPrefix()); + self::assertSame('backup_users', $result('users')->getTable()); + } + + public function testInvokeCreatesFactoryWithConfiguredSeparator(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + 'separator' => '__', + ], + ]); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertSame('__', $result->getSeparator()); + self::assertSame('backup__users', $result('users')->getTable()); + } + + public function testInvokeCreatesFactoryWithoutPrefixWhenPrefixKeyIsAbsent(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [], + ]); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertNull($result->getPrefix()); + } +} diff --git a/test/unit/Sql/TableIdentifierFactoryTest.php b/test/unit/Sql/TableIdentifierFactoryTest.php new file mode 100644 index 00000000..d6a3a2ac --- /dev/null +++ b/test/unit/Sql/TableIdentifierFactoryTest.php @@ -0,0 +1,105 @@ +getPrefix()); + } + + public function testGetPrefix(): void + { + $factory = new TableIdentifierFactory('backup'); + + self::assertSame('backup', $factory->getPrefix()); + } + + public function testGetDefaultSeparator(): void + { + $factory = new TableIdentifierFactory('backup'); + + self::assertSame('_', $factory->getSeparator()); + } + + public function testGetSeparator(): void + { + $factory = new TableIdentifierFactory('backup', '__'); + + self::assertSame('__', $factory->getSeparator()); + } + + public function testRejectsEmptyStringPrefix(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$prefix must be a valid table prefix or null, empty string given'); + new TableIdentifierFactory(''); + } + + public function testCreatesIdentifierWithConfiguredPrefix(): void + { + $factory = new TableIdentifierFactory('backup'); + $tableIdentifier = $factory('users'); + + self::assertSame('backup', $tableIdentifier->getPrefix()); + self::assertSame('backup_users', $tableIdentifier->getTable()); + self::assertNull($tableIdentifier->getSchema()); + } + + public function testCreatesIdentifierWithSchema(): void + { + $factory = new TableIdentifierFactory('backup'); + $tableIdentifier = $factory('users', 'public'); + + self::assertSame(['backup_users', 'public'], $tableIdentifier->getTableAndSchema()); + } + + public function testCreatesIdentifierWithConfiguredSeparator(): void + { + $factory = new TableIdentifierFactory('backup', '__'); + $tableIdentifier = $factory('users'); + + self::assertSame('__', $tableIdentifier->getSeparator()); + self::assertSame('backup__users', $tableIdentifier->getTable()); + } + + public function testCreatesIdentifierWithoutPrefixWhenNoneConfigured(): void + { + $factory = new TableIdentifierFactory(); + $tableIdentifier = $factory('users', 'public'); + + self::assertNull($tableIdentifier->getPrefix()); + self::assertSame('users', $tableIdentifier->getTable()); + } + + public function testCallTimePrefixOverridesConfiguredPrefix(): void + { + $factory = new TableIdentifierFactory('backup'); + $tableIdentifier = $factory('users', null, 'archive'); + + self::assertSame('archive', $tableIdentifier->getPrefix()); + self::assertSame('archive_users', $tableIdentifier->getTable()); + } + + public function testCallTimeSeparatorOverridesConfiguredSeparator(): void + { + $factory = new TableIdentifierFactory('backup', '__'); + $tableIdentifier = $factory('users', null, null, '_'); + + self::assertSame('_', $tableIdentifier->getSeparator()); + self::assertSame('backup_users', $tableIdentifier->getTable()); + } +} diff --git a/test/unit/Sql/TableIdentifierTest.php b/test/unit/Sql/TableIdentifierTest.php index 2e6c717e..410b9a6c 100644 --- a/test/unit/Sql/TableIdentifierTest.php +++ b/test/unit/Sql/TableIdentifierTest.php @@ -63,6 +63,83 @@ public function testGetSchemaFromObjectStringCast(): void self::assertSame('castResult', $tableIdentifier->getSchema()); } + public function testGetDefaultPrefix(): void + { + $tableIdentifier = new TableIdentifier('foo'); + + self::assertNull($tableIdentifier->getPrefix()); + } + + public function testGetPrefix(): void + { + $tableIdentifier = new TableIdentifier('foo', null, 'backup'); + + self::assertSame('backup', $tableIdentifier->getPrefix()); + } + + public function testGetDefaultSeparator(): void + { + $tableIdentifier = new TableIdentifier('foo'); + + self::assertSame('_', $tableIdentifier->getSeparator()); + } + + public function testGetSeparator(): void + { + $tableIdentifier = new TableIdentifier('foo', null, 'backup', '__'); + + self::assertSame('__', $tableIdentifier->getSeparator()); + } + + public function testGetTableAppliesPrefixWithDefaultSeparator(): void + { + $tableIdentifier = new TableIdentifier('foo', null, 'backup'); + + self::assertSame('backup_foo', $tableIdentifier->getTable()); + } + + public function testGetTableAppliesPrefixWithCustomSeparator(): void + { + $tableIdentifier = new TableIdentifier('foo', null, 'backup', '__'); + + self::assertSame('backup__foo', $tableIdentifier->getTable()); + } + + public function testGetTableAppliesPrefixWithEmptySeparator(): void + { + $tableIdentifier = new TableIdentifier('foo', null, 'backup', ''); + + self::assertSame('backupfoo', $tableIdentifier->getTable()); + } + + public function testGetTableIgnoresSeparatorWithoutPrefix(): void + { + $tableIdentifier = new TableIdentifier('foo', null, null, '__'); + + self::assertSame('foo', $tableIdentifier->getTable()); + } + + public function testGetUnprefixedTableReturnsTableAsGiven(): void + { + $tableIdentifier = new TableIdentifier('foo', null, 'backup'); + + self::assertSame('foo', $tableIdentifier->getUnprefixedTable()); + } + + public function testGetTableAndSchemaAppliesPrefix(): void + { + $tableIdentifier = new TableIdentifier('foo', 'bar', 'backup'); + + self::assertSame(['backup_foo', 'bar'], $tableIdentifier->getTableAndSchema()); + } + + public function testGetTableAndSchemaWithoutPrefix(): void + { + $tableIdentifier = new TableIdentifier('foo', 'bar'); + + self::assertSame(['foo', 'bar'], $tableIdentifier->getTableAndSchema()); + } + #[DataProvider('invalidTableProvider')] public function testRejectsInvalidTable(mixed $invalidTable): void { @@ -79,6 +156,14 @@ public function testRejectsInvalidSchema(mixed $invalidSchema): void new TableIdentifier('foo', $invalidSchema); } + #[DataProvider('invalidSchemaProvider')] + public function testRejectsInvalidPrefix(mixed $invalidPrefix): void + { + $this->expectException($invalidPrefix === '' ? InvalidArgumentException::class : TypeError::class); + /** @psalm-suppress MixedArgument */ + new TableIdentifier('foo', 'bar', $invalidPrefix); + } + /** * Data provider * From d1f9228499ad73826d9cb4328893dd3001320991 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Thu, 16 Jul 2026 10:09:32 +1000 Subject: [PATCH 02/10] - Improvements to testing - Updated DDL documentation --- docs/book/sql-ddl/alter-drop.md | 55 +++++++++++++++++++ test/unit/ConfigProviderTest.php | 4 ++ .../TableIdentifierFactoryFactoryTest.php | 15 ++++- test/unit/Sql/TableIdentifierFactoryTest.php | 8 +-- test/unit/Sql/TableIdentifierTest.php | 18 +++--- 5 files changed, 86 insertions(+), 14 deletions(-) diff --git a/docs/book/sql-ddl/alter-drop.md b/docs/book/sql-ddl/alter-drop.md index c0105350..3d2b9619 100644 --- a/docs/book/sql-ddl/alter-drop.md +++ b/docs/book/sql-ddl/alter-drop.md @@ -430,6 +430,61 @@ $fk = new ForeignKey( ); ``` +### Table Prefixes + +`TableIdentifier` accepts an optional prefix and separator. When a prefix is +set, `getTable()` and `getTableAndSchema()` return the prefixed table name; +`getUnprefixedTable()` returns the table name as given: + +```php title="Prefixed Table Identifiers" +use PhpDb\Sql\TableIdentifier; + +$identifier = new TableIdentifier('users', null, 'backup'); +$identifier->getTable(); // 'backup_users' +$identifier->getUnprefixedTable(); // 'users' + +// Custom separator +$identifier = new TableIdentifier('users', null, 'backup', '__'); +$identifier->getTable(); // 'backup__users' +``` + +The prefix must be a non-empty string or `null`; the separator defaults to +`'_'`. + +### The TableIdentifierFactory + +`PhpDb\Sql\TableIdentifierFactory` is a callable factory that creates +`TableIdentifier` instances sharing a preconfigured prefix and separator: + +```php title="Creating Identifiers via the Factory" +use PhpDb\Sql\TableIdentifierFactory; + +$factory = new TableIdentifierFactory('backup'); + +$factory('users')->getTable(); // 'backup_users' +$factory('orders', 'sales')->getTable(); // 'backup_orders' + +// A prefix or separator passed at call time overrides the configured one +$factory('users', null, 'archive')->getTable(); // 'archive_users' +``` + +`PhpDb\ConfigProvider` registers the factory as a container service. Configure +the prefix and separator through the application config: + +```php title="Configuring the Factory Service" +use PhpDb\Sql\TableIdentifierFactory; + +return [ + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + 'separator' => '_', + ], +]; +``` + +When no configuration is present the service is created without a prefix, and +the separator defaults to `'_'`. + ## Nullable and Default Values ### Setting Nullable diff --git a/test/unit/ConfigProviderTest.php b/test/unit/ConfigProviderTest.php index ab643db7..085f05ee 100644 --- a/test/unit/ConfigProviderTest.php +++ b/test/unit/ConfigProviderTest.php @@ -8,8 +8,12 @@ use PhpDb\ConfigProvider; use PhpDb\Container; use PhpDb\Sql; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; +#[CoversClass(ConfigProvider::class)] +#[Group('unit')] class ConfigProviderTest extends TestCase { /** diff --git a/test/unit/Container/TableIdentifierFactoryFactoryTest.php b/test/unit/Container/TableIdentifierFactoryFactoryTest.php index 791884fd..4a0d429a 100644 --- a/test/unit/Container/TableIdentifierFactoryFactoryTest.php +++ b/test/unit/Container/TableIdentifierFactoryFactoryTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; #[Group('unit')] #[CoversMethod(TableIdentifierFactoryFactory::class, '__invoke')] @@ -49,7 +50,6 @@ public function testInvokeCreatesFactoryWithConfiguredPrefix(): void $result = $factory($container); self::assertSame('backup', $result->getPrefix()); - self::assertSame('backup_users', $result('users')->getTable()); } public function testInvokeCreatesFactoryWithConfiguredSeparator(): void @@ -66,7 +66,18 @@ public function testInvokeCreatesFactoryWithConfiguredSeparator(): void $result = $factory($container); self::assertSame('__', $result->getSeparator()); - self::assertSame('backup__users', $result('users')->getTable()); + } + + public function testInvokeCreatesFactoryWithoutPrefixWhenConfigServiceIsNull(): void + { + $container = $this->createMock(ContainerInterface::class); + $container->method('has')->with('config')->willReturn(true); + $container->method('get')->with('config')->willReturn(null); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertNull($result->getPrefix()); } public function testInvokeCreatesFactoryWithoutPrefixWhenPrefixKeyIsAbsent(): void diff --git a/test/unit/Sql/TableIdentifierFactoryTest.php b/test/unit/Sql/TableIdentifierFactoryTest.php index d6a3a2ac..c407dd5c 100644 --- a/test/unit/Sql/TableIdentifierFactoryTest.php +++ b/test/unit/Sql/TableIdentifierFactoryTest.php @@ -14,28 +14,28 @@ #[CoversClass(TableIdentifierFactory::class)] final class TableIdentifierFactoryTest extends TestCase { - public function testGetDefaultPrefix(): void + public function testPrefixIsNullByDefault(): void { $factory = new TableIdentifierFactory(); self::assertNull($factory->getPrefix()); } - public function testGetPrefix(): void + public function testReturnsConfiguredPrefix(): void { $factory = new TableIdentifierFactory('backup'); self::assertSame('backup', $factory->getPrefix()); } - public function testGetDefaultSeparator(): void + public function testSeparatorDefaultsToUnderscore(): void { $factory = new TableIdentifierFactory('backup'); self::assertSame('_', $factory->getSeparator()); } - public function testGetSeparator(): void + public function testReturnsConfiguredSeparator(): void { $factory = new TableIdentifierFactory('backup', '__'); diff --git a/test/unit/Sql/TableIdentifierTest.php b/test/unit/Sql/TableIdentifierTest.php index 410b9a6c..7ca707b8 100644 --- a/test/unit/Sql/TableIdentifierTest.php +++ b/test/unit/Sql/TableIdentifierTest.php @@ -9,6 +9,7 @@ use PhpDbTest\TestAsset\ObjectToString; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use stdClass; use TypeError; @@ -19,6 +20,7 @@ * Tests for {@see TableIdentifier} */ #[CoversClass(TableIdentifier::class)] +#[Group('unit')] class TableIdentifierTest extends TestCase { public function testGetTable(): void @@ -148,7 +150,7 @@ public function testRejectsInvalidTable(mixed $invalidTable): void new TableIdentifier($invalidTable); } - #[DataProvider('invalidSchemaProvider')] + #[DataProvider('invalidNameArgumentProvider')] public function testRejectsInvalidSchema(mixed $invalidSchema): void { $this->expectException($invalidSchema === '' ? InvalidArgumentException::class : TypeError::class); @@ -156,7 +158,7 @@ public function testRejectsInvalidSchema(mixed $invalidSchema): void new TableIdentifier('foo', $invalidSchema); } - #[DataProvider('invalidSchemaProvider')] + #[DataProvider('invalidNameArgumentProvider')] public function testRejectsInvalidPrefix(mixed $invalidPrefix): void { $this->expectException($invalidPrefix === '' ? InvalidArgumentException::class : TypeError::class); @@ -172,8 +174,8 @@ public function testRejectsInvalidPrefix(mixed $invalidPrefix): void public static function invalidTableProvider(): array { return array_merge( - [[null]], - self::invalidSchemaProvider() + ['null' => [null]], + self::invalidNameArgumentProvider() ); } @@ -182,12 +184,12 @@ public static function invalidTableProvider(): array * * @return array[] */ - public static function invalidSchemaProvider(): array + public static function invalidNameArgumentProvider(): array { return [ - [''], - [new stdClass()], - [[]], + 'empty string' => [''], + 'object' => [new stdClass()], + 'array' => [[]], ]; } } From 8f3207a47d1b2f410aac1728e40b70fe87e5ecc2 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Wed, 22 Jul 2026 16:47:37 +1000 Subject: [PATCH 03/10] Added separator CONST to TableIdentifier Used separator CONST for Factory --- src/Sql/TableIdentifier.php | 4 +++- src/Sql/TableIdentifierFactory.php | 10 +++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Sql/TableIdentifier.php b/src/Sql/TableIdentifier.php index ec3f815e..07a1530d 100644 --- a/src/Sql/TableIdentifier.php +++ b/src/Sql/TableIdentifier.php @@ -6,13 +6,15 @@ class TableIdentifier { + public const SEPARATOR = '_'; + protected string $table; protected ?string $schema = null; protected ?string $prefix = null; - protected string $separator = '_'; + protected string $separator = self::SEPARATOR; public function __construct( string $table, diff --git a/src/Sql/TableIdentifierFactory.php b/src/Sql/TableIdentifierFactory.php index 28807092..18a0b99d 100644 --- a/src/Sql/TableIdentifierFactory.php +++ b/src/Sql/TableIdentifierFactory.php @@ -13,21 +13,17 @@ * factory share the same prefix — convenient for creating backup_* tables * during a migration. */ -final class TableIdentifierFactory +final readonly class TableIdentifierFactory { - private readonly ?string $prefix; - public function __construct( - ?string $prefix = null, - private readonly string $separator = '_', + private ?string $prefix = null, + private ?string $separator = TableIdentifier::SEPARATOR, ) { if ('' === $prefix) { throw new Exception\InvalidArgumentException( '$prefix must be a valid table prefix or null, empty string given' ); } - - $this->prefix = $prefix; } public function getPrefix(): ?string From 6c8b87cc8ae3fccc36412723ed69cd36c197b54e Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Wed, 22 Jul 2026 16:58:26 +1000 Subject: [PATCH 04/10] Updated testing to ensure a non-empty string for separator Updated documentation --- docs/book/sql-ddl/alter-drop.md | 11 ++- src/Sql/TableIdentifier.php | 9 +++ src/Sql/TableIdentifierFactory.php | 16 +++- .../TableIdentifierFactoryFactoryTest.php | 48 ++++++++++++ test/unit/Sql/TableIdentifierFactoryTest.php | 74 +++++++++++++++++++ test/unit/Sql/TableIdentifierTest.php | 22 ++++-- 6 files changed, 169 insertions(+), 11 deletions(-) diff --git a/docs/book/sql-ddl/alter-drop.md b/docs/book/sql-ddl/alter-drop.md index 3d2b9619..bd2bd895 100644 --- a/docs/book/sql-ddl/alter-drop.md +++ b/docs/book/sql-ddl/alter-drop.md @@ -448,8 +448,9 @@ $identifier = new TableIdentifier('users', null, 'backup', '__'); $identifier->getTable(); // 'backup__users' ``` -The prefix must be a non-empty string or `null`; the separator defaults to -`'_'`. +The prefix must be a non-empty string or `null`. The separator defaults to +`'_'` and must also be a non-empty string; passing `''` for either throws +`PhpDb\Sql\Exception\InvalidArgumentException`. ### The TableIdentifierFactory @@ -468,6 +469,12 @@ $factory('orders', 'sales')->getTable(); // 'backup_orders' $factory('users', null, 'archive')->getTable(); // 'archive_users' ``` +The configured separator is only applied when a prefix is passed, so a factory +created with a separator but no prefix produces unprefixed identifiers. Passing +`null` as the separator falls back to the default `'_'`; passing `''` — at +construction or at call time — throws +`PhpDb\Sql\Exception\InvalidArgumentException`. + `PhpDb\ConfigProvider` registers the factory as a container service. Configure the prefix and separator through the application config: diff --git a/src/Sql/TableIdentifier.php b/src/Sql/TableIdentifier.php index 07a1530d..68bf8248 100644 --- a/src/Sql/TableIdentifier.php +++ b/src/Sql/TableIdentifier.php @@ -16,6 +16,9 @@ class TableIdentifier protected string $separator = self::SEPARATOR; + /** + * @throws Exception\InvalidArgumentException If $table, $schema, $prefix or $separator is an empty string. + */ public function __construct( string $table, ?string $schema = null, @@ -50,6 +53,12 @@ public function __construct( $this->prefix = $prefix; } + if ('' === $separator) { + throw new Exception\InvalidArgumentException( + '$separator must be a valid table separator, empty string given' + ); + } + $this->separator = $separator; } diff --git a/src/Sql/TableIdentifierFactory.php b/src/Sql/TableIdentifierFactory.php index 18a0b99d..316803e4 100644 --- a/src/Sql/TableIdentifierFactory.php +++ b/src/Sql/TableIdentifierFactory.php @@ -15,6 +15,10 @@ */ final readonly class TableIdentifierFactory { + /** + * @param null|string $separator Null falls back to {@see TableIdentifier::SEPARATOR}. + * @throws Exception\InvalidArgumentException If $prefix or $separator is an empty string. + */ public function __construct( private ?string $prefix = null, private ?string $separator = TableIdentifier::SEPARATOR, @@ -24,6 +28,12 @@ public function __construct( '$prefix must be a valid table prefix or null, empty string given' ); } + + if ('' === $separator) { + throw new Exception\InvalidArgumentException( + '$separator must be a valid table separator, empty string given' + ); + } } public function getPrefix(): ?string @@ -33,7 +43,7 @@ public function getPrefix(): ?string public function getSeparator(): string { - return $this->separator; + return $this->separator ?? TableIdentifier::SEPARATOR; } /** @@ -41,6 +51,8 @@ public function getSeparator(): string * * A prefix or separator passed at call time takes precedence over the * configured one. + * + * @throws Exception\InvalidArgumentException If $prefix or $separator is an empty string. */ public function __invoke( string $table, @@ -48,6 +60,6 @@ public function __invoke( ?string $prefix = null, ?string $separator = null, ): TableIdentifier { - return new TableIdentifier($table, $schema, $prefix ?? $this->prefix, $separator ?? $this->separator); + return new TableIdentifier($table, $schema, $prefix ?? $this->prefix, $separator ?? $this->getSeparator()); } } diff --git a/test/unit/Container/TableIdentifierFactoryFactoryTest.php b/test/unit/Container/TableIdentifierFactoryFactoryTest.php index 4a0d429a..9f37ca6f 100644 --- a/test/unit/Container/TableIdentifierFactoryFactoryTest.php +++ b/test/unit/Container/TableIdentifierFactoryFactoryTest.php @@ -6,6 +6,7 @@ use Laminas\ServiceManager\ServiceManager; use PhpDb\Container\TableIdentifierFactoryFactory; +use PhpDb\Sql\Exception\InvalidArgumentException; use PhpDb\Sql\TableIdentifierFactory; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; @@ -80,6 +81,53 @@ public function testInvokeCreatesFactoryWithoutPrefixWhenConfigServiceIsNull(): self::assertNull($result->getPrefix()); } + public function testInvokeCreatesFactoryWithConfiguredSeparatorWithoutPrefix(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'separator' => '__', + ], + ]); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertNull($result->getPrefix()); + self::assertSame('__', $result->getSeparator()); + } + + public function testInvokeUsesDefaultSeparatorWhenSeparatorKeyIsAbsent(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + ], + ]); + + $factory = new TableIdentifierFactoryFactory(); + $result = $factory($container); + + self::assertSame('_', $result->getSeparator()); + } + + public function testInvokeRejectsEmptyStringSeparatorFromConfig(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'separator' => '', + ], + ]); + + $factory = new TableIdentifierFactoryFactory(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + $factory($container); + } + public function testInvokeCreatesFactoryWithoutPrefixWhenPrefixKeyIsAbsent(): void { $container = new ServiceManager(); diff --git a/test/unit/Sql/TableIdentifierFactoryTest.php b/test/unit/Sql/TableIdentifierFactoryTest.php index c407dd5c..28846ed9 100644 --- a/test/unit/Sql/TableIdentifierFactoryTest.php +++ b/test/unit/Sql/TableIdentifierFactoryTest.php @@ -35,6 +35,20 @@ public function testSeparatorDefaultsToUnderscore(): void self::assertSame('_', $factory->getSeparator()); } + public function testSeparatorDefaultsToUnderscoreWhenNoPrefixConfigured(): void + { + $factory = new TableIdentifierFactory(); + + self::assertSame('_', $factory->getSeparator()); + } + + public function testSeparatorFallsBackToDefaultWhenPassedAsNull(): void + { + $factory = new TableIdentifierFactory('backup', null); + + self::assertSame('_', $factory->getSeparator()); + } + public function testReturnsConfiguredSeparator(): void { $factory = new TableIdentifierFactory('backup', '__'); @@ -49,6 +63,20 @@ public function testRejectsEmptyStringPrefix(): void new TableIdentifierFactory(''); } + public function testRejectsEmptyStringSeparator(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + new TableIdentifierFactory('backup', ''); + } + + public function testRejectsEmptyStringSeparatorWithoutPrefix(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + new TableIdentifierFactory(null, ''); + } + public function testCreatesIdentifierWithConfiguredPrefix(): void { $factory = new TableIdentifierFactory('backup'); @@ -102,4 +130,50 @@ public function testCallTimeSeparatorOverridesConfiguredSeparator(): void self::assertSame('_', $tableIdentifier->getSeparator()); self::assertSame('backup_users', $tableIdentifier->getTable()); } + + public function testCallTimePrefixIsAppliedWhenNonePreconfigured(): void + { + $factory = new TableIdentifierFactory(); + $tableIdentifier = $factory('users', null, 'archive'); + + self::assertSame('archive', $tableIdentifier->getPrefix()); + self::assertSame('archive_users', $tableIdentifier->getTable()); + } + + public function testCallTimeSeparatorIsAppliedWhenNonePreconfigured(): void + { + $factory = new TableIdentifierFactory(); + $tableIdentifier = $factory('users', null, 'archive', '__'); + + self::assertSame('__', $tableIdentifier->getSeparator()); + self::assertSame('archive__users', $tableIdentifier->getTable()); + } + + public function testConfiguredSeparatorIsCarriedButUnusedWhenNoPrefixApplies(): void + { + $factory = new TableIdentifierFactory(null, '__'); + $tableIdentifier = $factory('users'); + + self::assertSame('__', $tableIdentifier->getSeparator()); + self::assertNull($tableIdentifier->getPrefix()); + self::assertSame('users', $tableIdentifier->getTable()); + } + + public function testRejectsEmptyStringCallTimePrefix(): void + { + $factory = new TableIdentifierFactory('backup'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$prefix must be a valid table prefix or null, empty string given'); + $factory('users', null, ''); + } + + public function testRejectsEmptyStringCallTimeSeparator(): void + { + $factory = new TableIdentifierFactory('backup'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + $factory('users', null, null, ''); + } } diff --git a/test/unit/Sql/TableIdentifierTest.php b/test/unit/Sql/TableIdentifierTest.php index 7ca707b8..10f667f1 100644 --- a/test/unit/Sql/TableIdentifierTest.php +++ b/test/unit/Sql/TableIdentifierTest.php @@ -107,13 +107,6 @@ public function testGetTableAppliesPrefixWithCustomSeparator(): void self::assertSame('backup__foo', $tableIdentifier->getTable()); } - public function testGetTableAppliesPrefixWithEmptySeparator(): void - { - $tableIdentifier = new TableIdentifier('foo', null, 'backup', ''); - - self::assertSame('backupfoo', $tableIdentifier->getTable()); - } - public function testGetTableIgnoresSeparatorWithoutPrefix(): void { $tableIdentifier = new TableIdentifier('foo', null, null, '__'); @@ -166,6 +159,21 @@ public function testRejectsInvalidPrefix(mixed $invalidPrefix): void new TableIdentifier('foo', 'bar', $invalidPrefix); } + #[DataProvider('invalidNameArgumentProvider')] + public function testRejectsInvalidSeparator(mixed $invalidSeparator): void + { + $this->expectException($invalidSeparator === '' ? InvalidArgumentException::class : TypeError::class); + /** @psalm-suppress MixedArgument */ + new TableIdentifier('foo', 'bar', 'backup', $invalidSeparator); + } + + public function testRejectsEmptyStringSeparatorWithoutPrefix(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + new TableIdentifier('foo', null, null, ''); + } + /** * Data provider * From 642f5bd120ede81af82e17ff3f11750594619e7f Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Thu, 23 Jul 2026 15:11:42 +1000 Subject: [PATCH 05/10] Fixes as per code review --- src/ConfigProvider.php | 2 +- .../TableIdentifierFactoryFactory.php | 3 +- src/Sql/TableIdentifier.php | 46 ++++++------------- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 4fea5d94..78d69ac0 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -4,7 +4,7 @@ namespace PhpDb; -final class ConfigProvider +final readonly class ConfigProvider { public const NAMED_ADAPTER_KEY = 'adapters'; diff --git a/src/Container/TableIdentifierFactoryFactory.php b/src/Container/TableIdentifierFactoryFactory.php index c48f0989..6f918434 100644 --- a/src/Container/TableIdentifierFactoryFactory.php +++ b/src/Container/TableIdentifierFactoryFactory.php @@ -4,6 +4,7 @@ namespace PhpDb\Container; +use PhpDb\Sql\TableIdentifier; use PhpDb\Sql\TableIdentifierFactory; use Psr\Container\ContainerInterface; @@ -37,7 +38,7 @@ public function __invoke(ContainerInterface $container): TableIdentifierFactory return new TableIdentifierFactory( $factoryConfig['prefix'] ?? null, - $factoryConfig['separator'] ?? '_', + $factoryConfig['separator'] ?? TableIdentifier::SEPARATOR, ); } } diff --git a/src/Sql/TableIdentifier.php b/src/Sql/TableIdentifier.php index 68bf8248..76337e8d 100644 --- a/src/Sql/TableIdentifier.php +++ b/src/Sql/TableIdentifier.php @@ -4,26 +4,18 @@ namespace PhpDb\Sql; -class TableIdentifier +final readonly class TableIdentifier { public const SEPARATOR = '_'; - protected string $table; - - protected ?string $schema = null; - - protected ?string $prefix = null; - - protected string $separator = self::SEPARATOR; - /** * @throws Exception\InvalidArgumentException If $table, $schema, $prefix or $separator is an empty string. */ public function __construct( - string $table, - ?string $schema = null, - ?string $prefix = null, - string $separator = '_', + protected string $table, + protected ?string $schema = null, + protected ?string $prefix = null, + protected ?string $separator = self::SEPARATOR, ) { if ('' === $table) { throw new Exception\InvalidArgumentException( @@ -31,26 +23,16 @@ public function __construct( ); } - $this->table = $table; - - if ($schema !== null) { - if ('' === $schema) { - throw new Exception\InvalidArgumentException( - '$schema must be a valid schema name or null, empty string given' - ); - } - - $this->schema = $schema; + if ('' === $schema) { + throw new Exception\InvalidArgumentException( + '$schema must be a valid schema name or null, empty string given' + ); } - if ($prefix !== null) { - if ('' === $prefix) { - throw new Exception\InvalidArgumentException( - '$prefix must be a valid table prefix or null, empty string given' - ); - } - - $this->prefix = $prefix; + if ('' === $prefix) { + throw new Exception\InvalidArgumentException( + '$prefix must be a valid table prefix or null, empty string given' + ); } if ('' === $separator) { @@ -58,8 +40,6 @@ public function __construct( '$separator must be a valid table separator, empty string given' ); } - - $this->separator = $separator; } /** From bb0926025aaa5c392f22004ddfd827e2c711ad0c Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Sun, 23 Aug 2026 23:19:04 +1000 Subject: [PATCH 06/10] Align table prefix changes with the Mago QA toolchain Apply mago formatting to the new TableIdentifier factory classes and tests, declare the container and validation exceptions propagated by TableIdentifierFactoryFactory::__invoke(), and baseline the linter's prefer-test-attribute, assertion-style and literal-named-argument findings for the new test classes, matching the rest of the suite. --- lint-baseline.toml | 302 +++++++++++++++++- .../TableIdentifierFactoryFactory.php | 8 + src/Sql/TableIdentifierFactory.php | 4 +- .../TableIdentifierFactoryFactoryTest.php | 62 ++-- test/unit/Sql/TableIdentifierFactoryTest.php | 168 +++++----- 5 files changed, 426 insertions(+), 118 deletions(-) diff --git a/lint-baseline.toml b/lint-baseline.toml index d62c7073..99046f37 100644 --- a/lint-baseline.toml +++ b/lint-baseline.toml @@ -2808,6 +2808,66 @@ code = "prefer-test-attribute" message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeThrowsWhenContainerHasNoConfig`." count = 1 +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 9 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithConfiguredPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithConfiguredSeparatorWithoutPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithConfiguredSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithoutPrefixWhenConfigIsEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithoutPrefixWhenConfigServiceIsNull`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithoutPrefixWhenContainerHasNoConfig`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesFactoryWithoutPrefixWhenPrefixKeyIsAbsent`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeRejectsEmptyStringSeparatorFromConfig`." +count = 1 + +[[issues]] +file = "test/unit/Container/TableIdentifierFactoryFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeUsesDefaultSeparatorWhenSeparatorKeyIsAbsent`." +count = 1 + [[issues]] file = "test/unit/Exception/ContainerExceptionTest.php" code = "assertion-style" @@ -7668,11 +7728,179 @@ code = "strict-assertions" message = "Use strict assertions in PHPUnit tests." count = 1 +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 25 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "literal-named-argument" +message = "Literal argument `''` should be passed as a named argument for clarity." +count = 2 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "literal-named-argument" +message = "Literal argument `'_'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "literal-named-argument" +message = "Literal argument `'__'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "literal-named-argument" +message = "Literal argument `'archive'` should be passed as a named argument for clarity." +count = 3 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "literal-named-argument" +message = "Literal argument `'public'` should be passed as a named argument for clarity." +count = 2 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "literal-named-argument" +message = "Literal argument `null` should be passed as a named argument for clarity." +count = 8 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallTimePrefixIsAppliedWhenNonePreconfigured`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallTimePrefixOverridesConfiguredPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallTimeSeparatorIsAppliedWhenNonePreconfigured`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallTimeSeparatorOverridesConfiguredSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConfiguredSeparatorIsCarriedButUnusedWhenNoPrefixApplies`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreatesIdentifierWithConfiguredPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreatesIdentifierWithConfiguredSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreatesIdentifierWithSchema`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreatesIdentifierWithoutPrefixWhenNoneConfigured`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrefixIsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsEmptyStringCallTimePrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsEmptyStringCallTimeSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsEmptyStringPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsEmptyStringSeparatorWithoutPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsEmptyStringSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testReturnsConfiguredPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testReturnsConfiguredSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSeparatorDefaultsToUnderscoreWhenNoPrefixConfigured`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSeparatorDefaultsToUnderscore`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSeparatorFallsBackToDefaultWhenPassedAsNull`." +count = 1 + [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" code = "assertion-style" message = "Inconsistent assertions style." -count = 7 +count = 17 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDefaultPrefix`." +count = 1 [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" @@ -7680,6 +7908,18 @@ code = "prefer-test-attribute" message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDefaultSchema`." count = 1 +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDefaultSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetPrefix`." +count = 1 + [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" code = "prefer-test-attribute" @@ -7692,24 +7932,84 @@ code = "prefer-test-attribute" message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSchema`." count = 1 +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableAndSchemaAppliesPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableAndSchemaWithoutPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableAppliesPrefixWithCustomSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableAppliesPrefixWithDefaultSeparator`." +count = 1 + [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" code = "prefer-test-attribute" message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableFromObjectStringCast`." count = 1 +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableIgnoresSeparatorWithoutPrefix`." +count = 1 + [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" code = "prefer-test-attribute" message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTable`." count = 1 +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetUnprefixedTableReturnsTableAsGiven`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsEmptyStringSeparatorWithoutPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsInvalidPrefix`." +count = 1 + [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" code = "prefer-test-attribute" message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsInvalidSchema`." count = 1 +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsInvalidSeparator`." +count = 1 + [[issues]] file = "test/unit/Sql/TableIdentifierTest.php" code = "prefer-test-attribute" diff --git a/src/Container/TableIdentifierFactoryFactory.php b/src/Container/TableIdentifierFactoryFactory.php index 6f918434..d79fcd63 100644 --- a/src/Container/TableIdentifierFactoryFactory.php +++ b/src/Container/TableIdentifierFactoryFactory.php @@ -4,9 +4,12 @@ namespace PhpDb\Container; +use PhpDb\Sql\Exception\InvalidArgumentException; use PhpDb\Sql\TableIdentifier; use PhpDb\Sql\TableIdentifierFactory; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; /** * Creates a {@see TableIdentifierFactory} configured from the application @@ -28,6 +31,11 @@ */ final class TableIdentifierFactoryFactory { + /** + * @throws InvalidArgumentException If the configured prefix or separator is an empty string. + * @throws NotFoundExceptionInterface If the config service cannot be resolved. + * @throws ContainerExceptionInterface If retrieving the config service fails. + */ public function __invoke(ContainerInterface $container): TableIdentifierFactory { /** @var array $config */ diff --git a/src/Sql/TableIdentifierFactory.php b/src/Sql/TableIdentifierFactory.php index 316803e4..af0d23c1 100644 --- a/src/Sql/TableIdentifierFactory.php +++ b/src/Sql/TableIdentifierFactory.php @@ -25,13 +25,13 @@ public function __construct( ) { if ('' === $prefix) { throw new Exception\InvalidArgumentException( - '$prefix must be a valid table prefix or null, empty string given' + '$prefix must be a valid table prefix or null, empty string given', ); } if ('' === $separator) { throw new Exception\InvalidArgumentException( - '$separator must be a valid table separator, empty string given' + '$separator must be a valid table separator, empty string given', ); } } diff --git a/test/unit/Container/TableIdentifierFactoryFactoryTest.php b/test/unit/Container/TableIdentifierFactoryFactoryTest.php index 9f37ca6f..af57a627 100644 --- a/test/unit/Container/TableIdentifierFactoryFactoryTest.php +++ b/test/unit/Container/TableIdentifierFactoryFactoryTest.php @@ -17,56 +17,62 @@ #[CoversMethod(TableIdentifierFactoryFactory::class, '__invoke')] final class TableIdentifierFactoryFactoryTest extends TestCase { - public function testInvokeCreatesFactoryWithoutPrefixWhenContainerHasNoConfig(): void + public function testInvokeCreatesFactoryWithConfiguredPrefix(): void { $container = new ServiceManager(); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + ], + ]); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); - self::assertNull($result->getPrefix()); + self::assertSame('backup', $result->getPrefix()); } - public function testInvokeCreatesFactoryWithoutPrefixWhenConfigIsEmpty(): void + public function testInvokeCreatesFactoryWithConfiguredSeparator(): void { $container = new ServiceManager(); - $container->setService('config', []); + $container->setService('config', [ + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + 'separator' => '__', + ], + ]); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); - self::assertNull($result->getPrefix()); + self::assertSame('__', $result->getSeparator()); } - public function testInvokeCreatesFactoryWithConfiguredPrefix(): void + public function testInvokeCreatesFactoryWithConfiguredSeparatorWithoutPrefix(): void { $container = new ServiceManager(); $container->setService('config', [ TableIdentifierFactory::class => [ - 'prefix' => 'backup', + 'separator' => '__', ], ]); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); - self::assertSame('backup', $result->getPrefix()); + self::assertNull($result->getPrefix()); + self::assertSame('__', $result->getSeparator()); } - public function testInvokeCreatesFactoryWithConfiguredSeparator(): void + public function testInvokeCreatesFactoryWithoutPrefixWhenConfigIsEmpty(): void { $container = new ServiceManager(); - $container->setService('config', [ - TableIdentifierFactory::class => [ - 'prefix' => 'backup', - 'separator' => '__', - ], - ]); + $container->setService('config', []); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); - self::assertSame('__', $result->getSeparator()); + self::assertNull($result->getPrefix()); } public function testInvokeCreatesFactoryWithoutPrefixWhenConfigServiceIsNull(): void @@ -81,35 +87,27 @@ public function testInvokeCreatesFactoryWithoutPrefixWhenConfigServiceIsNull(): self::assertNull($result->getPrefix()); } - public function testInvokeCreatesFactoryWithConfiguredSeparatorWithoutPrefix(): void + public function testInvokeCreatesFactoryWithoutPrefixWhenContainerHasNoConfig(): void { $container = new ServiceManager(); - $container->setService('config', [ - TableIdentifierFactory::class => [ - 'separator' => '__', - ], - ]); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); self::assertNull($result->getPrefix()); - self::assertSame('__', $result->getSeparator()); } - public function testInvokeUsesDefaultSeparatorWhenSeparatorKeyIsAbsent(): void + public function testInvokeCreatesFactoryWithoutPrefixWhenPrefixKeyIsAbsent(): void { $container = new ServiceManager(); $container->setService('config', [ - TableIdentifierFactory::class => [ - 'prefix' => 'backup', - ], + TableIdentifierFactory::class => [], ]); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); - self::assertSame('_', $result->getSeparator()); + self::assertNull($result->getPrefix()); } public function testInvokeRejectsEmptyStringSeparatorFromConfig(): void @@ -128,16 +126,18 @@ public function testInvokeRejectsEmptyStringSeparatorFromConfig(): void $factory($container); } - public function testInvokeCreatesFactoryWithoutPrefixWhenPrefixKeyIsAbsent(): void + public function testInvokeUsesDefaultSeparatorWhenSeparatorKeyIsAbsent(): void { $container = new ServiceManager(); $container->setService('config', [ - TableIdentifierFactory::class => [], + TableIdentifierFactory::class => [ + 'prefix' => 'backup', + ], ]); $factory = new TableIdentifierFactoryFactory(); $result = $factory($container); - self::assertNull($result->getPrefix()); + self::assertSame('_', $result->getSeparator()); } } diff --git a/test/unit/Sql/TableIdentifierFactoryTest.php b/test/unit/Sql/TableIdentifierFactoryTest.php index 28846ed9..a3139aed 100644 --- a/test/unit/Sql/TableIdentifierFactoryTest.php +++ b/test/unit/Sql/TableIdentifierFactoryTest.php @@ -14,67 +14,50 @@ #[CoversClass(TableIdentifierFactory::class)] final class TableIdentifierFactoryTest extends TestCase { - public function testPrefixIsNullByDefault(): void - { - $factory = new TableIdentifierFactory(); - - self::assertNull($factory->getPrefix()); - } - - public function testReturnsConfiguredPrefix(): void - { - $factory = new TableIdentifierFactory('backup'); - - self::assertSame('backup', $factory->getPrefix()); - } - - public function testSeparatorDefaultsToUnderscore(): void + public function testCallTimePrefixIsAppliedWhenNonePreconfigured(): void { - $factory = new TableIdentifierFactory('backup'); + $factory = new TableIdentifierFactory(); + $tableIdentifier = $factory('users', null, 'archive'); - self::assertSame('_', $factory->getSeparator()); + self::assertSame('archive', $tableIdentifier->getPrefix()); + self::assertSame('archive_users', $tableIdentifier->getTable()); } - public function testSeparatorDefaultsToUnderscoreWhenNoPrefixConfigured(): void + public function testCallTimePrefixOverridesConfiguredPrefix(): void { - $factory = new TableIdentifierFactory(); + $factory = new TableIdentifierFactory('backup'); + $tableIdentifier = $factory('users', null, 'archive'); - self::assertSame('_', $factory->getSeparator()); + self::assertSame('archive', $tableIdentifier->getPrefix()); + self::assertSame('archive_users', $tableIdentifier->getTable()); } - public function testSeparatorFallsBackToDefaultWhenPassedAsNull(): void + public function testCallTimeSeparatorIsAppliedWhenNonePreconfigured(): void { - $factory = new TableIdentifierFactory('backup', null); + $factory = new TableIdentifierFactory(); + $tableIdentifier = $factory('users', null, 'archive', '__'); - self::assertSame('_', $factory->getSeparator()); + self::assertSame('__', $tableIdentifier->getSeparator()); + self::assertSame('archive__users', $tableIdentifier->getTable()); } - public function testReturnsConfiguredSeparator(): void + public function testCallTimeSeparatorOverridesConfiguredSeparator(): void { - $factory = new TableIdentifierFactory('backup', '__'); - - self::assertSame('__', $factory->getSeparator()); - } + $factory = new TableIdentifierFactory('backup', '__'); + $tableIdentifier = $factory('users', null, null, '_'); - public function testRejectsEmptyStringPrefix(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('$prefix must be a valid table prefix or null, empty string given'); - new TableIdentifierFactory(''); + self::assertSame('_', $tableIdentifier->getSeparator()); + self::assertSame('backup_users', $tableIdentifier->getTable()); } - public function testRejectsEmptyStringSeparator(): void + public function testConfiguredSeparatorIsCarriedButUnusedWhenNoPrefixApplies(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); - new TableIdentifierFactory('backup', ''); - } + $factory = new TableIdentifierFactory(null, '__'); + $tableIdentifier = $factory('users'); - public function testRejectsEmptyStringSeparatorWithoutPrefix(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); - new TableIdentifierFactory(null, ''); + self::assertSame('__', $tableIdentifier->getSeparator()); + self::assertNull($tableIdentifier->getPrefix()); + self::assertSame('users', $tableIdentifier->getTable()); } public function testCreatesIdentifierWithConfiguredPrefix(): void @@ -87,14 +70,6 @@ public function testCreatesIdentifierWithConfiguredPrefix(): void self::assertNull($tableIdentifier->getSchema()); } - public function testCreatesIdentifierWithSchema(): void - { - $factory = new TableIdentifierFactory('backup'); - $tableIdentifier = $factory('users', 'public'); - - self::assertSame(['backup_users', 'public'], $tableIdentifier->getTableAndSchema()); - } - public function testCreatesIdentifierWithConfiguredSeparator(): void { $factory = new TableIdentifierFactory('backup', '__'); @@ -113,67 +88,92 @@ public function testCreatesIdentifierWithoutPrefixWhenNoneConfigured(): void self::assertSame('users', $tableIdentifier->getTable()); } - public function testCallTimePrefixOverridesConfiguredPrefix(): void + public function testCreatesIdentifierWithSchema(): void { $factory = new TableIdentifierFactory('backup'); - $tableIdentifier = $factory('users', null, 'archive'); + $tableIdentifier = $factory('users', 'public'); - self::assertSame('archive', $tableIdentifier->getPrefix()); - self::assertSame('archive_users', $tableIdentifier->getTable()); + self::assertSame(['backup_users', 'public'], $tableIdentifier->getTableAndSchema()); } - public function testCallTimeSeparatorOverridesConfiguredSeparator(): void + public function testPrefixIsNullByDefault(): void { - $factory = new TableIdentifierFactory('backup', '__'); - $tableIdentifier = $factory('users', null, null, '_'); + $factory = new TableIdentifierFactory(); - self::assertSame('_', $tableIdentifier->getSeparator()); - self::assertSame('backup_users', $tableIdentifier->getTable()); + self::assertNull($factory->getPrefix()); } - public function testCallTimePrefixIsAppliedWhenNonePreconfigured(): void + public function testRejectsEmptyStringCallTimePrefix(): void { - $factory = new TableIdentifierFactory(); - $tableIdentifier = $factory('users', null, 'archive'); + $factory = new TableIdentifierFactory('backup'); - self::assertSame('archive', $tableIdentifier->getPrefix()); - self::assertSame('archive_users', $tableIdentifier->getTable()); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$prefix must be a valid table prefix or null, empty string given'); + $factory('users', null, ''); } - public function testCallTimeSeparatorIsAppliedWhenNonePreconfigured(): void + public function testRejectsEmptyStringCallTimeSeparator(): void { - $factory = new TableIdentifierFactory(); - $tableIdentifier = $factory('users', null, 'archive', '__'); + $factory = new TableIdentifierFactory('backup'); - self::assertSame('__', $tableIdentifier->getSeparator()); - self::assertSame('archive__users', $tableIdentifier->getTable()); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + $factory('users', null, null, ''); } - public function testConfiguredSeparatorIsCarriedButUnusedWhenNoPrefixApplies(): void + public function testRejectsEmptyStringPrefix(): void { - $factory = new TableIdentifierFactory(null, '__'); - $tableIdentifier = $factory('users'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$prefix must be a valid table prefix or null, empty string given'); + new TableIdentifierFactory(''); + } - self::assertSame('__', $tableIdentifier->getSeparator()); - self::assertNull($tableIdentifier->getPrefix()); - self::assertSame('users', $tableIdentifier->getTable()); + public function testRejectsEmptyStringSeparator(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + new TableIdentifierFactory('backup', ''); } - public function testRejectsEmptyStringCallTimePrefix(): void + public function testRejectsEmptyStringSeparatorWithoutPrefix(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); + new TableIdentifierFactory(null, ''); + } + + public function testReturnsConfiguredPrefix(): void { $factory = new TableIdentifierFactory('backup'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('$prefix must be a valid table prefix or null, empty string given'); - $factory('users', null, ''); + self::assertSame('backup', $factory->getPrefix()); } - public function testRejectsEmptyStringCallTimeSeparator(): void + public function testReturnsConfiguredSeparator(): void + { + $factory = new TableIdentifierFactory('backup', '__'); + + self::assertSame('__', $factory->getSeparator()); + } + + public function testSeparatorDefaultsToUnderscore(): void { $factory = new TableIdentifierFactory('backup'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('$separator must be a valid table separator, empty string given'); - $factory('users', null, null, ''); + self::assertSame('_', $factory->getSeparator()); + } + + public function testSeparatorDefaultsToUnderscoreWhenNoPrefixConfigured(): void + { + $factory = new TableIdentifierFactory(); + + self::assertSame('_', $factory->getSeparator()); + } + + public function testSeparatorFallsBackToDefaultWhenPassedAsNull(): void + { + $factory = new TableIdentifierFactory('backup', null); + + self::assertSame('_', $factory->getSeparator()); } } From 766527e9bd9364b26c9b0d76e401ec026b1098ee Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Sun, 23 Aug 2026 23:19:04 +1000 Subject: [PATCH 07/10] Update Composer dependencies --- composer.lock | 361 +++++++++++++++++++++++++------------------------- 1 file changed, 183 insertions(+), 178 deletions(-) diff --git a/composer.lock b/composer.lock index a4a79fbb..0f5edd82 100644 --- a/composer.lock +++ b/composer.lock @@ -57,16 +57,16 @@ }, { "name": "laminas/laminas-servicemanager", - "version": "4.5.0", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/laminas/laminas-servicemanager.git", - "reference": "a6996829c8ce55025cca1b57b1e8a8b165e3926c" + "reference": "11192d588876ad04ba2988984c77b4ecb5c771c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/a6996829c8ce55025cca1b57b1e8a8b165e3926c", - "reference": "a6996829c8ce55025cca1b57b1e8a8b165e3926c", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/11192d588876ad04ba2988984c77b4ecb5c771c2", + "reference": "11192d588876ad04ba2988984c77b4ecb5c771c2", "shasum": "" }, "require": { @@ -130,7 +130,7 @@ "chat": "https://laminas.dev/chat", "forum": "https://discourse.laminas.dev", "issues": "https://github.com/laminas/laminas-servicemanager/issues", - "source": "https://github.com/laminas/laminas-servicemanager/tree/4.5.0" + "source": "https://github.com/laminas/laminas-servicemanager/tree/4.5.1" }, "funding": [ { @@ -138,7 +138,7 @@ "type": "community_bridge" } ], - "time": "2025-10-14T09:41:04+00:00" + "time": "2026-05-12T09:53:32+00:00" }, { "name": "laminas/laminas-stdlib", @@ -201,20 +201,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "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/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -253,9 +252,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.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "psr/container", @@ -539,16 +538,16 @@ }, { "name": "laminas/laminas-hydrator", - "version": "4.18.0", + "version": "4.19.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-hydrator.git", - "reference": "ab208d1b8a2dc4251182a6cd2d123ccbc56eda50" + "reference": "bf6c980013f70b1141aadef979cf8b66241fff4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/ab208d1b8a2dc4251182a6cd2d123ccbc56eda50", - "reference": "ab208d1b8a2dc4251182a6cd2d123ccbc56eda50", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/bf6c980013f70b1141aadef979cf8b66241fff4f", + "reference": "bf6c980013f70b1141aadef979cf8b66241fff4f", "shasum": "" }, "require": { @@ -612,24 +611,24 @@ "type": "community_bridge" } ], - "time": "2026-01-13T10:10:41+00:00" + "time": "2026-05-13T22:31:51+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -664,15 +663,15 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "phar-io/manifest", @@ -889,16 +888,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.4.3", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "b641dde59d969ea42eed70a39f9b51950bc96878" + "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878", - "reference": "b641dde59d969ea42eed70a39f9b51950bc96878", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", + "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", "shasum": "" }, "require": { @@ -909,7 +908,7 @@ "ext-reflection": "*", "ext-spl": "*", "ext-tokenizer": "*", - "php": "^8.1", + "php": "^8.2", "phpbench/container": "^2.2", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", @@ -923,14 +922,15 @@ "require-dev": { "dantleech/invoke": "^2.0", "ergebnis/composer-normalize": "^2.39", - "jangregor/phpstan-prophecy": "^1.0", + "jangregor/phpstan-prophecy": "^2.0", "php-cs-fixer/shim": "^3.9", "phpspec/prophecy": "^1.22", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.4 || ^11.0", - "rector/rector": "^1.2", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^11.5", + "rector/rector": "^2.2", + "sebastian/exporter": "^6.3.2", "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" }, @@ -975,7 +975,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.4.3" + "source": "https://github.com/phpbench/phpbench/tree/1.7.0" }, "funding": [ { @@ -983,15 +983,15 @@ "type": "github" } ], - "time": "2025-11-06T19:07:31+00:00" + "time": "2026-06-08T19:09:20+00:00" }, { "name": "phpstan/phpstan", - "version": "2.2.7", + "version": "2.2.9", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", - "reference": "692db47b9dddb0487934e5236e77d48594aef921", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", + "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", "shasum": "" }, "require": { @@ -1047,7 +1047,7 @@ "type": "github" } ], - "time": "2026-07-29T17:39:32+00:00" + "time": "2026-08-22T07:38:16+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1141,28 +1141,28 @@ }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1190,15 +1190,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", @@ -1386,42 +1398,43 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.46", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", - "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.11", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.2", + "sebastian/comparator": "^6.3.3", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -1467,31 +1480,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2025-12-06T08:01:15+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "psr/cache", @@ -1594,21 +1591,21 @@ }, { "name": "rector/rector", - "version": "2.3.1", + "version": "2.6.3", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "9afc1bb43571b25629f353c61a9315b5ef31383a" + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/9afc1bb43571b25629f353c61a9315b5ef31383a", - "reference": "9afc1bb43571b25629f353c61a9315b5ef31383a", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.33" + "phpstan/phpstan": "^2.2.6" }, "conflict": { "rector/rector-doctrine": "*", @@ -1642,7 +1639,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.1" + "source": "https://github.com/rectorphp/rector/tree/2.6.3" }, "funding": [ { @@ -1650,7 +1647,7 @@ "type": "github" } ], - "time": "2026-01-13T15:13:58+00:00" + "time": "2026-08-18T22:01:18+00:00" }, { "name": "sebastian/cli-parser", @@ -1824,16 +1821,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.2", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { @@ -1892,7 +1889,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { @@ -1912,7 +1909,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:07:46+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/complexity", @@ -2640,16 +2637,16 @@ }, { "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": { @@ -2688,7 +2685,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": [ { @@ -2700,7 +2697,7 @@ "type": "tidelift" } ], - "time": "2024-07-11T14:55:45+00:00" + "time": "2026-06-12T11:32:29+00:00" }, { "name": "staabm/side-effects-detector", @@ -2756,16 +2753,16 @@ }, { "name": "symfony/console", - "version": "v7.4.3", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" + "reference": "962e18f09ebe68a49039b4c82fc0ea4871824fca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "url": "https://api.github.com/repos/symfony/console/zipball/962e18f09ebe68a49039b4c82fc0ea4871824fca", + "reference": "962e18f09ebe68a49039b4c82fc0ea4871824fca", "shasum": "" }, "require": { @@ -2830,7 +2827,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.3" + "source": "https://github.com/symfony/console/tree/v7.4.17" }, "funding": [ { @@ -2850,20 +2847,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -2876,7 +2873,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2901,7 +2898,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.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -2912,25 +2909,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "ee7bc7bca4c7079b88e57d5000aeeb20df570c8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ee7bc7bca4c7079b88e57d5000aeeb20df570c8d", + "reference": "ee7bc7bca4c7079b88e57d5000aeeb20df570c8d", "shasum": "" }, "require": { @@ -2967,7 +2968,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.17" }, "funding": [ { @@ -2987,20 +2988,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/finder", - "version": "v7.4.3", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06" + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06", + "url": "https://api.github.com/repos/symfony/finder/zipball/5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", "shasum": "" }, "require": { @@ -3035,7 +3036,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.3" + "source": "https://github.com/symfony/finder/tree/v7.4.17" }, "funding": [ { @@ -3055,20 +3056,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.4.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b38026df55197f9e39a44f3215788edf83187b80" + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", - "reference": "b38026df55197f9e39a44f3215788edf83187b80", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", "shasum": "" }, "require": { @@ -3106,7 +3107,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.8" }, "funding": [ { @@ -3126,20 +3127,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -3189,7 +3190,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -3209,20 +3210,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -3271,7 +3272,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -3291,20 +3292,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -3356,7 +3357,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -3376,20 +3377,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -3441,7 +3442,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -3461,20 +3462,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/process", - "version": "v7.4.3", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f" + "reference": "058d17fc284cce14efb2385783b55014a461b176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f", - "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f", + "url": "https://api.github.com/repos/symfony/process/zipball/058d17fc284cce14efb2385783b55014a461b176", + "reference": "058d17fc284cce14efb2385783b55014a461b176", "shasum": "" }, "require": { @@ -3506,7 +3507,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.3" + "source": "https://github.com/symfony/process/tree/v7.4.17" }, "funding": [ { @@ -3526,20 +3527,20 @@ "type": "tidelift" } ], - "time": "2025-12-19T10:00:43+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { @@ -3557,7 +3558,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -3593,7 +3594,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -3613,20 +3614,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { @@ -3684,7 +3685,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -3704,7 +3705,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "theseer/tokenizer", @@ -3758,16 +3759,16 @@ }, { "name": "webmozart/assert", - "version": "2.1.2", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649" + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", - "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", "shasum": "" }, "require": { @@ -3783,7 +3784,11 @@ }, "type": "library", "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, "branch-alias": { + "dev-master": "2.0-dev", "dev-feature/2-0": "2.0-dev" } }, @@ -3814,9 +3819,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.2" + "source": "https://github.com/webmozarts/assert/tree/2.4.1" }, - "time": "2026-01-13T14:02:24+00:00" + "time": "2026-06-15T15:31:57+00:00" }, { "name": "webmozart/glob", From 771889fbabc0b3b02b25df8ac142b54505d4c339 Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Sun, 23 Aug 2026 23:28:12 +1000 Subject: [PATCH 08/10] Rename CI workflow to continuous-integration.yml Aligns the caller workflow filename and display name with the convention used across the php-db org and prescribed by the phpdb-qa-tools README. --- .github/workflows/{ci.yml => continuous-integration.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{ci.yml => continuous-integration.yml} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/continuous-integration.yml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows/continuous-integration.yml From c5a121468a1ab61a734f7125a0dd235d83955a2c Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Sun, 23 Aug 2026 23:49:48 +1000 Subject: [PATCH 09/10] Enable Codecov and Infection in CI; drop PHP 8.2 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turn on the qa-tools codecov and mutation-test jobs, which need secrets: inherit and a coverage-php-version to nominate the canonical matrix leg — without the latter no clover artifact is produced and both jobs break. Add infection/infection, infection.json5.dist and a mutation-test composer script. Infection 0.33+ requires PHP ^8.3, so dropping 8.2 is what allows 0.34.x; php constraints in composer.json, mago.toml, the CI matrix and the Docker deployment docs move with it. PHP 8.3 also permits typed class constants, so TableIdentifier::SEPARATOR gains one. min-msi and min-covered-msi are set to 80 against a measured baseline of 86% (2706 mutants, 100% mutation code coverage). --- .github/workflows/continuous-integration.yml | 10 +- .gitignore | 3 + codecov.yml | 20 + composer.json | 9 +- composer.lock | 1635 ++++++++++++++++-- docs/book/docker-deployment.md | 4 +- infection.json5.dist | 26 + mago.toml | 2 +- src/Sql/TableIdentifier.php | 2 +- 9 files changed, 1596 insertions(+), 115 deletions(-) create mode 100644 codecov.yml create mode 100644 infection.json5.dist diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ad78810f..d11efbae 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,4 +1,4 @@ -name: CI +name: "Continuous Integration" on: push: @@ -7,6 +7,12 @@ on: jobs: qa: uses: php-db/phpdb-qa-tools/.github/workflows/continuous-integration.yml@0.1.x + secrets: inherit with: - php-versions: '["8.2", "8.3", "8.4", "8.5"]' + php-versions: '["8.3", "8.4", "8.5"]' run-integration: false + coverage-php-version: "8.4" + enable-codecov: true + enable-infection: true + min-msi: "80" + min-covered-msi: "80" diff --git a/.gitignore b/.gitignore index ecba80f1..aa2c7e67 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ /phpunit.xml /vendor/ /.vscode +/infection.json5 +/infection.log +/summary.log diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..8668e785 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,20 @@ +coverage: + status: + project: + default: + target: auto + threshold: 0% + base: auto + patch: + default: + target: auto + threshold: 0% + base: auto + +comment: + layout: "diff, flags, files" + behavior: default + require_changes: false + require_base: false + require_head: true + hide_project_coverage: false diff --git a/composer.json b/composer.json index eaa39168..9abfe5d4 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,10 @@ "config": { "sort-packages": true, "platform": { - "php": "8.2.99" + "php": "8.3.99" + }, + "allow-plugins": { + "infection/extension-installer": true } }, "extra": { @@ -26,11 +29,12 @@ } }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0", "laminas/laminas-servicemanager": "^3.0.0 || ^4.0.0", "laminas/laminas-stdlib": "^3.20.0" }, "require-dev": { + "infection/infection": "^0.34.1", "laminas/laminas-eventmanager": "^3.14.0", "laminas/laminas-hydrator": "^4.6.0", "php-db/phpdb-qa-tools": "0.1.x-dev", @@ -70,6 +74,7 @@ "mago lint --fix" ], "static-analysis": "mago analyze", + "mutation-test": "infection", "test": "phpunit --colors=always --testsuite \"unit test\"", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", "test-integration": "phpunit --colors=always --testsuite \"integration test\"", diff --git a/composer.lock b/composer.lock index 0f5edd82..2fbf9dc1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "abc7c3577be8528422816192cc5068fa", + "content-hash": "6600948742b11dc28f0a4193e849e4d8", "packages": [ { "name": "brick/varexporter", @@ -311,6 +311,236 @@ } ], "packages-dev": [ + { + "name": "colinodell/json5", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/colinodell/json5.git", + "reference": "5724d21bc5c910c2560af1b8915f0cc0163579c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/colinodell/json5/zipball/5724d21bc5c910c2560af1b8915f0cc0163579c8", + "reference": "5724d21bc5c910c2560af1b8915f0cc0163579c8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.0" + }, + "require-dev": { + "mikehaertl/php-shellcommand": "^1.7.0", + "phpstan/phpstan": "^1.10.57", + "scrutinizer/ocular": "^1.9", + "squizlabs/php_codesniffer": "^3.8.1", + "symfony/finder": "^6.0|^7.0", + "symfony/phpunit-bridge": "^7.0.3" + }, + "bin": [ + "bin/json5" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "files": [ + "src/global.php" + ], + "psr-4": { + "ColinODell\\Json5\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Developer" + } + ], + "description": "UTF-8 compatible JSON5 parser for PHP", + "homepage": "https://github.com/colinodell/json5", + "keywords": [ + "JSON5", + "json", + "json5_decode", + "json_decode" + ], + "support": { + "issues": "https://github.com/colinodell/json5/issues", + "source": "https://github.com/colinodell/json5/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + } + ], + "time": "2024-02-09T13:06:12+00:00" + }, + { + "name": "composer/pcre", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<2.2.2" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-07T11:47:49+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, { "name": "doctrine/annotations", "version": "2.0.2", @@ -465,6 +695,501 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "infection/abstract-testframework-adapter", + "version": "0.5.0", + "source": { + "type": "git", + "url": "https://github.com/infection/abstract-testframework-adapter.git", + "reference": "18925e20d15d1a5995bb85c9dc09e8751e1e069b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/abstract-testframework-adapter/zipball/18925e20d15d1a5995bb85c9dc09e8751e1e069b", + "reference": "18925e20d15d1a5995bb85c9dc09e8751e1e069b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^2.17", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\AbstractTestFramework\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Abstract Test Framework Adapter for Infection", + "support": { + "issues": "https://github.com/infection/abstract-testframework-adapter/issues", + "source": "https://github.com/infection/abstract-testframework-adapter/tree/0.5.0" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2021-08-17T18:49:12+00:00" + }, + { + "name": "infection/extension-installer", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/infection/extension-installer.git", + "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/extension-installer/zipball/9b351d2910b9a23ab4815542e93d541e0ca0cdcf", + "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.9 || ^2.0", + "friendsofphp/php-cs-fixer": "^2.18, <2.19", + "infection/infection": "^0.15.2", + "php-coveralls/php-coveralls": "^2.4", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.10", + "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpstan/phpstan-webmozart-assert": "^0.12.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.8" + }, + "type": "composer-plugin", + "extra": { + "class": "Infection\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Infection\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Infection Extension Installer", + "support": { + "issues": "https://github.com/infection/extension-installer/issues", + "source": "https://github.com/infection/extension-installer/tree/0.1.2" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2021-10-20T22:08:34+00:00" + }, + { + "name": "infection/include-interceptor", + "version": "0.2.5", + "source": { + "type": "git", + "url": "https://github.com/infection/include-interceptor.git", + "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/include-interceptor/zipball/0cc76d95a79d9832d74e74492b0a30139904bdf7", + "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7", + "shasum": "" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "infection/infection": "^0.15.0", + "phan/phan": "^2.4 || ^3", + "php-coveralls/php-coveralls": "^2.2", + "phpstan/phpstan": "^0.12.8", + "phpunit/phpunit": "^8.5", + "vimeo/psalm": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\StreamWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Stream Wrapper: Include Interceptor. Allows to replace included (autoloaded) file with another one.", + "support": { + "issues": "https://github.com/infection/include-interceptor/issues", + "source": "https://github.com/infection/include-interceptor/tree/0.2.5" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2021-08-09T10:03:57+00:00" + }, + { + "name": "infection/infection", + "version": "0.34.2", + "source": { + "type": "git", + "url": "https://github.com/infection/infection.git", + "reference": "18d9bef39fae250f202920d1453ab96e793e6637" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/infection/zipball/18d9bef39fae250f202920d1453ab96e793e6637", + "reference": "18d9bef39fae250f202920d1453ab96e793e6637", + "shasum": "" + }, + "require": { + "colinodell/json5": "^3.0", + "composer-runtime-api": "^2.0", + "composer/xdebug-handler": "^3.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "fidry/cpu-core-counter": "^1.0", + "infection/abstract-testframework-adapter": "^0.5.0", + "infection/extension-installer": "^0.1.0", + "infection/include-interceptor": "^0.2.5 || ^1.0.0", + "infection/mutator": "^0.4", + "justinrainbow/json-schema": "^6.0", + "nikic/php-parser": "^5.6.2", + "ondram/ci-detector": "^4.1.0", + "php": "^8.3", + "psr/log": "^2.0 || ^3.0", + "sanmai/di-container": "^0.1.16", + "sanmai/duoclock": "^0.1.0", + "sanmai/later": "^0.1.7", + "sanmai/pipeline": "^7.2", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/filesystem": "^6.4 || ^7.4 || ^8.0", + "symfony/finder": "^6.4 || ^7.4 || ^8.0", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^6.4 || ^7.4 || ^8.0", + "thecodingmachine/safe": "^v3.0", + "webmozart/assert": "^1.11 || ^2.0" + }, + "conflict": { + "antecedent/patchwork": "<2.1.25", + "dg/bypass-finals": "<1.4.1" + }, + "require-dev": { + "carthage-software/mago": "^1.20", + "ext-simplexml": "*", + "fidry/makefile": "^1.0", + "fig/log-test": "^1.2", + "phpat/phpat": "^0.12.4", + "phpbench/phpbench": "^1.4", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + "phpunit/phpunit": "^12.5.29", + "rector/rector": "^2.2.4", + "shipmonk/dead-code-detector": "^1.3", + "shipmonk/name-collision-detector": "^2.1", + "sidz/phpstan-rules": "^0.5.1", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0", + "thecodingmachine/phpstan-safe-rule": "^1.4", + "webmozarts/strict-phpunit": "^7.15" + }, + "bin": [ + "bin/infection" + ], + "type": "library", + "autoload": { + "psr-4": { + "Infection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com", + "homepage": "https://twitter.com/maks_rafalko" + }, + { + "name": "Oleg Zhulnev", + "homepage": "https://github.com/sidz" + }, + { + "name": "Gert de Pagter", + "homepage": "https://github.com/BackEndTea" + }, + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com", + "homepage": "https://twitter.com/tfidry" + }, + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://www.alexeykopytko.com" + }, + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.", + "keywords": [ + "coverage", + "mutant", + "mutation framework", + "mutation testing", + "testing", + "unit testing" + ], + "support": { + "issues": "https://github.com/infection/infection/issues", + "source": "https://github.com/infection/infection/tree/0.34.2" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2026-08-07T12:59:47+00:00" + }, + { + "name": "infection/mutator", + "version": "0.4.1", + "source": { + "type": "git", + "url": "https://github.com/infection/mutator.git", + "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/infection/mutator/zipball/3c976d721b02b32f851ee4e15d553ef1e9186d1d", + "reference": "3c976d721b02b32f851ee4e15d553ef1e9186d1d", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Infection\\Mutator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Maks Rafalko", + "email": "maks.rafalko@gmail.com" + } + ], + "description": "Mutator interface to implement custom mutators (mutation operators) for Infection", + "support": { + "issues": "https://github.com/infection/mutator/issues", + "source": "https://github.com/infection/mutator/tree/0.4.1" + }, + "funding": [ + { + "url": "https://github.com/infection", + "type": "github" + }, + { + "url": "https://opencollective.com/infection", + "type": "open_collective" + } + ], + "time": "2025-04-29T08:19:52+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.11.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "7e420a943a6fbc95e60e3cf67acfbee85b3b4da7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/7e420a943a6fbc95e60e3cf67acfbee85b3b4da7", + "reference": "7e420a943a6fbc95e60e3cf67acfbee85b3b4da7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "dev-main", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Danny van der Sluijs", + "email": "danny.vandersluijs@icloud.com", + "role": "Maintainer" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.11.0" + }, + "time": "2026-08-21T10:30:42+00:00" + }, { "name": "laminas/laminas-eventmanager", "version": "3.15.0", @@ -613,6 +1338,79 @@ ], "time": "2026-05-13T22:31:51+00:00" }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.14.0", @@ -667,11 +1465,89 @@ }, "funding": [ { - "url": "https://github.com/mnapoli", - "type": "github" + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+00:00" + }, + { + "name": "ondram/ci-detector", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/OndraM/ci-detector.git", + "reference": "8b0223b5ed235fd377c75fdd1bfcad05c0f168b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/OndraM/ci-detector/zipball/8b0223b5ed235fd377c75fdd1bfcad05c0f168b8", + "reference": "8b0223b5ed235fd377c75fdd1bfcad05c0f168b8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.13.2", + "lmc/coding-standard": "^3.0.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.1.0", + "phpstan/phpstan": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpunit/phpunit": "^9.6.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "OndraM\\CiDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Machulda", + "email": "ondrej.machulda@gmail.com" } ], - "time": "2026-08-11T10:17:44+00:00" + "description": "Detect continuous integration environment and provide unified access to properties of current build", + "keywords": [ + "CircleCI", + "Codeship", + "Wercker", + "adapter", + "appveyor", + "aws", + "aws codebuild", + "azure", + "azure devops", + "azure pipelines", + "bamboo", + "bitbucket", + "buddy", + "ci-info", + "codebuild", + "continuous integration", + "continuousphp", + "devops", + "drone", + "github", + "gitlab", + "interface", + "jenkins", + "pipelines", + "sourcehut", + "teamcity", + "travis" + ], + "support": { + "issues": "https://github.com/OndraM/ci-detector/issues", + "source": "https://github.com/OndraM/ci-detector/tree/4.2.0" + }, + "time": "2024-03-12T13:22:30+00:00" }, { "name": "phar-io/manifest", @@ -1442,22 +2318,301 @@ "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" }, - "bin": [ - "phpunit" - ], + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:52:39+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "rector/rector", + "version": "2.6.3", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.2.6" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.6.3" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2026-08-18T22:01:18+00:00" + }, + { + "name": "sanmai/di-container", + "version": "0.1.23", + "source": { + "type": "git", + "url": "https://github.com/sanmai/di-container.git", + "reference": "8cf59c091f33297389d0a5a27ea0d688df15c376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sanmai/di-container/zipball/8cf59c091f33297389d0a5a27ea0d688df15c376", + "reference": "8cf59c091f33297389d0a5a27ea0d688df15c376", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1.2 || ^2.0", + "sanmai/pipeline": "^7.10" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.31", + "php-coveralls/php-coveralls": "^2.4.1", + "phpbench/phpbench": "^1.4", + "phpstan/extension-installer": "^1.4", + "phpunit/phpunit": "^11.5.25", + "sanmai/phpstan-rules": "^0.3.10" + }, "type": "library", "extra": { "branch-alias": { - "dev-main": "11.5-dev" - } + "dev-main": "0.1.x-dev" + }, + "preferred-install": "dist" }, "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] + "psr-4": { + "DIContainer\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1465,189 +2620,232 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://github.com/sanmai" + }, + { + "name": "Maks Rafalko", + "homepage": "https://twitter.com/maks_rafalko" + }, + { + "name": "Théo FIDRY", + "homepage": "https://twitter.com/tfidry" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "dependency injection container with automatic constructor dependency resolution", "keywords": [ - "phpunit", - "testing", - "xunit" + "Autowiring", + "constructor di", + "di container", + "psr 11" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" + "issues": "https://github.com/sanmai/di-container/issues", + "source": "https://github.com/sanmai/di-container/tree/0.1.23" }, "funding": [ { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" + "url": "https://github.com/sanmai", + "type": "github" } ], - "time": "2026-07-06T14:52:39+00:00" + "time": "2026-08-11T00:58:41+00:00" }, { - "name": "psr/cache", - "version": "3.0.0", + "name": "sanmai/duoclock", + "version": "0.1.3", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + "url": "https://github.com/sanmai/DuoClock.git", + "reference": "47461e3ff65b7308635047831a55615652e7be1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "url": "https://api.github.com/repos/sanmai/DuoClock/zipball/47461e3ff65b7308635047831a55615652e7be1a", + "reference": "47461e3ff65b7308635047831a55615652e7be1a", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.2", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.29", + "php-coveralls/php-coveralls": "^2.4.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11.5.25", + "sanmai/phpstan-rules": "^0.3.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "preferred-install": "dist" }, "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "DuoClock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" } ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], + "description": "PHP time mocking for tests - PSR-20 clock with mockable sleep(), time(), and TimeSpy for PHPUnit testing", "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" + "issues": "https://github.com/sanmai/DuoClock/issues", + "source": "https://github.com/sanmai/DuoClock/tree/0.1.3" }, - "time": "2021-02-03T23:26:27+00:00" + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2025-12-26T06:12:34+00:00" }, { - "name": "psr/log", - "version": "3.0.2", + "name": "sanmai/later", + "version": "0.1.8", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "url": "https://github.com/sanmai/later.git", + "reference": "c56aeb8fa7fdf81eda2640a68b51884685963d13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/sanmai/later/zipball/c56aeb8fa7fdf81eda2640a68b51884685963d13", + "reference": "c56aeb8fa7fdf81eda2640a68b51884685963d13", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.2" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "friendsofphp/php-cs-fixer": "^3.35.1", + "infection/infection": ">=0.27.6", + "phan/phan": ">=2", + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": ">=1.4.5", + "phpunit/phpunit": ">=9.5 <10", + "vimeo/psalm": ">=2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-main": "0.1.x-dev" } }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "Psr\\Log\\": "src" + "Later\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], + "description": "Later: deferred wrapper object", "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "issues": "https://github.com/sanmai/later/issues", + "source": "https://github.com/sanmai/later/tree/0.1.8" }, - "time": "2024-09-11T13:17:53+00:00" + "funding": [ + { + "url": "https://github.com/sanmai", + "type": "github" + } + ], + "time": "2026-06-29T07:24:33+00:00" }, { - "name": "rector/rector", - "version": "2.6.3", + "name": "sanmai/pipeline", + "version": "7.10", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58" + "url": "https://github.com/sanmai/pipeline.git", + "reference": "a8e4e57a6031efc7a92defd13fed9c1c4c73fc71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", - "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/a8e4e57a6031efc7a92defd13fed9c1c4c73fc71", + "reference": "a8e4e57a6031efc7a92defd13fed9c1c4c73fc71", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.2.6" - }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" + "php": ">=8.2" }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + "require-dev": { + "ergebnis/composer-normalize": "^2.8", + "esi/phpunit-coverage-check": ">2", + "friendsofphp/php-cs-fixer": "^3.17", + "infection/infection": ">=0.32.3", + "league/pipeline": "^0.3 || ^1.0", + "php-coveralls/php-coveralls": "^2.4.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^11 || ^12", + "sanmai/phpstan-rules": "^0.3.11", + "sanmai/phpunit-double-colon-syntax": "^0.1.1", + "vimeo/psalm": ">=2" }, - "bin": [ - "bin/rector" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.x-dev" + } + }, "autoload": { "files": [ - "bootstrap.php" - ] + "src/functions.php" + ], + "psr-4": { + "Pipeline\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "homepage": "https://getrector.com/", - "keywords": [ - "automation", - "dev", - "migration", - "refactoring" + "authors": [ + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com" + } ], + "description": "General-purpose collections pipeline", "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.6.3" + "issues": "https://github.com/sanmai/pipeline/issues", + "source": "https://github.com/sanmai/pipeline/tree/7.10" }, "funding": [ { - "url": "https://github.com/tomasvotruba", + "url": "https://github.com/sanmai", "type": "github" } ], - "time": "2026-08-18T22:01:18+00:00" + "time": "2026-08-03T04:56:22+00:00" }, { "name": "sebastian/cli-parser", @@ -3464,6 +4662,86 @@ ], "time": "2026-05-27T06:59:30+00:00" }, + { + "name": "symfony/polyfill-php85", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-01T12:47:55+00:00" + }, { "name": "symfony/process", "version": "v7.4.17", @@ -3707,6 +4985,149 @@ ], "time": "2026-07-28T07:33:02+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", @@ -3881,11 +5302,11 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "php": "~8.3.0 || ~8.4.0 || ~8.5.0" }, "platform-dev": {}, "platform-overrides": { - "php": "8.2.99" + "php": "8.3.99" }, "plugin-api-version": "2.9.0" } diff --git a/docs/book/docker-deployment.md b/docs/book/docker-deployment.md index 721d2749..dc8bd45c 100644 --- a/docs/book/docker-deployment.md +++ b/docs/book/docker-deployment.md @@ -13,7 +13,7 @@ Two web server options are supported: **Nginx with PHP-FPM** Create a `Dockerfile` in your project root: ```dockerfile -FROM php:8.2-fpm-alpine +FROM php:8.3-fpm-alpine RUN apk add --no-cache git zip unzip \ && docker-php-ext-install pdo_mysql @@ -55,7 +55,7 @@ server { Create a `Dockerfile` in your project root: ```dockerfile -FROM php:8.2-apache +FROM php:8.3-apache RUN apt-get update \ && apt-get install -y git zlib1g-dev libzip-dev \ diff --git a/infection.json5.dist b/infection.json5.dist new file mode 100644 index 00000000..7ea8caf6 --- /dev/null +++ b/infection.json5.dist @@ -0,0 +1,26 @@ +{ + "$schema": "vendor/infection/infection/resources/schema.json", + "source": { + "directories": [ + "src" + ] + }, + "timeout": 10, + "threads": "max", + "logs": { + "text": "infection.log", + "summary": "summary.log", + "stryker": { + // matches versioned release branches, e.g. "0.1.x", "0.2.x" + "badge": "/^\\d+\\.\\d+\\.x$/" + } + }, + "mutators": { + "@default": true + } + // "staticAnalysisTool": "mago" — no released Infection accepts "mago"; the + // schema enum is ["phpstan"] up to 0.33 and ["phpstan", "debug"] from 0.34, + // and passing it aborts the run. Re-enable once infection/infection#71311 + // ships mago support. phpstan is not an alternative here: the repo dropped + // it when it moved to the Mago toolchain. +} diff --git a/mago.toml b/mago.toml index 86ec9038..ae9cf5c7 100644 --- a/mago.toml +++ b/mago.toml @@ -1,5 +1,5 @@ extends = "vendor/php-db/phpdb-qa-tools/mago.toml" -php-version = "8.2.0" +php-version = "8.3.0" [source] paths = ["src", "test"] diff --git a/src/Sql/TableIdentifier.php b/src/Sql/TableIdentifier.php index ed7c1e60..5537b3b7 100644 --- a/src/Sql/TableIdentifier.php +++ b/src/Sql/TableIdentifier.php @@ -6,7 +6,7 @@ final readonly class TableIdentifier { - public const SEPARATOR = '_'; + public const string SEPARATOR = '_'; /** * @throws Exception\InvalidArgumentException If $table, $schema, $prefix or $separator is an empty string. From 1ea439c416df00ba6ac9154a0f9ec30559cfacdf Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Mon, 24 Aug 2026 00:04:05 +1000 Subject: [PATCH 10/10] Enable mago as Infection's static analysis tool Infection 0.34 added 'mago' to StaticAnalysisToolTypes, so escaped mutants are now re-checked with mago analyze. resources/schema.json still lists only phpstan and debug, but the runtime enum is what validates the config. --- infection.json5.dist | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/infection.json5.dist b/infection.json5.dist index 7ea8caf6..71b82aa6 100644 --- a/infection.json5.dist +++ b/infection.json5.dist @@ -17,10 +17,6 @@ }, "mutators": { "@default": true - } - // "staticAnalysisTool": "mago" — no released Infection accepts "mago"; the - // schema enum is ["phpstan"] up to 0.33 and ["phpstan", "debug"] from 0.34, - // and passing it aborts the run. Re-enable once infection/infection#71311 - // ships mago support. phpstan is not an alternative here: the repo dropped - // it when it moved to the Mago toolchain. + }, + "staticAnalysisTool": "mago" }