Skip to content

Commit 3697984

Browse files
committed
Guard nullable state in the TableGateway features
- Make EventFeature::$event non-nullable and assign it explicitly - Resolve GlobalAdapterFeature::getStaticAdapter through a coalesce chain and throw when no adapter is registered - Make MasterSlaveFeature::$masterSql nullable and throw when the gateway has no Sql instance or postInitialize has not run - Throw in SequenceFeature when a statement yields no result, when the sequence returns no usable value and when an insert exposes no arrays - Interpolate the SequenceFeature sequence statements and search insert columns strictly - Resolve the MetadataFeature table through getTable(), throw when it is not a named table, and take the primary key with reset() - Extract RowGatewayFeature::primaryKeyFromMetadata(), flatten the prototype branches and throw when the gateway has no named table - Replace the deprecated setArrayObjectPrototype() calls with setRowPrototype()
1 parent 5c6e701 commit 3697984

6 files changed

Lines changed: 163 additions & 73 deletions

File tree

src/TableGateway/Feature/EventFeature.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class EventFeature extends AbstractFeature implements EventFeatureEventsInterfac
2626
{
2727
protected EventManagerInterface $eventManager;
2828

29-
protected ?EventFeature\TableGatewayEvent $event;
29+
protected EventFeature\TableGatewayEvent $event;
3030

3131
public function __construct(
3232
?EventManagerInterface $eventManager = null,
@@ -40,7 +40,9 @@ public function __construct(
4040
TableGateway::class,
4141
]);
4242

43-
$this->event = $tableGatewayEvent ?: new EventFeature\TableGatewayEvent();
43+
$this->event = $tableGatewayEvent instanceof EventFeature\TableGatewayEvent
44+
? $tableGatewayEvent
45+
: new EventFeature\TableGatewayEvent();
4446
}
4547

4648
/**

src/TableGateway/Feature/GlobalAdapterFeature.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,13 @@ public static function getStaticAdapter(): AdapterInterface
2424
{
2525
$class = static::class;
2626

27-
// class specific adapter
28-
if (isset(static::$staticAdapters[$class])) {
29-
return static::$staticAdapters[$class];
30-
}
27+
$adapter = static::$staticAdapters[$class] ?? static::$staticAdapters[self::class] ?? null;
3128

32-
// default adapter
33-
if (isset(static::$staticAdapters[self::class])) {
34-
return static::$staticAdapters[self::class];
29+
if (! $adapter instanceof AdapterInterface) {
30+
throw new Exception\RuntimeException('No database adapter was found in the static registry.');
3531
}
3632

37-
throw new Exception\RuntimeException('No database adapter was found in the static registry.');
33+
return $adapter;
3834
}
3935

4036
/**

src/TableGateway/Feature/MasterSlaveFeature.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
use PhpDb\Adapter\AdapterInterface;
88
use PhpDb\Sql\Sql;
9+
use PhpDb\TableGateway\Exception;
910

1011
final class MasterSlaveFeature extends AbstractFeature
1112
{
1213
protected AdapterInterface $slaveAdapter;
1314

14-
protected Sql $masterSql;
15+
protected ?Sql $masterSql = null;
1516

1617
protected ?Sql $slaveSql = null;
1718

@@ -35,24 +36,41 @@ public function getSlaveSql(): ?Sql
3536

3637
/**
3738
* after initialization, retrieve the original adapter as "master"
39+
*
40+
* @throws Exception\RuntimeException
3841
*/
3942
public function postInitialize(): void
4043
{
41-
$this->masterSql = $this->tableGateway->sql;
44+
$masterSql = $this->tableGateway->sql;
45+
if (! $masterSql instanceof Sql) {
46+
throw new Exception\RuntimeException(
47+
'The table gateway must be initialized with a Sql instance before this feature is applied.',
48+
);
49+
}
50+
51+
$this->masterSql = $masterSql;
4252
if (null === $this->slaveSql) {
4353
$this->slaveSql = new Sql(
4454
$this->slaveAdapter,
45-
$this->tableGateway->sql->getTable(),
55+
$masterSql->getTable(),
4656
);
4757
}
4858
}
4959

