From e36c2131a6a90b5ccca1e361bb8dca2b5799d6fc Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Thu, 7 Oct 2021 14:16:55 +1000 Subject: [PATCH 01/32] Add support for renaming columns via the change command. --- src/Operation/ColumnOperation.php | 1 + src/Operation/TableOperation.php | 13 +++++++++++++ src/Statement/MysqlStatementBuilder.php | 17 ++++++++++++++++- src/TableMigration.php | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Operation/ColumnOperation.php b/src/Operation/ColumnOperation.php index dc8d6af..c159940 100644 --- a/src/Operation/ColumnOperation.php +++ b/src/Operation/ColumnOperation.php @@ -6,6 +6,7 @@ final class ColumnOperation extends AbstractOperation { const ADD = 'add'; const MODIFY = 'modify'; + const CHANGE = 'change'; const DROP = 'drop'; /** diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 6e73aa4..ae47b40 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -104,6 +104,18 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null) $originalColumn->getOptions() ); } + + if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { + if (!$originalColumn) { + throw new LogicException('Cannot revert a column that does not exist.'); + } + + $columnOperations[] = new ColumnOperation( + $columnOperation->getName(), + ColumnOperation::CHANGE, + $originalColumn->getOptions() + ); + } } $indexOperations = []; @@ -274,6 +286,7 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati } break; case ColumnOperation::MODIFY: + case ColumnOperation::CHANGE: $columns[] = new ColumnOperation( $columnOperation->getName(), $originalOperation, diff --git a/src/Statement/MysqlStatementBuilder.php b/src/Statement/MysqlStatementBuilder.php index 0fb7b9a..6cbba3e 100644 --- a/src/Statement/MysqlStatementBuilder.php +++ b/src/Statement/MysqlStatementBuilder.php @@ -126,6 +126,12 @@ public function buildTable(TableOperation $operation): string $this->buildColumn($columnOperation->getName(), $columnOperation->getOptions()) ); break; + case ColumnOperation::CHANGE: + $specifications[] = sprintf( + 'CHANGE COLUMN %s', + $this->buildColumn($columnOperation->getName(), $columnOperation->getOptions()) + ); + break; case ColumnOperation::DROP: $specifications[] = sprintf( 'DROP COLUMN %s', @@ -344,7 +350,16 @@ protected function buildType(array $options): string */ protected function buildColumn(string $column, array $options): string { - $definition = sprintf('%s %s', $this->buildIdentifier($column), $this->buildType($options)); + if ($options['name'] ?? null) { + $definition = sprintf( + '%s %s %s', + $this->buildIdentifier($column), + $this->buildIdentifier($options['name']), + $this->buildType($options) + ); + } else { + $definition = sprintf('%s %s', $this->buildIdentifier($column), $this->buildType($options)); + } if (!($options['null'] ?? true)) { $definition .= ' NOT NULL'; diff --git a/src/TableMigration.php b/src/TableMigration.php index 3795c48..fd744a9 100644 --- a/src/TableMigration.php +++ b/src/TableMigration.php @@ -114,6 +114,28 @@ public function modifyColumn(string $column, array $options): TableMigration return $this; } + /** + * Pushes a new change column operation. + * + * @param string $column + * @param array $options + * @return TableMigration + */ + public function changeColumn(string $column, array $options): TableMigration + { + if ($this->operation === TableOperation::CREATE) { + throw new LogicException('Cannot change columns in a create migration.'); + } + + if ($this->operation === TableOperation::DROP) { + throw new LogicException('Cannot change columns in a drop migration.'); + } + + $this->columnOperations[] = new ColumnOperation($column, ColumnOperation::CHANGE, $options); + + return $this; + } + /** * Pushes a new drop column operation. * From 564929c8a0709df11377240e6d16999d265d68f4 Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Thu, 7 Oct 2021 14:17:02 +1000 Subject: [PATCH 02/32] Add tests. --- tests/Operation/TableOperationTest.php | 58 +++++++++++++++++++ tests/Statement/MysqlStatementBuilderTest.php | 6 ++ tests/TableMigrationTest.php | 3 +- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/tests/Operation/TableOperationTest.php b/tests/Operation/TableOperationTest.php index c1d65b8..e52e77e 100644 --- a/tests/Operation/TableOperationTest.php +++ b/tests/Operation/TableOperationTest.php @@ -86,6 +86,39 @@ public function testApplyAlterToAlter() $this->assertEquals(['unique' => true], $operation->getIndexOperations()[0]->getOptions()); } + public function testApplyAlterToAlterWithColumnChange() + { + $base = new TableOperation('users', TableOperation::ALTER, [ + new ColumnOperation('id', ColumnOperation::DROP, []), + new ColumnOperation('email', ColumnOperation::ADD, ['type' => 'string', 'length' => 255, 'first' => true]), + new ColumnOperation('username', ColumnOperation::ADD, ['type' => 'string', 'length' => 255]) + ], [ + new IndexOperation('email_username', ColumnOperation::ADD, ['email', 'username'], ['unique' => true]) + ]); + + $operation = $base->apply(new TableOperation('users', TableOperation::ALTER, [ + new ColumnOperation('email', ColumnOperation::DROP, []), + new ColumnOperation('username', ColumnOperation::CHANGE, ['type' => 'string', 'length' => 255, 'name' => 'email']) + ], [])); + + $this->assertEquals('users', $operation->getName()); + $this->assertEquals(TableOperation::ALTER, $operation->getOperation()); + $this->assertCount(2, $operation->getColumnOperations()); + $this->assertCount(1, $operation->getIndexOperations()); + + $this->assertEquals('id', $operation->getColumnOperations()[0]->getName()); + $this->assertEquals(ColumnOperation::DROP, $operation->getColumnOperations()[0]->getOperation()); + + $this->assertEquals('username', $operation->getColumnOperations()[1]->getName()); + $this->assertEquals(ColumnOperation::ADD, $operation->getColumnOperations()[1]->getOperation()); + $this->assertEquals(['type' => 'string', 'length' => 255, 'name' => 'email'], $operation->getColumnOperations()[1]->getOptions()); + + $this->assertEquals('email_username', $operation->getIndexOperations()[0]->getName()); + $this->assertEquals(IndexOperation::ADD, $operation->getIndexOperations()[0]->getOperation()); + $this->assertEquals(['username'], $operation->getIndexOperations()[0]->getColumns()); + $this->assertEquals(['unique' => true], $operation->getIndexOperations()[0]->getOptions()); + } + public function testApplyDropToAlter() { $base = new TableOperation('users', TableOperation::ALTER, [ @@ -158,6 +191,31 @@ public function testReverseAlter() $this->assertEquals(IndexOperation::DROP, $operation->getIndexOperations()[1]->getOperation()); } + public function testReverseAlterWithColumnChange() + { + $base = new TableOperation('users', TableOperation::ALTER, [ + new ColumnOperation('username', ColumnOperation::CHANGE, ['name' => 'email', 'type' => 'string', 'length' => 255]) + ], []); + + $create = new TableOperation('users', TableOperation::CREATE, [ + new ColumnOperation('id', ColumnOperation::ADD, ['type' => 'uuid']), + new ColumnOperation('username', ColumnOperation::ADD, ['type' => 'string', 'length' => 64]) + ], [ + new IndexOperation('username', IndexOperation::ADD, ['username'], ['unique' => true]) + ]); + + $operation = $base->reverse($create); + + $this->assertEquals('users', $operation->getName()); + $this->assertEquals(TableOperation::ALTER, $operation->getOperation()); + + $this->assertEquals('username', $operation->getColumnOperations()[0]->getName()); + $this->assertEquals(ColumnOperation::CHANGE, $operation->getColumnOperations()[0]->getOperation()); + $this->assertEquals(['type' => 'string', 'length' => 64], $operation->getColumnOperations()[0]->getOptions()); + + $this->assertCount(0, $operation->getIndexOperations()); + } + public function testReverseDrop() { $base = new TableOperation('users', TableOperation::DROP, [], []); diff --git a/tests/Statement/MysqlStatementBuilderTest.php b/tests/Statement/MysqlStatementBuilderTest.php index 73a18c8..b790b0a 100644 --- a/tests/Statement/MysqlStatementBuilderTest.php +++ b/tests/Statement/MysqlStatementBuilderTest.php @@ -101,6 +101,12 @@ public function provider() 'MODIFY COLUMN `username` VARCHAR(255), ' . 'DROP COLUMN `created_at`, ADD UNIQUE INDEX `meta` (`meta`), DROP INDEX `username`;' ], + [ + new TableOperation('users', TableOperation::ALTER, [ + new ColumnOperation('meta', ColumnOperation::CHANGE, ['name' => 'metadata', 'type' => 'json']), + ], []), + 'ALTER TABLE `users` CHANGE COLUMN `meta` `metadata` JSON;' + ], [ new TableOperation('users', TableOperation::DROP, [], []), 'DROP TABLE `users`;' diff --git a/tests/TableMigrationTest.php b/tests/TableMigrationTest.php index 0ddce0a..6a36e68 100644 --- a/tests/TableMigrationTest.php +++ b/tests/TableMigrationTest.php @@ -25,11 +25,12 @@ public function testAlterMigration() ->addColumn('email', ['type' => 'string', 'length' => 255]) ->modifyColumn('password', ['type' => 'string', 'length' => 255]) ->dropColumn('username') + ->changeColumn('id', ['name' => 'uid', 'type' => 'string']) ->getOperation(); $this->assertEquals('users', $operation->getName()); $this->assertEquals(TableOperation::ALTER, $operation->getOperation()); - $this->assertCount(3, $operation->getColumnOperations()); + $this->assertCount(4, $operation->getColumnOperations()); } public function testDropMigration() From 4cc5d0f3bda002a65be66671c5246272b015d19c Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 16:01:48 +1000 Subject: [PATCH 03/32] Update TableOperation.php --- src/Operation/TableOperation.php | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index ae47b40..41e2bc0 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -93,26 +93,17 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null) $columnOperations[] = $originalColumn; } - if ($columnOperation->getOperation() === ColumnOperation::MODIFY) { + if ( + $columnOperation->getOperation() === ColumnOperation::MODIFY || + $columnOperation->getOperation() === ColumnOperation::CHANGE + ) { if (!$originalColumn) { throw new LogicException('Cannot revert a column that does not exist.'); } $columnOperations[] = new ColumnOperation( $columnOperation->getName(), - ColumnOperation::MODIFY, - $originalColumn->getOptions() - ); - } - - if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { - if (!$originalColumn) { - throw new LogicException('Cannot revert a column that does not exist.'); - } - - $columnOperations[] = new ColumnOperation( - $columnOperation->getName(), - ColumnOperation::CHANGE, + $columnOperation->getOperation(), $originalColumn->getOptions() ); } From 6f896e1949cb39d72ae35e856f3daac73e98ae9b Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 20:41:06 +1000 Subject: [PATCH 04/32] Make recommended adjustments to building query string. --- src/Statement/MysqlStatementBuilder.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Statement/MysqlStatementBuilder.php b/src/Statement/MysqlStatementBuilder.php index 6cbba3e..a7ab050 100644 --- a/src/Statement/MysqlStatementBuilder.php +++ b/src/Statement/MysqlStatementBuilder.php @@ -128,8 +128,12 @@ public function buildTable(TableOperation $operation): string break; case ColumnOperation::CHANGE: $specifications[] = sprintf( - 'CHANGE COLUMN %s', - $this->buildColumn($columnOperation->getName(), $columnOperation->getOptions()) + 'CHANGE COLUMN %s %s', + $this->buildIdentifier($columnOperation->getName()), + $this->buildColumn( + $columnOperation->getOptions()['new_name'], + $columnOperation->getOptions() + ) ); break; case ColumnOperation::DROP: @@ -350,16 +354,7 @@ protected function buildType(array $options): string */ protected function buildColumn(string $column, array $options): string { - if ($options['name'] ?? null) { - $definition = sprintf( - '%s %s %s', - $this->buildIdentifier($column), - $this->buildIdentifier($options['name']), - $this->buildType($options) - ); - } else { - $definition = sprintf('%s %s', $this->buildIdentifier($column), $this->buildType($options)); - } + $definition = sprintf('%s %s', $this->buildIdentifier($column), $this->buildType($options)); if (!($options['null'] ?? true)) { $definition .= ' NOT NULL'; From 15a82fecef3bad28366b4565a431ab53c52bb330 Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 21:28:21 +1000 Subject: [PATCH 05/32] Update table operations for Change to operate on the new column. --- src/Operation/TableOperation.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 41e2bc0..e7b6069 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -216,6 +216,15 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati $options ); + array_splice($columns, $offset, 0, [$addOperation]); + break; + case ColumnOperation::CHANGE: + $addOperation = new ColumnOperation( + $columnOperation->getOptions()['new_name'], + ColumnOperation::ADD, + $options + ); + array_splice($columns, $offset, 0, [$addOperation]); break; } @@ -277,13 +286,19 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati } break; case ColumnOperation::MODIFY: - case ColumnOperation::CHANGE: $columns[] = new ColumnOperation( $columnOperation->getName(), $originalOperation, $columnOperation->getOptions() ); break; + case ColumnOperation::CHANGE: + $columns[] = new ColumnOperation( + $columnOperation->getOptions()['new_name'], + $originalOperation, + $columnOperation->getOptions() + ); + break; } } From e95270483cbbc4c960248d527c5da2aa0ba0640a Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 21:29:15 +1000 Subject: [PATCH 06/32] make the changeColumn method more dev friendly by accepting a new column arg. --- src/TableMigration.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/TableMigration.php b/src/TableMigration.php index fd744a9..f473616 100644 --- a/src/TableMigration.php +++ b/src/TableMigration.php @@ -117,11 +117,12 @@ public function modifyColumn(string $column, array $options): TableMigration /** * Pushes a new change column operation. * - * @param string $column + * @param string $oldColumn + * @param string $newColumn * @param array $options * @return TableMigration */ - public function changeColumn(string $column, array $options): TableMigration + public function changeColumn(string $oldColumn, string $newColumn, array $options): TableMigration { if ($this->operation === TableOperation::CREATE) { throw new LogicException('Cannot change columns in a create migration.'); @@ -131,7 +132,12 @@ public function changeColumn(string $column, array $options): TableMigration throw new LogicException('Cannot change columns in a drop migration.'); } - $this->columnOperations[] = new ColumnOperation($column, ColumnOperation::CHANGE, $options); + if ($oldColumn === $newColumn) { + $this->columnOperations[] = new ColumnOperation($oldColumn, ColumnOperation::MODIFY, $options); + } else { + $options['new_name'] = $newColumn; + $this->columnOperations[] = new ColumnOperation($oldColumn, ColumnOperation::CHANGE, $options); + } return $this; } From 8d96cd803ffacd8240db94b98af2913c389f2450 Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 21:29:24 +1000 Subject: [PATCH 07/32] Get tests to working state. --- tests/Operation/TableOperationTest.php | 10 +++++----- tests/Statement/MysqlStatementBuilderTest.php | 2 +- tests/TableMigrationTest.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Operation/TableOperationTest.php b/tests/Operation/TableOperationTest.php index e52e77e..12b79af 100644 --- a/tests/Operation/TableOperationTest.php +++ b/tests/Operation/TableOperationTest.php @@ -98,7 +98,7 @@ public function testApplyAlterToAlterWithColumnChange() $operation = $base->apply(new TableOperation('users', TableOperation::ALTER, [ new ColumnOperation('email', ColumnOperation::DROP, []), - new ColumnOperation('username', ColumnOperation::CHANGE, ['type' => 'string', 'length' => 255, 'name' => 'email']) + new ColumnOperation('username', ColumnOperation::CHANGE, ['type' => 'string', 'length' => 255, 'new_name' => 'email']) ], [])); $this->assertEquals('users', $operation->getName()); @@ -109,13 +109,13 @@ public function testApplyAlterToAlterWithColumnChange() $this->assertEquals('id', $operation->getColumnOperations()[0]->getName()); $this->assertEquals(ColumnOperation::DROP, $operation->getColumnOperations()[0]->getOperation()); - $this->assertEquals('username', $operation->getColumnOperations()[1]->getName()); + $this->assertEquals('email', $operation->getColumnOperations()[1]->getName()); $this->assertEquals(ColumnOperation::ADD, $operation->getColumnOperations()[1]->getOperation()); - $this->assertEquals(['type' => 'string', 'length' => 255, 'name' => 'email'], $operation->getColumnOperations()[1]->getOptions()); + $this->assertEquals(['type' => 'string', 'length' => 255, 'new_name' => 'email'], $operation->getColumnOperations()[1]->getOptions()); $this->assertEquals('email_username', $operation->getIndexOperations()[0]->getName()); $this->assertEquals(IndexOperation::ADD, $operation->getIndexOperations()[0]->getOperation()); - $this->assertEquals(['username'], $operation->getIndexOperations()[0]->getColumns()); + $this->assertEquals(['email'], $operation->getIndexOperations()[0]->getColumns()); $this->assertEquals(['unique' => true], $operation->getIndexOperations()[0]->getOptions()); } @@ -194,7 +194,7 @@ public function testReverseAlter() public function testReverseAlterWithColumnChange() { $base = new TableOperation('users', TableOperation::ALTER, [ - new ColumnOperation('username', ColumnOperation::CHANGE, ['name' => 'email', 'type' => 'string', 'length' => 255]) + new ColumnOperation('username', ColumnOperation::CHANGE, ['new_name' => 'email', 'type' => 'string', 'length' => 255]) ], []); $create = new TableOperation('users', TableOperation::CREATE, [ diff --git a/tests/Statement/MysqlStatementBuilderTest.php b/tests/Statement/MysqlStatementBuilderTest.php index b790b0a..aee7a47 100644 --- a/tests/Statement/MysqlStatementBuilderTest.php +++ b/tests/Statement/MysqlStatementBuilderTest.php @@ -103,7 +103,7 @@ public function provider() ], [ new TableOperation('users', TableOperation::ALTER, [ - new ColumnOperation('meta', ColumnOperation::CHANGE, ['name' => 'metadata', 'type' => 'json']), + new ColumnOperation('meta', ColumnOperation::CHANGE, ['new_name' => 'metadata', 'type' => 'json']), ], []), 'ALTER TABLE `users` CHANGE COLUMN `meta` `metadata` JSON;' ], diff --git a/tests/TableMigrationTest.php b/tests/TableMigrationTest.php index 6a36e68..64025ea 100644 --- a/tests/TableMigrationTest.php +++ b/tests/TableMigrationTest.php @@ -25,7 +25,7 @@ public function testAlterMigration() ->addColumn('email', ['type' => 'string', 'length' => 255]) ->modifyColumn('password', ['type' => 'string', 'length' => 255]) ->dropColumn('username') - ->changeColumn('id', ['name' => 'uid', 'type' => 'string']) + ->changeColumn('id', 'uid', ['type' => 'string']) ->getOperation(); $this->assertEquals('users', $operation->getName()); From 7881e024b4abea32fd88ce33aa2ce445c38d4c97 Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 21:59:03 +1000 Subject: [PATCH 08/32] Add proper reverse method for change. --- src/Operation/TableOperation.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index e7b6069..ebbcd68 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -93,10 +93,7 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null) $columnOperations[] = $originalColumn; } - if ( - $columnOperation->getOperation() === ColumnOperation::MODIFY || - $columnOperation->getOperation() === ColumnOperation::CHANGE - ) { + if ($columnOperation->getOperation() === ColumnOperation::MODIFY) { if (!$originalColumn) { throw new LogicException('Cannot revert a column that does not exist.'); } @@ -107,6 +104,20 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null) $originalColumn->getOptions() ); } + + if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { + if (!$originalColumn) { + throw new LogicException('Cannot revert a column that does not exist.'); + } + + $options = $originalColumn->getOptions(); + $options['new_name'] = $columnOperation->getName(); + $columnOperations[] = new ColumnOperation( + $columnOperation->getOptions()['new_name'], + $columnOperation->getOperation(), + $options + ); + } } $indexOperations = []; From c4d4c260a493518f841e478474dfd696b1069192 Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Tue, 12 Oct 2021 22:16:05 +1000 Subject: [PATCH 09/32] Update TableOperationTest.php --- tests/Operation/TableOperationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Operation/TableOperationTest.php b/tests/Operation/TableOperationTest.php index 12b79af..c6c6fca 100644 --- a/tests/Operation/TableOperationTest.php +++ b/tests/Operation/TableOperationTest.php @@ -209,9 +209,9 @@ public function testReverseAlterWithColumnChange() $this->assertEquals('users', $operation->getName()); $this->assertEquals(TableOperation::ALTER, $operation->getOperation()); - $this->assertEquals('username', $operation->getColumnOperations()[0]->getName()); + $this->assertEquals('email', $operation->getColumnOperations()[0]->getName()); $this->assertEquals(ColumnOperation::CHANGE, $operation->getColumnOperations()[0]->getOperation()); - $this->assertEquals(['type' => 'string', 'length' => 64], $operation->getColumnOperations()[0]->getOptions()); + $this->assertEquals(['new_name' => 'username', 'type' => 'string', 'length' => 64], $operation->getColumnOperations()[0]->getOptions()); $this->assertCount(0, $operation->getIndexOperations()); } From 938d4adc3c5842691d899df9bb89a6804a638b49 Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Fri, 15 Oct 2021 14:36:35 +1000 Subject: [PATCH 10/32] Add tests to test applying and rewinding changes. --- .../TestChange/20211015_create_users.php | 4 + .../20211016_change_username_to_email.php | 4 + .../20211017_add_column_username.php | 4 + .../20211018_change_username_to_user_id.php | 4 + tests/MigrationIntegrationTest.php | 82 +++++++++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 tests/Fixtures/TestChange/20211015_create_users.php create mode 100644 tests/Fixtures/TestChange/20211016_change_username_to_email.php create mode 100644 tests/Fixtures/TestChange/20211017_add_column_username.php create mode 100644 tests/Fixtures/TestChange/20211018_change_username_to_user_id.php create mode 100644 tests/MigrationIntegrationTest.php diff --git a/tests/Fixtures/TestChange/20211015_create_users.php b/tests/Fixtures/TestChange/20211015_create_users.php new file mode 100644 index 0000000..3479ca3 --- /dev/null +++ b/tests/Fixtures/TestChange/20211015_create_users.php @@ -0,0 +1,4 @@ +addColumn('username', ['type' => 'string']); diff --git a/tests/Fixtures/TestChange/20211016_change_username_to_email.php b/tests/Fixtures/TestChange/20211016_change_username_to_email.php new file mode 100644 index 0000000..e717179 --- /dev/null +++ b/tests/Fixtures/TestChange/20211016_change_username_to_email.php @@ -0,0 +1,4 @@ +changeColumn('username', 'email', ['type' => 'string']); diff --git a/tests/Fixtures/TestChange/20211017_add_column_username.php b/tests/Fixtures/TestChange/20211017_add_column_username.php new file mode 100644 index 0000000..be3b18a --- /dev/null +++ b/tests/Fixtures/TestChange/20211017_add_column_username.php @@ -0,0 +1,4 @@ +addColumn('username', ['type' => 'string']); diff --git a/tests/Fixtures/TestChange/20211018_change_username_to_user_id.php b/tests/Fixtures/TestChange/20211018_change_username_to_user_id.php new file mode 100644 index 0000000..3311883 --- /dev/null +++ b/tests/Fixtures/TestChange/20211018_change_username_to_user_id.php @@ -0,0 +1,4 @@ +changeColumn('username', 'user_id', ['type' => 'integer']); diff --git a/tests/MigrationIntegrationTest.php b/tests/MigrationIntegrationTest.php new file mode 100644 index 0000000..c502084 --- /dev/null +++ b/tests/MigrationIntegrationTest.php @@ -0,0 +1,82 @@ +fromPath(__DIR__ . '/Fixtures/TestChange'); + $operations = $history->play('20211015_create_users', '20211018_change_username_to_user_id', true); + + $this->assertCount(1, $operations); + + $operation = $operations[0]; + + $this->assertSame('users', $operation->getName()); + $this->assertSame(TableOperation::CREATE, $operation->getOperation()); + $this->assertCount(2, $operation->getColumnOperations()); + + list($operation1, $operation2) = $operation->getColumnOperations(); + + $this->assertSame('email', $operation1->getName()); + $this->assertSame(ColumnOperation::ADD, $operation1->getOperation()); + $this->assertSame('string', $operation1->getOptions()['type']); + + $this->assertSame('user_id', $operation2->getName()); + $this->assertSame(ColumnOperation::ADD, $operation2->getOperation()); + $this->assertSame('integer', $operation2->getOptions()['type']); + } + + public function testRewindMigrationsWithChange() + { + $finder = new Finder([]); + $history = $finder->fromPath(__DIR__ . '/Fixtures/TestChange'); + $operations = $history->rewind('20211018_change_username_to_user_id', '20211017_add_column_username', false); + $this->assertCount(2, $operations); + + list($operation1, $operation2) = $operations; + + $this->assertSame('users', $operation1->getName()); + $this->assertSame(TableOperation::ALTER, $operation1->getOperation()); + $this->assertCount(1, $operation1->getColumnOperations()); + $this->assertSame('user_id', $operation1->getColumnOperations()[0]->getName()); + $this->assertSame(ColumnOperation::CHANGE, $operation1->getColumnOperations()[0]->getOperation()); + $this->assertSame('username', $operation1->getColumnOperations()[0]->getOptions()['new_name']); + + $this->assertSame('users', $operation2->getName()); + $this->assertSame(TableOperation::ALTER, $operation2->getOperation()); + $this->assertCount(1, $operation2->getColumnOperations()); + $this->assertSame('username', $operation2->getColumnOperations()[0]->getName()); + $this->assertSame(ColumnOperation::DROP, $operation2->getColumnOperations()[0]->getOperation()); + } + + public function testReduceRewindMigrationsWithChange() + { + $finder = new Finder([]); + $history = $finder->fromPath(__DIR__ . '/Fixtures/TestChange'); + $operations = $history->rewind('20211018_change_username_to_user_id', '20211017_add_column_username', true); + $this->assertCount(1, $operations); + + $operation = $operations[0]; + + $this->assertSame('users', $operation->getName()); + $this->assertSame(TableOperation::ALTER, $operation->getOperation()); + $this->assertCount(2, $operation->getColumnOperations()); + + list($operation1, $operation2) = $operation->getColumnOperations(); + + $this->assertSame('user_id', $operation1->getName()); + $this->assertSame(ColumnOperation::CHANGE, $operation1->getOperation()); + $this->assertSame('username', $operation1->getOptions()['new_name']); + + $this->assertSame('username', $operation2->getName()); + $this->assertSame(ColumnOperation::DROP, $operation2->getOperation()); + } +} From 5f8f565225df496c594e7e2fa2d1e5f4d1eee9ff Mon Sep 17 00:00:00 2001 From: Ryan Rigby Date: Fri, 15 Oct 2021 14:58:15 +1000 Subject: [PATCH 11/32] Added proper integration test parts where we run the migrations on the Db and confirm it works. --- tests/MigrationIntegrationTest.php | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/MigrationIntegrationTest.php b/tests/MigrationIntegrationTest.php index c502084..5599325 100644 --- a/tests/MigrationIntegrationTest.php +++ b/tests/MigrationIntegrationTest.php @@ -2,13 +2,40 @@ namespace Exo\Tests; +use Exo\Handler; +use PDO; use Exo\Operation\ColumnOperation; use Exo\Operation\TableOperation; +use Exo\Tests\Traits\UsesYamlConfig; use Exo\Util\Finder; use PHPUnit\Framework\TestCase; class MigrationIntegrationTest extends TestCase { + use UsesYamlConfig; + + /** + * @var PDO|null + */ + private ?PDO $pdo; + + public function setUp(): void + { + $mysql = self::yaml('handlers.mysql'); + + $this->pdo = new PDO( + sprintf('mysql:dbname=%s;host=%s;port=%s', $mysql['name'], $mysql['host'], $mysql['port']), + $mysql['user'], + $mysql['pass'] + ); + $this->pdo->exec('DROP TABLE IF EXISTS users;'); + } + + public function tearDown(): void + { + $this->pdo = null; + } + public function testReduceMigrationsWithChange() { $finder = new Finder([]); @@ -79,4 +106,46 @@ public function testReduceRewindMigrationsWithChange() $this->assertSame('username', $operation2->getName()); $this->assertSame(ColumnOperation::DROP, $operation2->getOperation()); } + + public function testMigrateMigrationsWithMysql() + { + $finder = new Finder([]); + $history = $finder->fromPath(__DIR__ . '/Fixtures/TestChange'); + $handler = new Handler($this->pdo, $history); + + $handler->migrate([], null, true); + + $usersTable = $this->pdo->query('DESCRIBE users')->fetchAll(); + + $this->assertSame('email', $usersTable[0]['Field']); + $this->contains('varchar', $usersTable[0]['Type']); + $this->assertSame('user_id', $usersTable[1]['Field']); + $this->contains('int', $usersTable[1]['Type']); + } + + public function testRollbackMigrationsWithMysql() + { + $finder = new Finder([]); + $history = $finder->fromPath(__DIR__ . '/Fixtures/TestChange'); + $handler = new Handler($this->pdo, $history); + $handler->migrate([], null, true); + + $handler->rollback( + [ + '20211015_create_users', + '20211016_change_username_to_email', + '20211017_add_column_username', + '20211018_change_username_to_user_id' + ], + '20211017_add_column_username', + true + ); + + $usersTable = $this->pdo->query('DESCRIBE users')->fetchAll(); + + $this->assertSame('email', $usersTable[0]['Field']); + $this->contains('varchar', $usersTable[0]['Type']); + $this->assertSame('username', $usersTable[1]['Field']); + $this->contains('varchar', $usersTable[1]['Type']); + } } From a991b4e2229cb12ed12b351d83907a19f24df845 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:21:08 +0000 Subject: [PATCH 12/32] Add PHP 8.1-8.4 support - Update composer.json to support PHP versions 7.4 through 8.4 - Update CI workflow to test against PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 - Update composer.lock to reflect new PHP version constraints Co-Authored-By: Ben Sinclair --- .github/workflows/tests.yml | 4 ++-- composer.json | 2 +- composer.lock | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d7adcf6..6ffad59 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - php-versions: [ '7.4', '8.0' ] + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] postgresql-versions: [ '12', '14' ] name: PHP ${{ matrix.php-versions }} with postgreSQL ${{ matrix.postgresql-versions }} @@ -73,7 +73,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - php-versions: [ '7.4', '8.0' ] + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] mysql-versions: [ '5.7', '8.0' ] name: PHP ${{ matrix.php-versions }} with MySQL ${{ matrix.mysql-versions }} diff --git a/composer.json b/composer.json index 3c15012..88ee793 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "license": "MIT", "require": { - "php": ">=7.4", + "php": ">=7.4 <8.5", "ext-pdo": "*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index c6e3bae..7de15f1 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": "f4eb836aa389b5ed2b1d1d035b61c22d", + "content-hash": "f012672abd9b2ab08d08654d9e0d303e", "packages": [], "packages-dev": [ { @@ -2245,13 +2245,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4", + "php": ">=7.4 <8.5", "ext-pdo": "*" }, - "platform-dev": [], - "plugin-api-version": "2.0.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } From eaa65bcd96cc2621cea60a27966dbd2389cbae61 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:25:16 +0000 Subject: [PATCH 13/32] Fix deprecated GitHub Actions versions - Update actions/checkout from v2 to v4 - Update actions/cache from v2 to v4 - Resolves CI failure due to deprecated action versions Co-Authored-By: Ben Sinclair --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ffad59..cd775dc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,7 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Check Out Code - name: Setup PHP @@ -48,7 +48,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -89,7 +89,7 @@ jobs: options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 10 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Check Out Code - name: Setup PHP @@ -103,7 +103,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} From 46ee98f9a80bd9a1d43ddb4e751429bfb3c84fe5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:29:36 +0000 Subject: [PATCH 14/32] Drop PHP 7.4 support, require PHP >=8.0 for dependency compatibility Co-Authored-By: Ben Sinclair --- .github/workflows/tests.yml | 4 ++-- composer.json | 2 +- composer.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cd775dc..6c2db61 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] + php-versions: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] postgresql-versions: [ '12', '14' ] name: PHP ${{ matrix.php-versions }} with postgreSQL ${{ matrix.postgresql-versions }} @@ -73,7 +73,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] + php-versions: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] mysql-versions: [ '5.7', '8.0' ] name: PHP ${{ matrix.php-versions }} with MySQL ${{ matrix.mysql-versions }} diff --git a/composer.json b/composer.json index 88ee793..92730a7 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "license": "MIT", "require": { - "php": ">=7.4 <8.5", + "php": ">=8.0 <8.5", "ext-pdo": "*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 7de15f1..d0c397d 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": "f012672abd9b2ab08d08654d9e0d303e", + "content-hash": "818deb88d1b484f20f0f29563872d88e", "packages": [], "packages-dev": [ { @@ -2249,7 +2249,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4 <8.5", + "php": ">=8.0 <8.5", "ext-pdo": "*" }, "platform-dev": {}, From 68e3ecb2abaf0cfec616ef73ec62b7eaa49bfb2d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:30:13 +0000 Subject: [PATCH 15/32] Implement before/after name concepts for ColumnOperation to fix reduction logic - Add beforeName and afterName properties to ColumnOperation - Update TableOperation apply method to use before/after name matching - Fix reduction scenarios where CHANGE operations are followed by operations on renamed columns - Addresses feedback from joshmcrae in PR #22 This resolves the issue where column operations couldn't be properly matched during reduction when CHANGE operations were involved. The solution introduces explicit before/after name tracking so operations can be matched based on the relationship between the old name (beforeName) and new name (afterName). Co-Authored-By: Ben Sinclair --- src/Operation/ColumnOperation.php | 38 +++++++++++++++++++++++++++++++ src/Operation/TableOperation.php | 16 ++++++------- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/Operation/ColumnOperation.php b/src/Operation/ColumnOperation.php index c159940..a77bcb6 100644 --- a/src/Operation/ColumnOperation.php +++ b/src/Operation/ColumnOperation.php @@ -14,6 +14,16 @@ final class ColumnOperation extends AbstractOperation */ private string $operation; + /** + * @var string + */ + private string $beforeName; + + /** + * @var string + */ + private string $afterName; + /** * @var array */ @@ -31,6 +41,14 @@ public function __construct(string $name, string $operation, array $options) $this->name = $name; $this->operation = $operation; $this->options = $options; + + if ($operation === self::CHANGE && isset($options['new_name'])) { + $this->beforeName = $name; + $this->afterName = $options['new_name']; + } else { + $this->beforeName = $name; + $this->afterName = $name; + } } /** @@ -52,4 +70,24 @@ public function getOptions(): array { return $this->options; } + + /** + * Returns the before name (original name for CHANGE operations). + * + * @return string + */ + public function getBeforeName(): string + { + return $this->beforeName; + } + + /** + * Returns the after name (new name for CHANGE operations). + * + * @return string + */ + public function getAfterName(): string + { + return $this->afterName; + } } diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index ebbcd68..3d7773c 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -111,9 +111,9 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null) } $options = $originalColumn->getOptions(); - $options['new_name'] = $columnOperation->getName(); + $options['new_name'] = $columnOperation->getBeforeName(); $columnOperations[] = new ColumnOperation( - $columnOperation->getOptions()['new_name'], + $columnOperation->getAfterName(), $columnOperation->getOperation(), $options ); @@ -206,9 +206,9 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati $offset = array_search($options['after'], array_keys($columns)) + 1; } - // Remove existing operation for the column + // Remove existing operation for the column using before/after name matching foreach ($columns as $existing => $column) { - if ($column->getName() === $columnOperation->getName()) { + if ($column->getAfterName() === $columnOperation->getBeforeName()) { unset($columns[$existing]); break; } @@ -231,7 +231,7 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati break; case ColumnOperation::CHANGE: $addOperation = new ColumnOperation( - $columnOperation->getOptions()['new_name'], + $columnOperation->getAfterName(), ColumnOperation::ADD, $options ); @@ -277,9 +277,9 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati foreach ($operation->getColumnOperations() as $columnOperation) { $originalOperation = $columnOperation->getOperation(); - // Remove existing operation for the column + // Remove existing operation for the column using before/after name matching foreach ($columns as $existing => $column) { - if ($column->getName() === $columnOperation->getName()) { + if ($column->getAfterName() === $columnOperation->getBeforeName()) { unset($columns[$existing]); $originalOperation = $column->getOperation(); break; @@ -305,7 +305,7 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati break; case ColumnOperation::CHANGE: $columns[] = new ColumnOperation( - $columnOperation->getOptions()['new_name'], + $columnOperation->getAfterName(), $originalOperation, $columnOperation->getOptions() ); From 0e2019ceaa90bfd4e07212509a3bbcd8605d0a7d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:32:39 +0000 Subject: [PATCH 16/32] Update phpspec/prophecy to 1.22.0 for PHP 8.2-8.4 compatibility Co-Authored-By: Ben Sinclair --- composer.json | 3 ++- composer.lock | 27 +++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 92730a7..935c786 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ }, "require-dev": { "phpunit/phpunit": "^9.4", - "symfony/yaml": "^5.0" + "symfony/yaml": "^5.0", + "phpspec/prophecy": "^1.22.0" }, "scripts":{ "test": "./vendor/bin/phpunit -c phpunit.xml" diff --git a/composer.lock b/composer.lock index d0c397d..aa3eeeb 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": "818deb88d1b484f20f0f29563872d88e", + "content-hash": "d975bb62eb5a45c054f1f8f280ed56dd", "packages": [], "packages-dev": [ { @@ -462,28 +462,30 @@ }, { "name": "phpspec/prophecy", - "version": "1.14.0", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" + "reference": "35f1adb388946d92e6edab2aa2cb2b60e132ebd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/35f1adb388946d92e6edab2aa2cb2b60e132ebd5", + "reference": "35f1adb388946d92e6edab2aa2cb2b60e132ebd5", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", + "doctrine/instantiator": "^1.2 || ^2.0", + "php": "^7.4 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "phpstan/phpstan": "^2.1.13", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" }, "type": "library", "extra": { @@ -516,6 +518,7 @@ "keywords": [ "Double", "Dummy", + "dev", "fake", "mock", "spy", @@ -523,9 +526,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.22.0" }, - "time": "2021-09-10T09:02:12+00:00" + "time": "2025-04-29T14:58:06+00:00" }, { "name": "phpunit/php-code-coverage", From 67bbe0379cc53e29249091ce56d1754adf722e04 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:25:16 +0000 Subject: [PATCH 17/32] Fix deprecated GitHub Actions versions - Update actions/checkout from v2 to v4 - Update actions/cache from v2 to v4 - Resolves CI failure due to deprecated action versions Co-Authored-By: Ben Sinclair --- .github/workflows/tests.yml | 123 ++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..cd775dc --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,123 @@ +name: Run Tests + +on: + push: + branches: [ master ] + pull_request: + branches: '*' + +jobs: + + postgresql_tests: + + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] + postgresql-versions: [ '12', '14' ] + name: PHP ${{ matrix.php-versions }} with postgreSQL ${{ matrix.postgresql-versions }} + + services: + postgres: + image: postgres:${{ matrix.postgresql-versions }} + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: test + ports: + [ '5432:5432' ] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + name: Check Out Code + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: pdo_pgsql + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Setup Database + run: | + sudo apt update + cp tests/db.yml.example tests/db.yml + + - name: Run test suite + run: ./vendor/bin/phpunit + + mysql_tests: + + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4' ] + mysql-versions: [ '5.7', '8.0' ] + name: PHP ${{ matrix.php-versions }} with MySQL ${{ matrix.mysql-versions }} + + services: + mysql: + image: mysql:${{ matrix.mysql-versions }} + env: + MYSQL_DATABASE: test + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_HOST: 127.0.0.1 + ports: + [ '3306:3306' ] + options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 10 + + steps: + - uses: actions/checkout@v4 + name: Check Out Code + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: pdo_mysql + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Setup Database + run: | + sudo apt update + cp tests/db.yml.example tests/db.yml + + - name: Run test suite + run: ./vendor/bin/phpunit From 3ebbec92d8ae742052f305cfa8af38f821c27dd2 Mon Sep 17 00:00:00 2001 From: Ben Sinclair Date: Tue, 29 Jul 2025 09:38:21 +1000 Subject: [PATCH 18/32] Remove explicit upper PHP version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 935c786..9f2d573 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "license": "MIT", "require": { - "php": ">=8.0 <8.5", + "php": ">=8.0", "ext-pdo": "*" }, "require-dev": { From 10013a4fb8afc39b137b8331aa447dbc1fd9ceac Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:42:37 +0000 Subject: [PATCH 19/32] Update composer.lock after removing upper PHP version constraint Co-Authored-By: Ben Sinclair --- composer.lock | 644 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 387 insertions(+), 257 deletions(-) diff --git a/composer.lock b/composer.lock index aa3eeeb..f89d9d9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,83 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d975bb62eb5a45c054f1f8f280ed56dd", + "content-hash": "2ef951c4b9bc3b0d5de2f265f789494c", "packages": [], "packages-dev": [ + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -58,7 +107,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -74,41 +123,43 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -124,7 +175,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -132,29 +183,31 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nikic/php-parser", - "version": "v4.13.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "50953a2691a922aa1769461637869a0a2faa3f53" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", - "reference": "50953a2691a922aa1769461637869a0a2faa3f53", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -162,7 +215,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -186,26 +239,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2021-09-20T12:20:58+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -246,22 +300,28 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -297,9 +357,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2021-02-23T14:00:09+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -356,27 +416,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -400,37 +468,45 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2025-04-13T19:20:35+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.5.1", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -456,9 +532,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2021-10-02T14:08:47+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpspec/prophecy", @@ -530,46 +606,93 @@ }, "time": "2025-04-29T14:58:06+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + }, + "time": "2025-07-13T07:04:09+00:00" + }, { "name": "phpunit/php-code-coverage", - "version": "9.2.7", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", - "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.12.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -597,7 +720,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -605,20 +729,20 @@ "type": "github" } ], - "time": "2021-09-17T05:39:03+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -657,7 +781,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -665,7 +789,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -850,55 +974,50 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.10", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.7", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -906,15 +1025,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -937,32 +1056,45 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "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" } ], - "time": "2021-09-25T07:38:51+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -997,7 +1129,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -1005,7 +1137,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -1120,16 +1252,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -1182,7 +1314,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -1190,24 +1322,24 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1239,7 +1371,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1247,20 +1379,20 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -1305,7 +1437,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -1313,20 +1445,20 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -1368,7 +1500,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -1376,20 +1508,20 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -1438,14 +1570,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -1453,20 +1585,20 @@ "type": "github" } ], - "time": "2020-09-28T05:24:23+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -1509,7 +1641,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -1517,24 +1649,24 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1566,7 +1698,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -1574,7 +1706,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -1690,16 +1822,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -1738,10 +1870,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -1749,20 +1881,20 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -1774,7 +1906,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1795,8 +1927,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -1804,33 +1935,32 @@ "type": "github" } ], - "abandoned": true, - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1853,7 +1983,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -1861,7 +1991,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -1918,29 +2048,29 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -1965,7 +2095,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1981,45 +2111,45 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2044,7 +2174,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -2060,32 +2190,32 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/yaml", - "version": "v5.3.6", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7" + "reference": "a454d47278cc16a5db371fe73ae66a78a633371e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7", - "reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a454d47278cc16a5db371fe73ae66a78a633371e", + "reference": "a454d47278cc16a5db371fe73ae66a78a633371e", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-ctype": "~1.8" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<4.4" + "symfony/console": "<5.3" }, "require-dev": { - "symfony/console": "^4.4|^5.0" + "symfony/console": "^5.3|^6.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -2119,7 +2249,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.3.6" + "source": "https://github.com/symfony/yaml/tree/v5.4.45" }, "funding": [ { @@ -2135,20 +2265,20 @@ "type": "tidelift" } ], - "time": "2021-07-29T06:20:01+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -2177,7 +2307,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -2185,25 +2315,25 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "webmozart/assert", - "version": "1.10.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" }, "conflict": { "phpstan/phpstan": "<0.12.20", @@ -2241,9 +2371,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "time": "2021-03-09T10:59:23+00:00" + "time": "2022-06-03T18:03:27+00:00" } ], "aliases": [], @@ -2252,7 +2382,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.0 <8.5", + "php": ">=8.0", "ext-pdo": "*" }, "platform-dev": {}, From 79f7f05292781ff85644a98f7e30f81126d00734 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:46:26 +0000 Subject: [PATCH 20/32] Fix PHP 8.0 compatibility by constraining doctrine/instantiator and symfony/deprecation-contracts Co-Authored-By: Ben Sinclair --- composer.json | 4 +++- composer.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 9f2d573..38ccacf 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,9 @@ "require-dev": { "phpunit/phpunit": "^9.4", "symfony/yaml": "^5.0", - "phpspec/prophecy": "^1.22.0" + "phpspec/prophecy": "^1.22.0", + "doctrine/instantiator": "^1.5", + "symfony/deprecation-contracts": "^2.4" }, "scripts":{ "test": "./vendor/bin/phpunit -c phpunit.xml" diff --git a/composer.lock b/composer.lock index f89d9d9..3c6b5f5 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": "2ef951c4b9bc3b0d5de2f265f789494c", + "content-hash": "73e103ce6c073e02a467617792310203", "packages": [], "packages-dev": [ { @@ -57,30 +57,30 @@ }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -107,7 +107,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -123,7 +123,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "myclabs/deep-copy", @@ -2048,20 +2048,20 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" }, "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/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.1" }, "type": "library", "extra": { @@ -2070,7 +2070,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -2095,7 +2095,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/v2.5.4" }, "funding": [ { @@ -2111,7 +2111,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/polyfill-ctype", From 4200f2f302122d23eedf4f04d795abeb12649c6c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:52:28 +0000 Subject: [PATCH 21/32] Update dependencies for PHP 8.3/8.4 compatibility - Update phpspec/prophecy to v1.22.0 to support PHP 8.3/8.4 - Update PHPUnit and related packages for compatibility - Fixes CI failures on extended PHP version matrix Co-Authored-By: Ben Sinclair --- composer.lock | 79 ++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/composer.lock b/composer.lock index c6e3bae..8f549ff 100644 --- a/composer.lock +++ b/composer.lock @@ -462,28 +462,30 @@ }, { "name": "phpspec/prophecy", - "version": "1.14.0", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" + "reference": "35f1adb388946d92e6edab2aa2cb2b60e132ebd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/35f1adb388946d92e6edab2aa2cb2b60e132ebd5", + "reference": "35f1adb388946d92e6edab2aa2cb2b60e132ebd5", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", + "doctrine/instantiator": "^1.2 || ^2.0", + "php": "^7.4 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "phpstan/phpstan": "^2.1.13", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" }, "type": "library", "extra": { @@ -516,6 +518,7 @@ "keywords": [ "Double", "Dummy", + "dev", "fake", "mock", "spy", @@ -523,29 +526,29 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.22.0" }, - "time": "2021-09-10T09:02:12+00:00" + "time": "2025-04-29T14:58:06+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.7", + "version": "9.2.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", - "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.12.0", + "nikic/php-parser": "^4.13.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -594,7 +597,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -602,20 +605,20 @@ "type": "github" } ], - "time": "2021-09-17T05:39:03+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -654,7 +657,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -662,7 +665,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -847,16 +850,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.10", + "version": "9.5.18", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" + "reference": "1b5856028273bfd855e60a887278857d872ec67a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1b5856028273bfd855e60a887278857d872ec67a", + "reference": "1b5856028273bfd855e60a887278857d872ec67a", "shasum": "" }, "require": { @@ -872,7 +875,7 @@ "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -907,11 +910,11 @@ } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -934,11 +937,11 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.18" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { @@ -946,7 +949,7 @@ "type": "github" } ], - "time": "2021-09-25T07:38:51+00:00" + "time": "2022-03-08T06:52:28+00:00" }, { "name": "sebastian/cli-parser", @@ -2245,13 +2248,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=7.4", "ext-pdo": "*" }, - "platform-dev": [], - "plugin-api-version": "2.0.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } From 0abc14f9f86ddc69946269f16779751bf1254402 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:59:23 +0000 Subject: [PATCH 22/32] Fix PHPUnit compatibility issues for PHP 8.2 - Replace deprecated contains() with assertStringContainsString() - Resolves 'Call to undefined method' errors in MigrationIntegrationTest - Addresses CI failures on PHP 8.2 with MySQL 5.7 and PostgreSQL 14 Co-Authored-By: Ben Sinclair --- tests/MigrationIntegrationTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/MigrationIntegrationTest.php b/tests/MigrationIntegrationTest.php index 5599325..b5892b4 100644 --- a/tests/MigrationIntegrationTest.php +++ b/tests/MigrationIntegrationTest.php @@ -118,9 +118,9 @@ public function testMigrateMigrationsWithMysql() $usersTable = $this->pdo->query('DESCRIBE users')->fetchAll(); $this->assertSame('email', $usersTable[0]['Field']); - $this->contains('varchar', $usersTable[0]['Type']); + $this->assertStringContainsString('varchar', $usersTable[0]['Type']); $this->assertSame('user_id', $usersTable[1]['Field']); - $this->contains('int', $usersTable[1]['Type']); + $this->assertStringContainsString('int', $usersTable[1]['Type']); } public function testRollbackMigrationsWithMysql() @@ -144,8 +144,8 @@ public function testRollbackMigrationsWithMysql() $usersTable = $this->pdo->query('DESCRIBE users')->fetchAll(); $this->assertSame('email', $usersTable[0]['Field']); - $this->contains('varchar', $usersTable[0]['Type']); + $this->assertStringContainsString('varchar', $usersTable[0]['Type']); $this->assertSame('username', $usersTable[1]['Field']); - $this->contains('varchar', $usersTable[1]['Type']); + $this->assertStringContainsString('varchar', $usersTable[1]['Type']); } } From d0ce3c266e92983f0d120859a59e81ab8b2b70f9 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:04:56 +0000 Subject: [PATCH 23/32] Fix column matching logic in TableOperation.apply() for proper reduction - Use appropriate matching logic for CHANGE vs other operations - CHANGE operations match by before/after names - Other operations match by column name - Resolves testReduceRewindMigrationsWithChange failure Co-Authored-By: Ben Sinclair --- src/Operation/TableOperation.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 3d7773c..394f2cf 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -206,9 +206,17 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati $offset = array_search($options['after'], array_keys($columns)) + 1; } - // Remove existing operation for the column using before/after name matching + // Remove existing operation for the column using proper name matching foreach ($columns as $existing => $column) { - if ($column->getAfterName() === $columnOperation->getBeforeName()) { + $shouldRemove = false; + + if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { + $shouldRemove = ($column->getAfterName() === $columnOperation->getBeforeName()); + } else { + $shouldRemove = ($column->getName() === $columnOperation->getName()); + } + + if ($shouldRemove) { unset($columns[$existing]); break; } @@ -277,9 +285,17 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati foreach ($operation->getColumnOperations() as $columnOperation) { $originalOperation = $columnOperation->getOperation(); - // Remove existing operation for the column using before/after name matching + // Remove existing operation for the column using proper name matching foreach ($columns as $existing => $column) { - if ($column->getAfterName() === $columnOperation->getBeforeName()) { + $shouldRemove = false; + + if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { + $shouldRemove = ($column->getAfterName() === $columnOperation->getBeforeName()); + } else { + $shouldRemove = ($column->getName() === $columnOperation->getName()); + } + + if ($shouldRemove) { unset($columns[$existing]); $originalOperation = $column->getOperation(); break; From 370634aa12337b81b63bfda5928d0cf14eed3fde Mon Sep 17 00:00:00 2001 From: Ben Sinclair Date: Tue, 29 Jul 2025 10:53:25 +1000 Subject: [PATCH 24/32] Combine MODIFY and CHANGE conditions Resolves https://github.com/tithely/exo/pull/22/files#r724598094 --- src/Operation/TableOperation.php | 34 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 394f2cf..3b2041f 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -93,30 +93,26 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null) $columnOperations[] = $originalColumn; } - if ($columnOperation->getOperation() === ColumnOperation::MODIFY) { + if ($columnOperation->getOperation() === ColumnOperation::MODIFY || $columnOperation->getOperation() === ColumnOperation::CHANGE) { if (!$originalColumn) { throw new LogicException('Cannot revert a column that does not exist.'); } - $columnOperations[] = new ColumnOperation( - $columnOperation->getName(), - $columnOperation->getOperation(), - $originalColumn->getOptions() - ); - } - - if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { - if (!$originalColumn) { - throw new LogicException('Cannot revert a column that does not exist.'); + if ($columnOperation->getOperation() === ColumnOperation::MODIFY) { + $columnOperations[] = new ColumnOperation( + $columnOperation->getName(), + $columnOperation->getOperation(), + $originalColumn->getOptions() + ); + } else { + $options = $originalColumn->getOptions(); + $options['new_name'] = $columnOperation->getBeforeName(); + $columnOperations[] = new ColumnOperation( + $columnOperation->getAfterName(), + $columnOperation->getOperation(), + $options + ); } - - $options = $originalColumn->getOptions(); - $options['new_name'] = $columnOperation->getBeforeName(); - $columnOperations[] = new ColumnOperation( - $columnOperation->getAfterName(), - $columnOperation->getOperation(), - $options - ); } } From 973264a4184a42cb99ff8b23683565ae4cb24318 Mon Sep 17 00:00:00 2001 From: Ben Sinclair Date: Tue, 29 Jul 2025 11:04:41 +1000 Subject: [PATCH 25/32] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 63b887c..c23f70a 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,4 @@ $ composer require tithely/exo ## Documentation - [Introduction](doc/01-introduction.md) - [Usage](doc/02-usage.md) +- [Testing](doc/03-testing.md) From 0b5666514eed3309da8171c6e9faa7c489790f4b Mon Sep 17 00:00:00 2001 From: Ben Sinclair Date: Tue, 29 Jul 2025 11:05:02 +1000 Subject: [PATCH 26/32] Fix compatibility issues with newer PHP versions --- src/ExecMigration.php | 2 +- src/FunctionMigration.php | 4 ++-- src/History.php | 2 +- src/Operation/ExecOperation.php | 2 +- src/Operation/FunctionOperation.php | 4 ++-- src/Operation/ProcedureOperation.php | 2 +- src/Operation/ViewOperation.php | 2 +- src/ProcedureMigration.php | 2 +- src/ViewMigration.php | 2 +- .../20200605_alter_user_counts_view-with_context.php | 2 +- tests/Traits/UsesYamlConfig.php | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ExecMigration.php b/src/ExecMigration.php index 45cb35e..9c5ba77 100644 --- a/src/ExecMigration.php +++ b/src/ExecMigration.php @@ -33,7 +33,7 @@ public static function create(string $name): ExecMigration * @param string $name * @param string|null $body */ - private function __construct(string $name, string $body = null) + private function __construct(string $name, ?string $body = null) { $this->name = $name; $this->body = $body; diff --git a/src/FunctionMigration.php b/src/FunctionMigration.php index 200fd11..0cf50ec 100644 --- a/src/FunctionMigration.php +++ b/src/FunctionMigration.php @@ -104,13 +104,13 @@ public static function drop(string $name): FunctionMigration private function __construct( string $name, string $operation, - ReturnTypeOperation $returnType = null, + ?ReturnTypeOperation $returnType = null, bool $deterministic = false, string $dataUse = 'READS SQL DATA', string $language = 'plpgsql', array $parameterOperations = [], array $variableOperations = [], - string $body = null + ?string $body = null ) { $this->name = $name; $this->operation = $operation; diff --git a/src/History.php b/src/History.php index d51c614..7346709 100644 --- a/src/History.php +++ b/src/History.php @@ -64,7 +64,7 @@ public function add(string $version, MigrationInterface $migrationOrView) * @param array|null $versions * @return History */ - public function clone(array $versions = null): History + public function clone(?array $versions = null): History { $history = new History(); diff --git a/src/Operation/ExecOperation.php b/src/Operation/ExecOperation.php index d604250..11fe946 100644 --- a/src/Operation/ExecOperation.php +++ b/src/Operation/ExecOperation.php @@ -15,7 +15,7 @@ final class ExecOperation extends AbstractOperation * @param string $name * @param ?string $body */ - public function __construct(string $name, string $body = null) { + public function __construct(string $name, ?string $body = null) { $this->name = $name; $this->body = $body; } diff --git a/src/Operation/FunctionOperation.php b/src/Operation/FunctionOperation.php index ced0f31..7ab24e8 100644 --- a/src/Operation/FunctionOperation.php +++ b/src/Operation/FunctionOperation.php @@ -66,13 +66,13 @@ final class FunctionOperation extends AbstractOperation implements ReversibleOpe public function __construct( string $name, string $operation, - ReturnTypeOperation $returnType = null, + ?ReturnTypeOperation $returnType = null, bool $deterministic = false, string $dataUse = 'READS SQL DATA', string $language = 'plpgsql', array $parameterOperations = [], array $variableOperations = [], - string $body = null + ?string $body = null ) { $this->name = $name; $this->operation = $operation; diff --git a/src/Operation/ProcedureOperation.php b/src/Operation/ProcedureOperation.php index e92242b..f8f3df4 100644 --- a/src/Operation/ProcedureOperation.php +++ b/src/Operation/ProcedureOperation.php @@ -64,7 +64,7 @@ public function __construct( string $language = 'plpgsql', array $inParameterOperations = [], array $outParameterOperations = [], - string $body = null + ?string $body = null ) { $this->name = $name; $this->operation = $operation; diff --git a/src/Operation/ViewOperation.php b/src/Operation/ViewOperation.php index 39de8c0..175b74f 100644 --- a/src/Operation/ViewOperation.php +++ b/src/Operation/ViewOperation.php @@ -27,7 +27,7 @@ final class ViewOperation extends AbstractOperation implements ReversibleOperati * @param string $operation * @param string|null $body */ - public function __construct(string $name, string $operation, string $body = null) + public function __construct(string $name, string $operation, ?string $body = null) { $this->name = $name; $this->operation = $operation; diff --git a/src/ProcedureMigration.php b/src/ProcedureMigration.php index 84aff22..6eaff87 100644 --- a/src/ProcedureMigration.php +++ b/src/ProcedureMigration.php @@ -90,7 +90,7 @@ private function __construct( string $language = 'plpgsql', array $inParameterOperations = [], array $outParameterOperations = [], - string $body = null + ?string $body = null ) { $this->name = $name; $this->operation = $operation; diff --git a/src/ViewMigration.php b/src/ViewMigration.php index a3497bc..b82859a 100644 --- a/src/ViewMigration.php +++ b/src/ViewMigration.php @@ -61,7 +61,7 @@ public static function drop(string $name) * @param string $operation * @param string|null $body */ - private function __construct(string $name, string $operation, string $body = null) + private function __construct(string $name, string $operation, ?string $body = null) { $this->name = $name; $this->operation = $operation; diff --git a/tests/Migrations/20200605_alter_user_counts_view-with_context.php b/tests/Migrations/20200605_alter_user_counts_view-with_context.php index df3bc8e..5483db4 100644 --- a/tests/Migrations/20200605_alter_user_counts_view-with_context.php +++ b/tests/Migrations/20200605_alter_user_counts_view-with_context.php @@ -4,4 +4,4 @@ $tenant_database_name = $tenant_database_name ?? 'undefined'; return Exo\ViewMigration::alter('user_counts') - ->withBody("SELECT COUNT(id) AS user_count FROM ${tenant_database_name}.users"); + ->withBody("SELECT COUNT(id) AS user_count FROM {$tenant_database_name}.users"); diff --git a/tests/Traits/UsesYamlConfig.php b/tests/Traits/UsesYamlConfig.php index 51d4bc3..9fd7f3b 100644 --- a/tests/Traits/UsesYamlConfig.php +++ b/tests/Traits/UsesYamlConfig.php @@ -16,7 +16,7 @@ trait UsesYamlConfig * When the value cannot be found by the given key, we default to null. * @throws Exception */ - protected static function yaml(string $keys = null) + protected static function yaml(?string $keys = null) { $yaml = self::readYamlFile(); From f275b013993b2c4d3b955032d3a01234f6b8188d Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Thu, 23 Oct 2025 10:40:12 +1000 Subject: [PATCH 27/32] Support test matrix --- tests/MigrationIntegrationTest.php | 37 +++++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/tests/MigrationIntegrationTest.php b/tests/MigrationIntegrationTest.php index b5892b4..8a9d7e3 100644 --- a/tests/MigrationIntegrationTest.php +++ b/tests/MigrationIntegrationTest.php @@ -17,23 +17,24 @@ class MigrationIntegrationTest extends TestCase /** * @var PDO|null */ - private ?PDO $pdo; + private ?PDO $mysql; public function setUp(): void { - $mysql = self::yaml('handlers.mysql'); - - $this->pdo = new PDO( - sprintf('mysql:dbname=%s;host=%s;port=%s', $mysql['name'], $mysql['host'], $mysql['port']), - $mysql['user'], - $mysql['pass'] - ); - $this->pdo->exec('DROP TABLE IF EXISTS users;'); + if ($mysql = self::yaml('handlers.mysql')) { + $this->mysql = new PDO( + sprintf('mysql:dbname=%s;host=%s;port=%s', $mysql['name'], $mysql['host'], $mysql['port']), + $mysql['user'], + $mysql['pass'] + ); + + $this->mysql->exec('DROP TABLE IF EXISTS users;'); + } } public function tearDown(): void { - $this->pdo = null; + $this->mysql = null; } public function testReduceMigrationsWithChange() @@ -109,13 +110,17 @@ public function testReduceRewindMigrationsWithChange() public function testMigrateMigrationsWithMysql() { + if (!$this->mysql) { + $this->markTestSkipped('No MySQL connection'); + } + $finder = new Finder([]); $history = $finder->fromPath(__DIR__ . '/Fixtures/TestChange'); - $handler = new Handler($this->pdo, $history); + $handler = new Handler($this->mysql, $history); $handler->migrate([], null, true); - $usersTable = $this->pdo->query('DESCRIBE users')->fetchAll(); + $usersTable = $this->mysql->query('DESCRIBE users')->fetchAll(); $this->assertSame('email', $usersTable[0]['Field']); $this->assertStringContainsString('varchar', $usersTable[0]['Type']); @@ -125,9 +130,13 @@ public function testMigrateMigrationsWithMysql() public function testRollbackMigrationsWithMysql() { + if (!$this->mysql) { + $this->markTestSkipped('No MySQL connection'); + } + $finder = new Finder([]); $history = $finder->fromPath(__DIR__ . '/Fixtures/TestChange'); - $handler = new Handler($this->pdo, $history); + $handler = new Handler($this->mysql, $history); $handler->migrate([], null, true); $handler->rollback( @@ -141,7 +150,7 @@ public function testRollbackMigrationsWithMysql() true ); - $usersTable = $this->pdo->query('DESCRIBE users')->fetchAll(); + $usersTable = $this->mysql->query('DESCRIBE users')->fetchAll(); $this->assertSame('email', $usersTable[0]['Field']); $this->assertStringContainsString('varchar', $usersTable[0]['Type']); From cd770761a1d9839e00161d61a7f501f3b8260c81 Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Thu, 23 Oct 2025 10:44:31 +1000 Subject: [PATCH 28/32] Mark skipped during setup --- tests/MigrationIntegrationTest.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/MigrationIntegrationTest.php b/tests/MigrationIntegrationTest.php index 8a9d7e3..b557f09 100644 --- a/tests/MigrationIntegrationTest.php +++ b/tests/MigrationIntegrationTest.php @@ -22,13 +22,17 @@ class MigrationIntegrationTest extends TestCase public function setUp(): void { if ($mysql = self::yaml('handlers.mysql')) { - $this->mysql = new PDO( - sprintf('mysql:dbname=%s;host=%s;port=%s', $mysql['name'], $mysql['host'], $mysql['port']), - $mysql['user'], - $mysql['pass'] - ); - - $this->mysql->exec('DROP TABLE IF EXISTS users;'); + try { + $this->mysql = new PDO( + sprintf('mysql:dbname=%s;host=%s;port=%s', $mysql['name'], $mysql['host'], $mysql['port']), + $mysql['user'], + $mysql['pass'] + ); + + $this->mysql->exec('DROP TABLE IF EXISTS users;'); + } catch (\PDOException $e) { + $this->markTestSkipped('No MySQL connection'); + } } } From 889c688d4000a7eba44a5de106514f0849c71169 Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Thu, 23 Oct 2025 13:12:31 +1000 Subject: [PATCH 29/32] Simplified logic around column names When applying one table operation to another, an existing column operation's "after name" should be compared to the applied column operation's name. In cases where an "after name" is not applicable, it's always the same as the column's name --- src/Operation/TableOperation.php | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 3b2041f..8c6efcb 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -202,17 +202,9 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati $offset = array_search($options['after'], array_keys($columns)) + 1; } - // Remove existing operation for the column using proper name matching + // Remove existing operation for the column foreach ($columns as $existing => $column) { - $shouldRemove = false; - - if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { - $shouldRemove = ($column->getAfterName() === $columnOperation->getBeforeName()); - } else { - $shouldRemove = ($column->getName() === $columnOperation->getName()); - } - - if ($shouldRemove) { + if ($column->getAfterName() === $columnOperation->getName()) { unset($columns[$existing]); break; } @@ -281,17 +273,9 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati foreach ($operation->getColumnOperations() as $columnOperation) { $originalOperation = $columnOperation->getOperation(); - // Remove existing operation for the column using proper name matching + // Remove existing operation for the column foreach ($columns as $existing => $column) { - $shouldRemove = false; - - if ($columnOperation->getOperation() === ColumnOperation::CHANGE) { - $shouldRemove = ($column->getAfterName() === $columnOperation->getBeforeName()); - } else { - $shouldRemove = ($column->getName() === $columnOperation->getName()); - } - - if ($shouldRemove) { + if ($column->getAfterName() === $columnOperation->getName()) { unset($columns[$existing]); $originalOperation = $column->getOperation(); break; From e19894904647e5f9c8e8c45618d6557e622670f6 Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Thu, 23 Oct 2025 13:40:24 +1000 Subject: [PATCH 30/32] Fixed application of drop to renamed column --- src/Operation/TableOperation.php | 16 +++++++++++++--- tests/MigrationIntegrationTest.php | 12 ++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 8c6efcb..becea40 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -202,9 +202,9 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati $offset = array_search($options['after'], array_keys($columns)) + 1; } - // Remove existing operation for the column + // Remove existing operation for the column using proper name matching foreach ($columns as $existing => $column) { - if ($column->getAfterName() === $columnOperation->getName()) { + if ($column->getName() === $columnOperation->getName()) { unset($columns[$existing]); break; } @@ -272,12 +272,14 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati foreach ($operation->getColumnOperations() as $columnOperation) { $originalOperation = $columnOperation->getOperation(); + $originalName = $columnOperation->getName(); - // Remove existing operation for the column + // Remove existing operation for the column using proper name matching foreach ($columns as $existing => $column) { if ($column->getAfterName() === $columnOperation->getName()) { unset($columns[$existing]); $originalOperation = $column->getOperation(); + $originalName = $column->getBeforeName(); break; } } @@ -288,6 +290,14 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati $columns[] = $columnOperation; break; case ColumnOperation::DROP: + if ($originalOperation == ColumnOperation::CHANGE) { + $columnOperation = new ColumnOperation( + $originalName, + $columnOperation->getOperation(), + $columnOperation->getOptions() + ); + } + if ($originalOperation !== ColumnOperation::ADD) { $columns[] = $columnOperation; } diff --git a/tests/MigrationIntegrationTest.php b/tests/MigrationIntegrationTest.php index b557f09..59b6e99 100644 --- a/tests/MigrationIntegrationTest.php +++ b/tests/MigrationIntegrationTest.php @@ -3,6 +3,7 @@ namespace Exo\Tests; use Exo\Handler; +use Exo\Statement\MysqlStatementBuilder; use PDO; use Exo\Operation\ColumnOperation; use Exo\Operation\TableOperation; @@ -100,16 +101,11 @@ public function testReduceRewindMigrationsWithChange() $this->assertSame('users', $operation->getName()); $this->assertSame(TableOperation::ALTER, $operation->getOperation()); - $this->assertCount(2, $operation->getColumnOperations()); - - list($operation1, $operation2) = $operation->getColumnOperations(); + $this->assertCount(1, $operation->getColumnOperations()); + list($operation1) = $operation->getColumnOperations(); $this->assertSame('user_id', $operation1->getName()); - $this->assertSame(ColumnOperation::CHANGE, $operation1->getOperation()); - $this->assertSame('username', $operation1->getOptions()['new_name']); - - $this->assertSame('username', $operation2->getName()); - $this->assertSame(ColumnOperation::DROP, $operation2->getOperation()); + $this->assertSame(ColumnOperation::DROP, $operation1->getOperation()); } public function testMigrateMigrationsWithMysql() From 262a480d5e55af1658af605ea3cf2cca46aa053f Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Thu, 23 Oct 2025 15:03:38 +1000 Subject: [PATCH 31/32] Update logic for apply alter table to alter table --- src/Operation/TableOperation.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index becea40..2e3c4e4 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -303,15 +303,25 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati } break; case ColumnOperation::MODIFY: + $options = $columnOperation->getOptions(); + + if ($originalOperation == ColumnOperation::CHANGE) { + $options['new_name'] = $columnOperation->getName(); + } + $columns[] = new ColumnOperation( $columnOperation->getName(), $originalOperation, - $columnOperation->getOptions() + $options ); break; case ColumnOperation::CHANGE: + if ($originalOperation == ColumnOperation::MODIFY) { + $originalOperation = ColumnOperation::CHANGE; + } + $columns[] = new ColumnOperation( - $columnOperation->getAfterName(), + $columnOperation->getName(), $originalOperation, $columnOperation->getOptions() ); From 208fd2486584c43b25b87e5d3991c3d30153febd Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Thu, 23 Oct 2025 15:12:25 +1000 Subject: [PATCH 32/32] Account for CHANGE COLUM applied to ADD COLUMN --- src/Operation/TableOperation.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Operation/TableOperation.php b/src/Operation/TableOperation.php index 2e3c4e4..71734a0 100644 --- a/src/Operation/TableOperation.php +++ b/src/Operation/TableOperation.php @@ -316,12 +316,18 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati ); break; case ColumnOperation::CHANGE: + $columnName = $columnOperation->getName(); + + if ($originalOperation == ColumnOperation::ADD) { + $columnName = $columnOperation->getAfterName(); + } + if ($originalOperation == ColumnOperation::MODIFY) { $originalOperation = ColumnOperation::CHANGE; } $columns[] = new ColumnOperation( - $columnOperation->getName(), + $columnName, $originalOperation, $columnOperation->getOptions() );