diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 3413f0e..1af883b 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,14 +15,10 @@ jobs: strategy: matrix: php: - - version: '8.2' - composer_flags: --prefer-lowest - version: '8.3' composer_flags: --prefer-lowest - version: '8.4' composer_flags: --prefer-lowest - - version: '8.2' - composer_flags: --prefer-stable - version: '8.3' composer_flags: --prefer-stable - version: '8.4' @@ -62,7 +58,7 @@ jobs: run: ./bin/phpunit --coverage-text --coverage-clover clover.xml - name: Upload Scrutinizer coverage - if: matrix.php.version == '8.2' && matrix.php.composer_flags == '--prefer-stable' + if: matrix.php.version == '8.3' && matrix.php.composer_flags == '--prefer-stable' uses: sudo-bot/action-scrutinizer@latest with: cli-args: "--format=php-clover clover.xml --revision=${{ github.event.pull_request.head.sha || github.sha }}" \ No newline at end of file diff --git a/composer.json b/composer.json index 3fc7382..5553ded 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "A library to manage patch requests", "keywords": ["rest", "patch", "api", "symfony", "bundle"], "require": { - "php": "^8.2", + "php": "^8.3", "ext-json": "*", "symfony/property-access": "^6.0 || ^7.0", "symfony/http-foundation": "^6.0 || ^7.0", diff --git a/src/PatchManager/Event/PatchManagerEvent.php b/src/PatchManager/Event/PatchManagerEvent.php index 26b08df..eae9170 100644 --- a/src/PatchManager/Event/PatchManagerEvent.php +++ b/src/PatchManager/Event/PatchManagerEvent.php @@ -8,16 +8,12 @@ use Cypress\PatchManager\Patchable; use Symfony\Contracts\EventDispatcher\Event; -class PatchManagerEvent extends Event +abstract class PatchManagerEvent extends Event { - private MatchedPatchOperation $matchedPatchOperation; - - private Patchable $subject; - - public function __construct(MatchedPatchOperation $matchedPatchOperation, Patchable $subject) - { - $this->matchedPatchOperation = $matchedPatchOperation; - $this->subject = $subject; + public function __construct( + private readonly MatchedPatchOperation $matchedPatchOperation, + private readonly Patchable $subject + ) { } public function getMatchedPatchOperation(): MatchedPatchOperation diff --git a/src/PatchManager/Event/PatchManagerEvents.php b/src/PatchManager/Event/PatchManagerEvents.php index 8670602..c8b0d3d 100644 --- a/src/PatchManager/Event/PatchManagerEvents.php +++ b/src/PatchManager/Event/PatchManagerEvents.php @@ -7,12 +7,12 @@ final class PatchManagerEvents { /** - * this event gets fired before calling an handler + * this event gets fired before calling a handler */ - public const PATCH_MANAGER_PRE = 'patch_manager.pre'; + public const string PATCH_MANAGER_PRE = 'patch_manager.pre'; /** - * this event gets fired after calling an handler + * this event gets fired after calling a handler */ - public const PATCH_MANAGER_POST = 'patch_manager.post'; + public const string PATCH_MANAGER_POST = 'patch_manager.post'; } diff --git a/src/PatchManager/Event/PostHandlerInvocationEvent.php b/src/PatchManager/Event/PostHandlerInvocationEvent.php new file mode 100644 index 0000000..2ab080b --- /dev/null +++ b/src/PatchManager/Event/PostHandlerInvocationEvent.php @@ -0,0 +1,27 @@ +exception; + } +} diff --git a/src/PatchManager/Event/PreHandlerInvocationEvent.php b/src/PatchManager/Event/PreHandlerInvocationEvent.php new file mode 100644 index 0000000..ceaf841 --- /dev/null +++ b/src/PatchManager/Event/PreHandlerInvocationEvent.php @@ -0,0 +1,9 @@ +|Patchable|\Traversable $subject a Patchable instance or a collection of instances + * @param iterable|Patchable $subject a Patchable instance or a collection of instances * @throws Exception\InvalidJsonRequestContent * @throws Exception\MissingOperationNameRequest * @throws Exception\MissingOperationRequest * @throws HandlerNotFoundException */ - public function handle($subject): void + public function handle(Patchable|iterable $subject): void { $matchedOperations = $this->getMatchedOperations($subject); $this->handleSubject($subject, $matchedOperations); @@ -50,11 +52,27 @@ public function handle($subject): void protected function doHandle(MatchedPatchOperation $matchedPatchOperation, Patchable $subject): void { - $event = new PatchManagerEvent($matchedPatchOperation, $subject); - $this->dispatchEvents($event, $matchedPatchOperation->getOpName(), PatchManagerEvents::PATCH_MANAGER_PRE); + $this->dispatchEvents( + event: new PreHandlerInvocationEvent($matchedPatchOperation, $subject), + opName: $matchedPatchOperation->getOpName(), + type: PatchManagerEvents::PATCH_MANAGER_PRE + ); + + $exception = null; - $matchedPatchOperation->process($subject); - $this->dispatchEvents($event, $matchedPatchOperation->getOpName(), PatchManagerEvents::PATCH_MANAGER_POST); + try { + $matchedPatchOperation->process($subject); + } catch (\Throwable $e) { + $exception = $e; + + throw $e; + } finally { + $this->dispatchEvents( + event: new PostHandlerInvocationEvent($matchedPatchOperation, $subject, $exception), + opName: $matchedPatchOperation->getOpName(), + type: PatchManagerEvents::PATCH_MANAGER_POST + ); + } } /** diff --git a/tests/PatchManager/Bundle/DependencyInjection/PatchManagerCompilerPassTest.php b/tests/PatchManager/Bundle/DependencyInjection/PatchManagerCompilerPassTest.php index 931b8de..5481b6b 100644 --- a/tests/PatchManager/Bundle/DependencyInjection/PatchManagerCompilerPassTest.php +++ b/tests/PatchManager/Bundle/DependencyInjection/PatchManagerCompilerPassTest.php @@ -1,9 +1,12 @@ compilerPass = new PatchManagerCompilerPass(); } - public function testProcessWithoutDefinition(): void + #[Test] + public function processWithoutDefinition(): void { try { $this->cb->hasDefinition("patch_manager.operation_matcher")->willReturn(false); @@ -36,7 +40,8 @@ public function testProcessWithoutDefinition(): void $this->assertTrue(true); } - public function testProcess(): void + #[Test] + public function shouldProcessSucceed(): void { $this->cb->findTaggedServiceIds()->willReturn(['test.service' => 'test', 'test.service2' => 'test2']); $definition = $this->prophesize(Definition::class); diff --git a/tests/PatchManager/Bundle/PatchManagerBundleTest.php b/tests/PatchManager/Bundle/PatchManagerBundleTest.php index 3833978..1c8b436 100644 --- a/tests/PatchManager/Bundle/PatchManagerBundleTest.php +++ b/tests/PatchManager/Bundle/PatchManagerBundleTest.php @@ -1,17 +1,21 @@ prophesize(ContainerBuilder::class); $cb->addCompilerPass(new PatchManagerCompilerPass())->shouldBeCalled()->willReturn($cb->reveal()); diff --git a/tests/PatchManager/Bundle/RequestAdapter/RequestStackAdapterTest.php b/tests/PatchManager/Bundle/RequestAdapter/RequestStackAdapterTest.php index 9c5023d..3c401cf 100644 --- a/tests/PatchManager/Bundle/RequestAdapter/RequestStackAdapterTest.php +++ b/tests/PatchManager/Bundle/RequestAdapter/RequestStackAdapterTest.php @@ -1,16 +1,20 @@ prophesize(Request::class); $currentRequest->getContent()->willReturn('{"op":"data"}'); diff --git a/tests/PatchManager/FakeObjects/SubjectA.php b/tests/PatchManager/FakeObjects/SubjectA.php index 6151143..b4646de 100644 --- a/tests/PatchManager/FakeObjects/SubjectA.php +++ b/tests/PatchManager/FakeObjects/SubjectA.php @@ -1,5 +1,7 @@ handler = new FiniteHandler($finiteFactory); } - public function testGetName(): void + #[Test] + public function getNameShouldReturnTheHandlerIdentifier(): void { $this->assertEquals('sm', $this->handler->getName()); } - public function testHandleOk(): void + #[Test] + public function handleShouldSucceed(): void { $patchable = new FiniteSubject(); $this->stateMachine->setObject($patchable); @@ -59,7 +64,8 @@ public function testHandleOk(): void $this->assertEquals('s2', $patchable->getFiniteState()); } - public function testHandleWithException(): void + #[Test] + public function handleShouldReturnAnException(): void { $this->expectException(StateException::class); $patchable = new FiniteSubject(); @@ -78,7 +84,8 @@ public function testHandleWithException(): void ); } - public function testHandleWrongWithCheck(): void + #[Test] + public function handleWithWrongCheck(): void { $patchable = new FiniteSubject(); $this->stateMachine->setObject($patchable); @@ -97,7 +104,8 @@ public function testHandleWrongWithCheck(): void $this->assertEquals('s1', $patchable->getFiniteState()); } - public function testConfigureOptions(): void + #[Test] + public function shouldConfigureOptions(): void { $or = new OptionsResolver(); $this->handler->configureOptions($or); diff --git a/tests/PatchManager/MatchedPatchOperationTest.php b/tests/PatchManager/MatchedPatchOperationTest.php index d84fdd5..82b8ad9 100644 --- a/tests/PatchManager/MatchedPatchOperationTest.php +++ b/tests/PatchManager/MatchedPatchOperationTest.php @@ -1,21 +1,26 @@ mockHandler('data')->reveal()); $this->assertTrue($mpo->matchFor('data')); $this->assertFalse($mpo->matchFor('method')); } - public function testProcess(): void + #[Test] + public function process(): void { $handler = $this->mockHandler('data'); $handler->handle(Argument::any(), Argument::any())->shouldBeCalled(); diff --git a/tests/PatchManager/OperationDataTest.php b/tests/PatchManager/OperationDataTest.php index 17dd397..b798b5a 100644 --- a/tests/PatchManager/OperationDataTest.php +++ b/tests/PatchManager/OperationDataTest.php @@ -7,34 +7,40 @@ use Cypress\PatchManager\OperationData; use PhpCollection\Sequence; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; class OperationDataTest extends PatchManagerTestCase { - public function testGetOpWithEmptyData(): void + #[Test] + public function getOpWithEmptyData(): void { $od = new OperationData(); $this->assertNull($od->getOp()->getOrElse(null)); } - public function testGetOpWithData(): void + #[Test] + public function getOpWithData(): void { $od = new OperationData(['op' => 'data']); $this->assertEquals('data', $od->getOp()->getOrElse(null)); } - public function testGetDataWithEmptyData(): void + #[Test] + public function getDataWithEmptyData(): void { $od = new OperationData(); $this->assertTrue($od->getData()->isEmpty()); } - public function testGetDataWithOpOnly(): void + #[Test] + public function getDataWithOpOnly(): void { $od = new OperationData(['op' => 'data']); $this->assertTrue($od->getData()->isEmpty()); } - public function testGetDataWithData(): void + #[Test] + public function getDataWithData(): void { $od = new OperationData(['op' => 'data', 'test' => 1, 'test2' => '2']); $this->assertFalse($od->getData()->isEmpty()); @@ -46,7 +52,8 @@ public function testGetDataWithData(): void } #[DataProvider("diffKeysProvider")] - public function testDiffKeys(Sequence $expected, array $requiredKeys): void + #[Test] + public function diffKeys(Sequence $expected, array $requiredKeys): void { $od = new OperationData(['op' => 'data', 'test' => 1, 'test2' => '2']); $this->assertEquals($expected, $od->diffKeys($requiredKeys)); diff --git a/tests/PatchManager/OperationMatcherTest.php b/tests/PatchManager/OperationMatcherTest.php index ab897fd..4a9ffb8 100644 --- a/tests/PatchManager/OperationMatcherTest.php +++ b/tests/PatchManager/OperationMatcherTest.php @@ -1,5 +1,7 @@ ops = new Sequence(); $this->ops->add(['op' => 'data']); - $operations->shouldReceive('all')->andReturn($this->ops)->byDefault(); + $operations->allows('all')->andReturns($this->ops)->byDefault(); $this->matcher = new OperationMatcher($operations); } - public function testGetMatchedOperationsWithoutHandlers(): void + #[Test] + public function getMatchedOperationsWithoutHandlers(): void { $this->assertEquals(new Sequence(), $this->matcher->getMatchedOperations(new SubjectA())); } - public function testGetMatchedOperationsWithHandlerNotMatching(): void + #[Test] + public function getMatchedOperationsWithHandlerNotMatching(): void { $this->matcher->addHandler($this->mockHandler('method')->reveal()); $this->assertInstanceOf(Sequence::class, $this->matcher->getMatchedOperations(new SubjectA())); $this->assertCount(0, $this->matcher->getMatchedOperations(new SubjectA())); } - public function testGetMatchedOperationsWithMatchingHandler(): void + #[Test] + public function getMatchedOperationsWithMatchingHandler(): void { $this->matcher->addHandler($this->mockHandler('data')->reveal()); $this->assertInstanceOf(Sequence::class, $this->matcher->getMatchedOperations(new SubjectA())); @@ -49,7 +55,8 @@ public function testGetMatchedOperationsWithMatchingHandler(): void ); } - public function testGetMatchedOperationsWithMultipleOperationsMatching(): void + #[Test] + public function getMatchedOperationsWithMultipleOperationsMatching(): void { $this->ops->add(['op' => 'data']); $this->matcher->addHandler($this->mockHandler('data')->reveal()); @@ -58,7 +65,8 @@ public function testGetMatchedOperationsWithMultipleOperationsMatching(): void $this->assertCount(2, $mpos->filter($this->handlerNameMatcher('data'))); } - public function testGetMatchedOperationsWithMultipleOperationsMatchingMultipleHandlers(): void + #[Test] + public function getMatchedOperationsWithMultipleOperationsMatchingMultipleHandlers(): void { $this->ops->add(['op' => 'method']); $this->matcher->addHandler($this->mockHandler('data')->reveal()); @@ -72,7 +80,8 @@ public function testGetMatchedOperationsWithMultipleOperationsMatchingMultipleHa ); } - public function testGetUnmatchedOperationsWithHandlerNotMatching(): void + #[Test] + public function getUnmatchedOperationsWithHandlerNotMatching(): void { $this->matcher->addHandler($this->mockHandler('method')->reveal()); $this->assertInstanceOf(Sequence::class, $this->matcher->getMatchedOperations(new SubjectA())); @@ -81,7 +90,8 @@ public function testGetUnmatchedOperationsWithHandlerNotMatching(): void $this->assertEquals(new Sequence(['data']), $this->matcher->getUnmatchedOperations(new SubjectA())); } - public function testHandlerThatRespondsFalseToCanHandle(): void + #[Test] + public function handlerThatRespondsFalseToCanHandle(): void { $this->matcher->addHandler($this->mockHandler('data', false)->reveal()); $this->assertCount(0, $this->matcher->getMatchedOperations(new SubjectA())); @@ -92,11 +102,8 @@ public function testHandlerThatRespondsFalseToCanHandle(): void $this->assertEquals(new Sequence(['data']), $this->matcher->getUnmatchedOperations(new SubjectA())); } - /** - * @param string $name - */ - private function handlerNameMatcher($name): \Closure + private function handlerNameMatcher(string $name): \Closure { - return fn (MatchedPatchOperation $mpo) => $mpo->matchFor($name); + return static fn (MatchedPatchOperation $mpo) => $mpo->matchFor($name); } } diff --git a/tests/PatchManager/PatchManagerTest.php b/tests/PatchManager/PatchManagerTest.php index 6680e23..688e8b1 100644 --- a/tests/PatchManager/PatchManagerTest.php +++ b/tests/PatchManager/PatchManagerTest.php @@ -1,5 +1,7 @@ */ - private $operationMatcher; + private ObjectProphecy $operationMatcher; /** - * @var \Prophecy\Prophecy\ObjectProphecy + * @var ObjectProphecy */ - private $eventDispatcher; + private ObjectProphecy $eventDispatcher; - /** - * @var PatchManager - */ - private $patchManager; + private PatchManager $patchManager; public function setUp(): void { @@ -42,7 +46,8 @@ public function setUp(): void $this->patchManager->setEventDispatcherInterface($this->eventDispatcher->reveal()); } - public function testStrictMode(): void + #[Test] + public function strictMode(): void { $this->expectException(HandlerNotFoundException::class); $this->expectExceptionMessage("test"); @@ -51,7 +56,8 @@ public function testStrictMode(): void $pm->handle(new SubjectA()); } - public function testStrictModeMultipleOps(): void + #[Test] + public function gtrictModeMultipleOps(): void { $this->expectException(HandlerNotFoundException::class); $this->expectExceptionMessage("test, test2"); @@ -60,7 +66,8 @@ public function testStrictModeMultipleOps(): void $pm->handle(new SubjectA()); } - public function testArraySubject(): void + #[Test] + public function arraySubject(): void { $handler = $this->mockHandler('data'); $handler->handle(Argument::any(), Argument::any())->shouldBeCalled(); @@ -81,7 +88,8 @@ public function testArraySubject(): void $pm->handle([new SubjectA(), new FakeObjects\SubjectB()]); } - public function testSequenceSubject(): void + #[Test] + public function sequenceSubject(): void { $handler = $this->mockHandler('data'); $handler->handle(Argument::any(), Argument::any())->shouldBeCalled(); @@ -108,4 +116,40 @@ public function testSequenceSubject(): void $pm->handle(new Sequence([new SubjectA(), new FakeObjects\SubjectB()])); } + + #[Test] + public function handlerThrowsAnExceptionAndSendEvents(): void + { + $handler = $this->prophesize(PatchOperationHandler::class); + $handler->configureOptions(Argument::any())->shouldBeCalled(); + $handler->getName()->willReturn('data'); + $handler->handle(Argument::any(), Argument::any()) + ->willThrow(\LogicException::class) + ->shouldBeCalled(); + $handler->canHandle(Argument::type(SubjectA::class))->willReturn(true); + + $requestAdapter = $this->prophesize(Adapter::class); + $requestAdapter->getRequestBody()->willReturn('{"op": "data"}'); + $operations = new Operations($requestAdapter->reveal()); + + $operationMatcher = new OperationMatcher($operations); + $operationMatcher->addHandler($handler->reveal()); + + $mockEventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $mockEventDispatcher->dispatch( + Argument::type(PatchManagerEvent::class), + Argument::type('string') + )->shouldBeCalledTimes(4); + + $pm = new PatchManager($operationMatcher, true); + $pm->setEventDispatcherInterface( + $mockEventDispatcher->reveal() + ); + + try { + $pm->handle(new SubjectA()); + } catch (\LogicException $e) { + $this->assertEquals(\LogicException::class, $e::class); + } + } } diff --git a/tests/PatchManager/PatchManagerTestCase.php b/tests/PatchManager/PatchManagerTestCase.php index 3a70452..58b9c8d 100644 --- a/tests/PatchManager/PatchManagerTestCase.php +++ b/tests/PatchManager/PatchManagerTestCase.php @@ -1,14 +1,16 @@ */ - protected function mockHandler(?string $name, bool $canHandle = true) + protected function mockHandler(?string $name, bool $canHandle = true): ObjectProphecy { $handler = $this->prophesize(PatchOperationHandler::class); if (!is_null($name)) { @@ -35,12 +37,4 @@ protected function mockHandler(?string $name, bool $canHandle = true) return $handler; } - - /** - * @param null $handlerName - */ - protected function getMatchedPatchOperation($handlerName = null): MatchedPatchOperation - { - return MatchedPatchOperation::create([], $this->mockHandler($handlerName)->reveal()); - } } diff --git a/tests/PatchManager/Request/OperationsTest.php b/tests/PatchManager/Request/OperationsTest.php index 00bd217..7bcae9c 100644 --- a/tests/PatchManager/Request/OperationsTest.php +++ b/tests/PatchManager/Request/OperationsTest.php @@ -1,5 +1,7 @@ expectException(InvalidJsonRequestContent::class); $adapter = $this->prophesize(Adapter::class); @@ -20,7 +24,8 @@ public function testRequestWithInvalidJson(): void $operations->all(); } - public function testExeceptionWithNullRequest(): void + #[Test] + public function exceptionWithNullRequest(): void { $this->expectException(InvalidJsonRequestContent::class); $adapter = $this->prophesize(Adapter::class); @@ -29,7 +34,8 @@ public function testExeceptionWithNullRequest(): void $operations->all(); } - public function testCorrectOperationsNumberWithOneOperation(): void + #[Test] + public function correctOperationsNumberWithOneOperation(): void { $adapter = $this->prophesize(Adapter::class); $adapter->getRequestBody()->willReturn('{"op": "data"}'); @@ -40,7 +46,8 @@ public function testCorrectOperationsNumberWithOneOperation(): void $this->assertEquals('data', $op['op']); } - public function testCorrectOperationsNumberWithMultipleOperation(): void + #[Test] + public function correctOperationsNumberWithMultipleOperation(): void { $adapter = $this->prophesize(Adapter::class); $adapter->getRequestBody()->willReturn('[{"op": "data"},{"op": "data2"}]'); @@ -56,7 +63,8 @@ public function testCorrectOperationsNumberWithMultipleOperation(): void $this->assertEquals('data2', $op2['op']); } - public function testExeceptionWithEmptyRequest(): void + #[Test] + public function execeptionWithEmptyRequest(): void { $this->expectException(MissingOperationRequest::class); $adapter = $this->prophesize(Adapter::class); @@ -65,7 +73,8 @@ public function testExeceptionWithEmptyRequest(): void $operations->all(); } - public function testExeceptionWithOperationWithoutOp(): void + #[Test] + public function exceptionWithOperationWithoutOp(): void { $this->expectException(MissingOperationNameRequest::class); $adapter = $this->prophesize(Adapter::class); @@ -74,7 +83,8 @@ public function testExeceptionWithOperationWithoutOp(): void $operations->all(); } - public function testExeceptionWithMultipleOperationWithoutOp(): void + #[Test] + public function exceptionWithMultipleOperationWithoutOp(): void { $this->expectException(MissingOperationNameRequest::class); $adapter = $this->prophesize(Adapter::class);