5060
/**
5161
* postSelect()
5262
* Ensure to return to the master adapter
63+
*
64+
* @throws Exception\RuntimeException
5365
*/
5466
public function postSelect(): void
5567
{
68+
if (! $this->masterSql instanceof Sql) {
69+
throw new Exception\RuntimeException(
70+
'The master Sql instance is not available; postInitialize() has not been run.',
71+
);
72+
}
73+
5674
$this->tableGateway->sql = $this->masterSql;
5775
}
5876

src/TableGateway/Feature/MetadataFeature.php

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use function count;
1313
use function current;
1414
use function is_array;
15+
use function is_string;
16+
use function reset;
1517

1618
/**
1719
* @api
@@ -30,28 +32,43 @@ public function __construct(
3032
];
3133
}
3234

35+
/**
36+
* @throws Exception\RuntimeException
37+
*/
3338
public function postInitialize(): void
3439
{
3540
// localize variable for brevity
3641
$t = $this->tableGateway;
3742
$m = $this->metadata;
3843

39-
$tableGatewayTable = is_array($t->table) ? current($t->table) : $t->table;
44+
$tableGatewayTable = $t->getTable();
45+
if (is_array($tableGatewayTable)) {
46+
$tableGatewayTable = current($tableGatewayTable);
47+
}
4048

41-
if ($tableGatewayTable instanceof TableIdentifier) {
42-
$table = $tableGatewayTable->getTable();
43-
$schema = $tableGatewayTable->getSchema();
44-
} else {
45-
$table = $tableGatewayTable;
46-
$schema = null;
49+
if (! $tableGatewayTable instanceof TableIdentifier && ! is_string($tableGatewayTable)) {
50+
throw new Exception\RuntimeException(
51+
'The table gateway must reference a named table before metadata can be resolved.',
52+
);
4753
}
4854

55+
$table = $tableGatewayTable instanceof TableIdentifier
56+
? $tableGatewayTable->getTable()
57+
: $tableGatewayTable;
58+
59+
$schema = $tableGatewayTable instanceof TableIdentifier
60+
? $tableGatewayTable->getSchema()
61+
: null;
62+
4963
// get column named
5064
$columns = $m->getColumnNames($table, $schema);
5165
$t->columns = $columns;
5266

5367
// set locally
54-
$this->sharedData['metadata']['columns'] = $columns;
68+
$metadata = $this->sharedData['metadata'] ?? [];
69+
$metadata = is_array($metadata) ? $metadata : [];
70+
$metadata['columns'] = $columns;
71+
$this->sharedData['metadata'] = $metadata;
5572

5673
// process primary key only if table is a table; there are no PK constraints on views
5774
if (! $m->getTable($table, $schema) instanceof TableObject) {
@@ -74,12 +91,9 @@ public function postInitialize(): void
7491
}
7592

7693
$pkcColumns = $pkc->getColumns();
77-
if (count($pkcColumns) === 1) {
78-
$primaryKey = $pkcColumns[0];
79-
} else {
80-
$primaryKey = $pkcColumns;
81-
}
94+
$primaryKey = 1 === count($pkcColumns) ? reset($pkcColumns) : $pkcColumns;
8295

83-
$this->sharedData['metadata']['primaryKey'] = $primaryKey;
96+
$metadata['primaryKey'] = $primaryKey;
97+
$this->sharedData['metadata'] = $metadata;
8498
}
8599
}

src/TableGateway/Feature/RowGatewayFeature.php

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
use PhpDb\ResultSet\ResultSet;
88
use PhpDb\RowGateway\RowGateway;
99
use PhpDb\RowGateway\RowGatewayInterface;
10+
use PhpDb\Sql\TableIdentifier;
1011
use PhpDb\TableGateway\Exception;
1112

13+
use function is_array;
1214
use function is_string;
1315

1416
final class RowGatewayFeature extends AbstractFeature
@@ -21,50 +23,81 @@ public function __construct(mixed ...$constructorArguments)
2123
$this->constructorArguments = $constructorArguments;
2224
}
2325

