Skip to content

Commit de32800

Browse files
committed
Route the remaining ResultSet and Container throw sites through named constructors
The last 5 throw sites outside the components covered by #191 to #194. Message text is byte-identical, verified template by template against the originals. - Add ResultSet\Exception\RuntimeException::forUnbufferedIteration() - Add forNonAdapterAwareDelegate() to Exception\ContainerException, and give the three reasons that AdapterInterfaceFactory and AbstractAdapterInterfaceFactory passed as inline strings their own factories: forMissingConfigService(), forMissingConfiguration() and forMissingDriver(), each composing the reason into the SERVICE template - Retype the delegator's non-AdapterAware guard from the root Exception\RuntimeException to ContainerException, which extends it, so the failure carries PSR-11's ContainerExceptionInterface as a container failure should - Own the "not found in container" wording as ContainerException::MISSING_SERVICE while still throwing the ServiceManager's own ServiceNotFoundException. Laminas annotates that class @Final, so this package cannot own it; the throw site renders the template itself, which docs/book/exceptions.md now records as the single documented exception to the convention - Derive the message assertions in the six test files the earlier PRs do not touch from the templates rather than restating them One literal assertion is deliberately left in test/unit/Container/TableIdentifierFactoryFactoryTest.php: it asserts Sql\Exception\InvalidArgumentException::EMPTY_SEPARATOR, a constant #192 introduces, so it can only be migrated once that has landed. Signed-off-by: Simon Mundy <simon.mundy@peptolab.com>
1 parent 2886bc7 commit de32800

14 files changed

Lines changed: 170 additions & 47 deletions

‎docs/book/exceptions.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ a named constructor cannot:
6464

6565
Everything else is a named constructor on the component's SPL-shaped class.
6666

67+
### The one exception to the rule
68+
69+
`PhpDb\Container\AdapterInterfaceDelegator` renders a template at the throw site rather than
70+
calling a named constructor. The ServiceManager decides the type it must throw, and Laminas
71+
annotates `ServiceNotFoundException` `@final`, so this package cannot own the class. It owns
72+
the wording instead, as `PhpDb\Exception\ContainerException::MISSING_SERVICE`. Anywhere the
73+
thrown type is ours, the rule above applies without exception.
74+
6775
## Component namespaces
6876

6977
There is exactly one marker interface, `PhpDb\Exception\ExceptionInterface`, and every

