From 90449340541b0656457660b19b5947f5d00815e3 Mon Sep 17 00:00:00 2001 From: Nicolas Ruflin Date: Thu, 30 Apr 2026 22:27:49 +0200 Subject: [PATCH] refactor: throw typed Elastica exceptions for unsupported features Replace `throw new \Exception('Not supported')` calls in `Elastica\Client` (setAsync/getAsync/setResponseException/ getResponseException/setServerless) and the bare `\Exception` in `Elastica\Task::cancel()` with the existing typed exceptions: - `Elastica\Exception\NotImplementedException` for unsupported features on the Client. It already extends `\BadMethodCallException` and implements `Elastica\Exception\ExceptionInterface`. - `Elastica\Exception\InvalidException` for the missing-id guard in `Task::cancel()`. Callers can now `catch (Elastica\Exception\ExceptionInterface)` instead of relying on the SPL root. Update unit tests accordingly and add coverage that asserts the NotImplementedException is catchable as both `BadMethodCallException` and `ExceptionInterface`. --- CHANGELOG.md | 2 ++ src/Client.php | 11 ++++++----- src/Task.php | 8 ++++++-- tests/ClientTest.php | 38 ++++++++++++++++++++++++++++++++++++++ tests/TaskTest.php | 3 ++- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e7e1579..226c7660d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added support for "search after" based pagination [#1645](https://github.com/ruflin/Elastica/issues/1645) * Added support for the `seq_no_primary_term` search option and the `if_seq_no` / `if_primary_term` index options to enable optimistic concurrency control [#2284](https://github.com/ruflin/Elastica/pull/2284) ### Changed +* `Elastica\Client::setAsync()`, `getAsync()`, `setResponseException()`, `getResponseException()` and `setServerless()` now throw `Elastica\Exception\NotImplementedException` (a `BadMethodCallException` implementing `Elastica\Exception\ExceptionInterface`) instead of a generic `\Exception` when called. Callers can now catch a typed exception. +* `Elastica\Task::cancel()` now throws `Elastica\Exception\InvalidException` (instead of a generic `\Exception`) when no task id is set. ### Deprecated ### Removed ### Fixed diff --git a/src/Client.php b/src/Client.php index 3a56b41fe..9f88debb9 100644 --- a/src/Client.php +++ b/src/Client.php @@ -24,6 +24,7 @@ use Elastica\Exception\Bulk\ResponseException as BulkResponseException; use Elastica\Exception\ClientException; use Elastica\Exception\InvalidException; +use Elastica\Exception\NotImplementedException; use Elastica\Script\AbstractScript; use Psr\Http\Client\ClientInterface as HttpClientInterface; use Psr\Http\Message\RequestInterface; @@ -80,12 +81,12 @@ public function getTransport(): Transport public function setAsync(bool $async): self { - throw new \Exception('Not supported'); + throw new NotImplementedException('Async mode is not supported by Elastica.'); } public function getAsync(): bool { - throw new \Exception('Not supported'); + throw new NotImplementedException('Async mode is not supported by Elastica.'); } public function setElasticMetaHeader(bool $active): self @@ -102,17 +103,17 @@ public function getElasticMetaHeader(): bool public function setResponseException(bool $active): self { - throw new \Exception('Not supported'); + throw new NotImplementedException('Toggling the response-exception behaviour is not supported by Elastica.'); } public function getResponseException(): bool { - throw new \Exception('Not supported'); + throw new NotImplementedException('Toggling the response-exception behaviour is not supported by Elastica.'); } public function setServerless(bool $value): ClientInterface { - throw new \Exception('Not supported'); + throw new NotImplementedException('Serverless mode is not supported by Elastica.'); } public function getServerless(): bool diff --git a/src/Task.php b/src/Task.php index 49c11ad7f..bc35375d0 100644 --- a/src/Task.php +++ b/src/Task.php @@ -9,6 +9,7 @@ use Elastic\Elasticsearch\Exception\ServerResponseException; use Elastic\Transport\Exception\NoNodeAvailableException; use Elastica\Exception\ClientException; +use Elastica\Exception\InvalidException; /** * Represents elasticsearch task. @@ -107,12 +108,15 @@ public function isCompleted(): bool } /** - * @throws \Exception + * @throws InvalidException if no task id is set + * @throws ClientResponseException if the status code of response is 4xx + * @throws ServerResponseException if the status code of response is 5xx + * @throws ClientException */ public function cancel(): Response { if ('' === $this->_id) { - throw new \Exception('No task id given'); + throw new InvalidException('No task id given'); } return $this->_client->toElasticaResponse( diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 3bee250f5..4f5c7805c 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -6,8 +6,11 @@ use Elastica\Client; use Elastica\ClientConfiguration; +use Elastica\Exception\ExceptionInterface; use Elastica\Exception\InvalidException; +use Elastica\Exception\NotImplementedException; use Elastica\Test\Base as BaseTest; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; /** @@ -251,4 +254,39 @@ public function testRetriesConfigurationWithStringZero(): void $transport = $client->getTransport(); $this->assertEquals(0, $transport->getRetries()); } + + /** + * @return iterable + */ + public static function unsupportedClientFeaturesProvider(): iterable + { + yield 'setAsync' => [static fn (Client $client) => $client->setAsync(true)]; + yield 'getAsync' => [static fn (Client $client) => $client->getAsync()]; + yield 'setResponseException' => [static fn (Client $client) => $client->setResponseException(true)]; + yield 'getResponseException' => [static fn (Client $client) => $client->getResponseException()]; + yield 'setServerless' => [static fn (Client $client) => $client->setServerless(true)]; + } + + #[DataProvider('unsupportedClientFeaturesProvider')] + public function testUnsupportedFeaturesThrowNotImplementedException(callable $invocation): void + { + $client = new Client(); + + $this->expectException(NotImplementedException::class); + + $invocation($client); + } + + public function testNotImplementedExceptionIsCatchableAsElasticaException(): void + { + $client = new Client(); + + try { + $client->setAsync(true); + $this->fail('Expected NotImplementedException was not thrown.'); + } catch (ExceptionInterface $e) { + $this->assertInstanceOf(NotImplementedException::class, $e); + $this->assertInstanceOf(\BadMethodCallException::class, $e); + } + } } diff --git a/tests/TaskTest.php b/tests/TaskTest.php index 67d3ea7d7..a5ace7938 100644 --- a/tests/TaskTest.php +++ b/tests/TaskTest.php @@ -5,6 +5,7 @@ namespace Elastica\Test; use Elastica\Document; +use Elastica\Exception\InvalidException; use Elastica\Index; use Elastica\Task; use PHPUnit\Framework\Attributes\Group; @@ -61,7 +62,7 @@ public function testRefreshWithOptionsContainingOnWaitForResponseTrue(): void #[Group('unit')] public function testCancelThrowsExceptionWithEmptyTaskId(): void { - $this->expectException(\Exception::class); + $this->expectException(InvalidException::class); $this->expectExceptionMessage('No task id given'); $task = new Task($this->_getClient(), '');