26+
/**
27+
* @throws Exception\RuntimeException
28+
*/
2429
public function postInitialize(): void
2530
{
26-
$args = $this->constructorArguments;
27-
28-
/** @var ResultSet $resultSetPrototype */
2931
$resultSetPrototype = $this->tableGateway->resultSetPrototype;
30-
31-
if (! $this->tableGateway->resultSetPrototype instanceof ResultSet) {
32+
if (! $resultSetPrototype instanceof ResultSet) {
3233
throw new Exception\RuntimeException(
3334
'This feature ' . self::class . ' expects the ResultSet to be an instance of ' . ResultSet::class,
3435
);
3536
}
3637

37-
if (isset($args[0])) {
38-
if (is_string($args[0])) {
39-
$primaryKey = $args[0];
40-
$rowGatewayPrototype = new RowGateway(
41-
$primaryKey,
42-
$this->tableGateway->table,
43-
$this->tableGateway->adapter,
44-
);
45-
$resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype);
46-
} elseif ($args[0] instanceof RowGatewayInterface) {
47-
$rowGatewayPrototype = $args[0];
48-
$resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype);
49-
}
50-
} else {
51-
// get from metadata feature
52-
$metadata = $this->tableGateway->featureSet->getFeatureByClassName(
53-
MetadataFeature::class,
38+
$firstArgument = $this->constructorArguments[0] ?? null;
39+
40+
if ($firstArgument instanceof RowGatewayInterface) {
41+
$resultSetPrototype->setRowPrototype($firstArgument);
42+
return;
43+
}
44+
45+
if (null !== $firstArgument && ! is_string($firstArgument)) {
46+
return;
47+
}
48+
49+
$primaryKey = $firstArgument ?? $this->primaryKeyFromMetadata();
50+
51+
$table = $this->tableGateway->table;
52+
if (! is_string($table) && ! $table instanceof TableIdentifier) {
53+
throw new Exception\RuntimeException(
54+
'The table gateway must reference a named table before a RowGateway prototype can be created.',
55+
);
56+
}
57+
58+
$resultSetPrototype->setRowPrototype(new RowGateway(
59+
$primaryKey,
60+
$table,
61+
$this->tableGateway->adapter,
62+
));
63+
}
64+
65+
/**
66+
* @return string|array<array-key, mixed>
67+
*
68+
* @throws Exception\RuntimeException
69+
*
70+
* @mago-expect analysis:mixed-assignment
71+
*/
72+
private function primaryKeyFromMetadata(): string|array
73+
{
74+
$featureSet = $this->tableGateway->featureSet;
75+
$metadata = $featureSet instanceof FeatureSet
76+
? $featureSet->getFeatureByClassName(MetadataFeature::class)
77+
: null;
78+
79+
$metadataData = $metadata instanceof MetadataFeature
80+
? $metadata->sharedData['metadata'] ?? null
81+
: null;
82+
83+
if (null === $metadataData) {
84+
throw new Exception\RuntimeException(
85+
'No information was provided to the RowGatewayFeature and/or no MetadataFeature could be consulted '
86+
. 'to find the primary key necessary for RowGateway object creation.',
5487
);
55-
if (null === $metadata || ! isset($metadata->sharedData['metadata'])) {
56-
throw new Exception\RuntimeException(
57-
'No information was provided to the RowGatewayFeature and/or no MetadataFeature could be consulted '
58-
. 'to find the primary key necessary for RowGateway object creation.',
59-
);
60-
}
61-
$primaryKey = $metadata->sharedData['metadata']['primaryKey'];
62-
$rowGatewayPrototype = new RowGateway(
63-
$primaryKey,
64-
$this->tableGateway->table,
65-
$this->tableGateway->adapter,
88+
}
89+
90+
if (! is_array($metadataData)) {
91+
throw new Exception\RuntimeException('The MetadataFeature did not expose its metadata as an array.');
92+
}
93+
94+
$primaryKey = $metadataData['primaryKey'] ?? null;
95+
if (! is_string($primaryKey) && ! is_array($primaryKey)) {
96+
throw new Exception\RuntimeException(
97+
'The MetadataFeature did not expose a usable primary key for RowGateway object creation.',
6698
);
67-
$resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype);
6899
}
100+
101+
return $primaryKey;
69102
}
70103
}

0 commit comments

Comments
 (0)