‎src/Container/AbstractAdapterInterfaceFactory.php‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,7 @@ public function __invoke(
8888
$driverClass = $this->config[$requestedName]['driver'] ?? null;
8989

9090
if (null === $driverClass) {
91-
throw ContainerException::forService(
92-
$requestedName,
93-
self::class,
94-
'no driver configured',
95-
);
91+
throw ContainerException::forMissingDriver($requestedName, self::class);
9692
}
9793

9894
/** @var DriverInterface|PdoDriverInterface $driver */

‎src/Container/AdapterInterfaceDelegator.php‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
88
use PhpDb\Adapter\AdapterAwareInterface;
99
use PhpDb\Adapter\AdapterInterface;
10-
use PhpDb\Exception;
10+
use PhpDb\Exception\ContainerException;
1111
use Psr\Container\ContainerExceptionInterface;
1212
use Psr\Container\ContainerInterface;
1313
use Psr\Container\NotFoundExceptionInterface;
@@ -33,18 +33,11 @@ public function __invoke(
3333
$instance = $callback();
3434

3535
if (! $instance instanceof AdapterAwareInterface) {
36-
throw new Exception\RuntimeException(sprintf(
37-
'Delegated service "%s" must implement %s',
38-
$name,
39-
AdapterAwareInterface::class,
40-
));
36+
throw ContainerException::forNonAdapterAwareDelegate($name, AdapterAwareInterface::class);
4137
}
4238

4339
if (! $container->has($this->adapterName)) {
44-
throw new ServiceNotFoundException(sprintf(
45-
'Service "%s" not found in container',
46-
$this->adapterName,
47-
));
40+
throw new ServiceNotFoundException(sprintf(ContainerException::MISSING_SERVICE, $this->adapterName));
4841
}
4942

5043
$databaseAdapter = $container->get($this->adapterName);

‎src/Container/AdapterInterfaceFactory.php‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,14 @@ public function __invoke(
2323
string $requestedName,
2424
): AdapterInterface&Adapter {
2525
if (! $container->has('config')) {
26-
throw ContainerException::forService(
27-
$requestedName,
28-
self::class,
29-
'Container is missing a config service',
30-
);
26+
throw ContainerException::forMissingConfigService($requestedName, self::class);
3127
}
3228

3329
$config = $container->get('config') ?? [];
3430
$adapterConfig = $config[AdapterInterface::class] ?? $config[Adapter::class] ?? [];
3531

3632
if ([] === $adapterConfig) {
37-
throw ContainerException::forService(
38-
AdapterInterface::class,
39-
self::class,
40-
"No configuration found for {$requestedName}",
41-
);
33+
throw ContainerException::forMissingConfiguration(AdapterInterface::class, self::class, $requestedName);
4234
}
4335

4436
/** @var class-string<DriverInterface>|class-string<PdoDriverInterface>|null $driverClass */

‎src/Exception/ContainerException.php‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,46 @@
1010

1111
final class ContainerException extends RuntimeException implements ContainerExceptionInterface
1212
{
13+
/**
14+
* The ServiceManager dictates the thrown type here, and Laminas annotates
15+
* ServiceNotFoundException @final, so this package owns the wording without owning the
16+
* class.
17+
*/
18+
final public const string MISSING_SERVICE = 'Service "%s" not found in container';
19+
20+
final public const string MISSING_CONFIGURATION = 'No configuration found for %s';
21+
22+
final public const string MISSING_CONFIG_SERVICE = 'Container is missing a config service';
23+
24+
final public const string MISSING_DRIVER = 'no driver configured';
25+
26+
final public const string NON_ADAPTER_AWARE_DELEGATE = 'Delegated service "%s" must implement %s';
27+
1328
final public const string SERVICE = 'Failed to create service "%s" in factory %s Reason: %s';
1429

30+
public static function forMissingConfigService(string $serviceName, string $factoryClass): self
31+
{
32+
return self::forService($serviceName, $factoryClass, self::MISSING_CONFIG_SERVICE);
33+
}
34+
35+
public static function forMissingConfiguration(
36+
string $serviceName,
37+
string $factoryClass,
38+
string $requestedName,
39+
): self {
40+
return self::forService($serviceName, $factoryClass, sprintf(self::MISSING_CONFIGURATION, $requestedName));
41+
}
42+
43+
public static function forMissingDriver(string $serviceName, string $factoryClass): self
44+
{
45+
return self::forService($serviceName, $factoryClass, self::MISSING_DRIVER);
46+
}
47+
48+
public static function forNonAdapterAwareDelegate(string $name, string $interface): self
49+
{
50+
return new self(sprintf(self::NON_ADAPTER_AWARE_DELEGATE, $name, $interface));
51+
}
52+
1553
public static function forService(
1654
string $serviceName,
1755
string $factoryClass,

‎src/ResultSet/AbstractResultSet.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ abstract class AbstractResultSet implements ResultSetInterface
4545
public function buffer(): ResultSetInterface
4646
{
4747
if ($this->buffer === -2) {
48-
throw new RuntimeException('Buffering must be enabled before iteration is started');
48+
throw RuntimeException::forUnbufferedIteration();
4949
} elseif ($this->buffer === null) {
5050
$this->buffer = [];
5151
if ($this->dataSource instanceof ResultInterface) {

‎src/ResultSet/Exception/RuntimeException.php‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,12 @@
66

77
use PhpDb\Exception;
88

9-
class RuntimeException extends Exception\RuntimeException {}
9+
class RuntimeException extends Exception\RuntimeException
10+
{
11+
final public const string UNBUFFERED_ITERATION = 'Buffering must be enabled before iteration is started';
12+
13+
public static function forUnbufferedIteration(): self
14+
{
15+
return new self(self::UNBUFFERED_ITERATION);
16+
}
17+
}

‎test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public function testGetConfigReturnsEmptyWhenContainerHasNoConfig(): void
9393
#[DataProvider('providerInvalidService')]
9494
public function testInvalidService(string $service): void
9595
{
96-
$this->expectException(ServiceNotFoundException::class);
96+
self::expectException(ServiceNotFoundException::class);
9797
$this->serviceManager->get($service);
9898
}
9999

@@ -111,8 +111,8 @@ public function testInvokeThrowsWhenDriverNotConfigured(): void
111111
$factory = new AbstractAdapterInterfaceFactory();
112112
$factory->canCreate($container, 'PhpDb\Adapter\NoDriver');
113113

114-
$this->expectException(ContainerException::class);
115-
$this->expectExceptionMessage('no driver configured');
114+
self::expectException(ContainerException::class);
115+
self::expectExceptionMessage(ContainerException::MISSING_DRIVER);
116116
$factory($container, 'PhpDb\Adapter\NoDriver');
117117
}
118118

‎test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
99
use Laminas\ServiceManager\ServiceManager;
1010
use PhpDb\Adapter\Adapter;
11+
use PhpDb\Adapter\AdapterAwareInterface;
1112
use PhpDb\Adapter\AdapterInterface;
1213
use PhpDb\Adapter\Driver\DriverInterface;
1314
use PhpDb\Adapter\Platform\PlatformInterface;
1415
use PhpDb\Container\AdapterInterfaceDelegator;
16+
use PhpDb\Exception\ContainerException;
1517
use PhpDb\Exception\RuntimeException;
1618
use PhpDb\ResultSet\ResultSetInterface;
1719
use PhpDbTest\Adapter\TestAsset\ConcreteAdapterAwareObject;
@@ -24,6 +26,8 @@
2426
use Psr\Container\NotFoundExceptionInterface;
2527
use stdClass;
2628

29+
use function sprintf;
30+
2731
#[Group('unit')]
2832
#[CoversMethod(AdapterInterfaceDelegator::class, '__construct')]
2933
#[CoversMethod(AdapterInterfaceDelegator::class, '__set_state')]
@@ -247,8 +251,8 @@ public function testSetAdapterShouldNotBeCalledForMissingAdapter(): void
247251

248252
$callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject();
249253

250-
$this->expectException(ServiceNotFoundException::class);
251-
$this->expectExceptionMessage('Service "PhpDb\Adapter\AdapterInterface" not found in container');
254+
self::expectException(ServiceNotFoundException::class);
255+
self::expectExceptionMessage(sprintf(ContainerException::MISSING_SERVICE, AdapterInterface::class));
252256

253257
(new AdapterInterfaceDelegator())(
254258
$container,
@@ -268,10 +272,12 @@ public function testSetAdapterShouldNotBeCalledForWrongClassInstance(): void
268272

269273
$callback = static fn(): stdClass => new stdClass();
270274

271-
$this->expectException(RuntimeException::class);
272-
$this->expectExceptionMessage(
273-
'Delegated service "stdClass" must implement PhpDb\Adapter\AdapterAwareInterface',
274-
);
275+
self::expectException(RuntimeException::class);
276+
self::expectExceptionMessage(sprintf(
277+
ContainerException::NON_ADAPTER_AWARE_DELEGATE,
278+
stdClass::class,
279+
AdapterAwareInterface::class,
280+
));
275281

276282
(new AdapterInterfaceDelegator())(
277283
$container,

‎test/unit/Container/AdapterInterfaceFactoryTest.php‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ public function testInvokeThrowsWhenAdapterConfigIsEmpty(): void
111111

112112
$factory = new AdapterInterfaceFactory();
113113

114-
$this->expectException(ContainerException::class);
115-
$this->expectExceptionMessage('No configuration found for');
114+
self::expectException(ContainerException::class);
115+
self::expectExceptionMessage('No configuration found for');
116116
$factory($container, AdapterInterface::class);
117117
}
118118

@@ -130,8 +130,8 @@ public function testInvokeThrowsWhenContainerHasNoConfig(): void
130130

131131
$factory = new AdapterInterfaceFactory();
132132

133-
$this->expectException(ContainerException::class);
134-
$this->expectExceptionMessage('Container is missing a config service');
133+
self::expectException(ContainerException::class);
134+
self::expectExceptionMessage(ContainerException::MISSING_CONFIG_SERVICE);
135135
$factory($container, AdapterInterface::class);
136136
}
137137
}

0 commit comments

Comments
 (0)