Skip to content

Commit 0967304

Browse files
committed
phpstan level 9
1 parent 218ac0d commit 0967304

6 files changed

Lines changed: 107 additions & 24 deletions

File tree

data-access-kit-symfony/src/DataAccessKitBundle.php

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,28 +112,38 @@ private function persistenceId(string $name): string
112112
*/
113113
private function configureGlobalServices(array $config, ServicesConfigurator $services, ContainerBuilder $builder): void
114114
{
115-
$services->set($config["name_converter"])->autowire();
116-
$services->alias(NameConverterInterface::class, $config["name_converter"]);
115+
/** @var string $nameConverter */
116+
$nameConverter = $config["name_converter"];
117+
$services->set($nameConverter)->autowire();
118+
$services->alias(NameConverterInterface::class, $nameConverter);
117119

118120
$services->set(Registry::class)->autowire();
119121

120-
if (!$builder->hasDefinition($config["value_converter"])) {
121-
$services->set($config["value_converter"])->autowire();
122+
/** @var string $valueConverter */
123+
$valueConverter = $config["value_converter"];
124+
if (!$builder->hasDefinition($valueConverter)) {
125+
$services->set($valueConverter)->autowire();
122126
}
123-
$services->alias(ValueConverterInterface::class, $config["value_converter"]);
127+
$services->alias(ValueConverterInterface::class, $valueConverter);
124128
}
125129

126130
/**
127131
* @param array<string, mixed> $config
128132
*/
129133
private function configureDatabaseServices(array $config, ServicesConfigurator $services): void
130134
{
131-
foreach ($config["databases"] as $name => $database) {
135+
/** @var array<string, array<string, mixed>> $databases */
136+
$databases = $config["databases"];
137+
foreach ($databases as $name => $database) {
138+
/** @var string $connection */
139+
$connection = $database["connection"];
132140
$services->set($this->persistenceId($name), Persistence::class)
133-
->arg("\$connection", new Reference($database["connection"]))
141+
->arg("\$connection", new Reference($connection))
134142
->autowire();
135143

136-
if ($name === $config["default_database"]) {
144+
/** @var string $defaultDatabase */
145+
$defaultDatabase = $config["default_database"];
146+
if ($name === $defaultDatabase) {
137147
$services->alias(PersistenceInterface::class, $this->persistenceId($name));
138148
}
139149
}
@@ -149,17 +159,28 @@ private function configureRepositoryServices(array $config, ServicesConfigurator
149159
$nameConverter = new $nameConverterClass();
150160
$registry = new Registry($nameConverter);
151161
$compiler = new Compiler($registry);
152-
$outputDir = $builder->getParameterBag()->resolveValue("%kernel.cache_dir%") . DIRECTORY_SEPARATOR . "DataAccessKit";
162+
163+
/** @var string $outputDirValue */
164+
$outputDirValue = $builder->getParameterBag()->resolveValue("%kernel.cache_dir%");
165+
$outputDir = $outputDirValue . DIRECTORY_SEPARATOR . "DataAccessKit";
166+
167+
/** @var bool $debug */
153168
$debug = $builder->getParameterBag()->resolveValue("%kernel.debug%");
154169

155-
foreach ($config["repositories"] as $namespace => $repository) {
170+
/** @var array<string, array<string, mixed>> $repositories */
171+
$repositories = $config["repositories"];
172+
foreach ($repositories as $namespace => $repository) {
173+
/** @var string $path */
174+
$path = $repository["path"];
156175
$finder = (new Finder())
157-
->in($repository["path"])
176+
->in($path)
158177
->files()
159178
->name("*.php");
160179

161-
if (count($repository["exclude"]) > 0) {
162-
$finder->notName($repository["exclude"]);
180+
/** @var array<string> $excludePatterns */
181+
$excludePatterns = $repository["exclude"];
182+
if (count($excludePatterns) > 0) {
183+
$finder->notName($excludePatterns);
163184
}
164185

165186
foreach ($finder as $file) {
@@ -177,7 +198,10 @@ private function configureRepositoryServices(array $config, ServicesConfigurator
177198
$cfg->arg('$' . Compiler::PERSISTENCE_PROPERTY, new Reference($this->persistenceId($result->repository->database)));
178199
}
179200
$aliasCfg = $services->alias($result->reflection->getName(), $result->getName());
180-
if ($config["repositories_public"]) {
201+
202+
/** @var bool $repositoriesPublic */
203+
$repositoriesPublic = $config["repositories_public"];
204+
if ($repositoriesPublic) {
181205
$aliasCfg->public();
182206
}
183207
}

data-access-kit/src/Converter/DefaultValueConverter.php

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ public function objectToDatabase(Table $table, Column $column, mixed $value, boo
6262
} else {
6363
$nestedTable = $this->registry->get($column->itemType);
6464
$jsonArray = [];
65+
if (!is_iterable($value)) {
66+
throw new ConverterException("Expected iterable value for array type");
67+
}
6568
foreach ($value as $item) {
69+
if (!is_object($item)) {
70+
throw new ConverterException("Expected object in array value");
71+
}
6672
$jsonArray[] = $jsonObject = new stdClass();
6773
foreach ($nestedTable->columns as $nestedColumn) {
6874
$jsonObject->{$nestedColumn->name} = $this->objectToDatabase(
@@ -87,6 +93,9 @@ public function objectToDatabase(Table $table, Column $column, mixed $value, boo
8793
$returnValue = $value->name;
8894
}
8995
} else if (null !== ($nestedTable = $this->registry->maybeGet($valueType->getName()))) {
96+
if (!is_object($value)) {
97+
throw new ConverterException("Expected object value for nested type");
98+
}
9099
$jsonObject = new stdClass();
91100
foreach ($nestedTable->columns as $nestedColumn) {
92101
$jsonObject->{$nestedColumn->name} = $this->objectToDatabase(
@@ -133,14 +142,30 @@ public function databaseToObject(Table $table, Column $column, mixed $value, boo
133142
if (in_array($valueType->getName(), ["int", "float", "string", "bool"], true)) {
134143
$returnValue = $value;
135144
} else if ($valueType->getName() === "object") {
145+
if (!is_string($value)) {
146+
throw new ConverterException("Expected string value for JSON decoding");
147+
}
148+
/** @var string $value */
136149
$returnValue = $decode ? json_decode($value) : $value;
137150
} else if ($valueType->getName() === "array") {
138151
if ($column->itemType === null) {
152+
if (!is_string($value)) {
153+
throw new ConverterException("Expected string value for JSON decoding");
154+
}
155+
/** @var string $value */
139156
$returnValue = $decode ? json_decode($value) : $value;
140157
} else {
141158
$nestedTable = $this->registry->get($column->itemType);
142159
$array = [];
143-
foreach ($decode ? json_decode($value) : $value as $jsonObject) {
160+
if ($decode && !is_string($value)) {
161+
throw new ConverterException("Expected string value for JSON decoding");
162+
}
163+
/** @var string $value */
164+
$decodedValue = $decode ? json_decode($value) : $value;
165+
if (!is_iterable($decodedValue)) {
166+
throw new ConverterException("Expected iterable value for array type");
167+
}
168+
foreach ($decodedValue as $jsonObject) {
144169
$nestedObject = $nestedTable->reflection->newInstanceWithoutConstructor();
145170
foreach ($nestedTable->columns as $nestedColumn) {
146171
$nestedColumn->reflection->setValue(
@@ -160,14 +185,17 @@ public function databaseToObject(Table $table, Column $column, mixed $value, boo
160185
} else if (in_array($valueType->getName(), [DateTime::class, DateTimeImmutable::class], true)) {
161186
/** @var class-string<DateTime|DateTimeImmutable> $className */
162187
$className = $valueType->getName();
188+
if (!is_string($value)) {
189+
throw new ConverterException("Expected string value for DateTime conversion");
190+
}
163191
$returnValue = $className::createFromFormat($this->dateTimeFormat, $value, $this->dateTimeZone);
164192
if ($returnValue === false) {
165193
try {
166194
$returnValue = new $className($value, $this->dateTimeZone);
167195
} catch (\Exception $e) {
168196
throw new ConverterException(sprintf(
169197
"Could not parse datetime value [%s] for property [%s::\$%s].",
170-
$value,
198+
(string) $value,
171199
$table->reflection->getName(),
172200
$column->reflection->getName()
173201
));
@@ -181,6 +209,9 @@ public function databaseToObject(Table $table, Column $column, mixed $value, boo
181209
if ($enumReflection->isBacked()) {
182210
// Backed enum - use from() method
183211
/** @var class-string<BackedEnum> $className */
212+
if (!is_int($value) && !is_string($value)) {
213+
throw new ConverterException("Expected int or string value for backed enum");
214+
}
184215
$returnValue = $className::from($value);
185216
} else {
186217
// Unit enum - find case by name
@@ -193,16 +224,21 @@ public function databaseToObject(Table $table, Column $column, mixed $value, boo
193224
}
194225
}
195226
if ($returnValue === null) {
227+
$valueStr = is_scalar($value) ? (string) $value : get_debug_type($value);
196228
throw new ConverterException(sprintf(
197229
"Could not find enum case [%s] for enum [%s] in property [%s::\$%s].",
198-
$value,
230+
$valueStr,
199231
$className,
200232
$table->reflection->getName(),
201233
$column->reflection->getName()
202234
));
203235
}
204236
}
205237
} else if (null !== ($nestedTable = $this->registry->maybeGet($valueType->getName()))) {
238+
if ($decode && !is_string($value)) {
239+
throw new ConverterException("Expected string value for JSON decoding");
240+
}
241+
/** @var string $value */
206242
$jsonObject = $decode ? json_decode($value) : $value;
207243
$nestedObject = $nestedTable->reflection->newInstanceWithoutConstructor();
208244
foreach ($nestedTable->columns as $nestedColumn) {

data-access-kit/src/Persistence.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,18 @@ public function select(string $className, string $sql, array $parameters = []):
6363
}
6464
}
6565

66-
public function selectScalar(string $sql, array $parameters = []): mixed
66+
public function selectScalar(string $sql, array $parameters = []): bool|float|int|string|null
6767
{
6868
/** @var array<int<0, max>|string, mixed> $dbParams */
6969
$dbParams = $parameters;
70-
return $this->connection->executeQuery($sql, $dbParams)->fetchOne();
70+
$result = $this->connection->executeQuery($sql, $dbParams)->fetchOne();
71+
if (!is_bool($result) && !is_float($result) && !is_int($result) && !is_string($result) && $result !== null) {
72+
throw new PersistenceException(sprintf(
73+
"Expected scalar value, got %s.",
74+
get_debug_type($result)
75+
));
76+
}
77+
return $result;
7178
}
7279

7380
public function execute(string $sql, array $parameters = []): int
@@ -89,7 +96,9 @@ public function insert(object|array $data): void
8996
if (!is_array($data)) {
9097
$data = [$data];
9198
}
92-
$this->doInsert($data, []);
99+
/** @var array<object> $objectData */
100+
$objectData = $data;
101+
$this->doInsert($objectData, []);
93102
}
94103

95104
public function upsert(object|array $data, ?array $columns = null): void
@@ -101,7 +110,9 @@ public function upsert(object|array $data, ?array $columns = null): void
101110
if (!is_array($data)) {
102111
$data = [$data];
103112
}
104-
$this->doInsert($data, $columns);
113+
/** @var array<object> $objectData */
114+
$objectData = $data;
115+
$this->doInsert($objectData, $columns);
105116
}
106117

107118
/**
@@ -390,7 +401,11 @@ public function delete(object|array $data): void
390401
return;
391402
}
392403

393-
$table = $this->registry->get($data[0], true);
404+
$firstObject = $data[0];
405+
if (!is_object($firstObject)) {
406+
throw new PersistenceException("Expected object in data array");
407+
}
408+
$table = $this->registry->get($firstObject, true);
394409
$platform = $this->connection->getDatabasePlatform();
395410

396411
$where = [];
@@ -405,6 +420,9 @@ public function delete(object|array $data): void
405420
}
406421

407422
foreach ($data as $object) {
423+
if (!is_object($object)) {
424+
throw new PersistenceException("Expected object in data array");
425+
}
408426
$rowWhere = [];
409427
foreach ($primaryColumns as $column) {
410428
if (!$column->reflection->isInitialized($object)) {

data-access-kit/src/Registry.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public function get(object|string $objectOrClass, bool $requireTable = false): T
136136
public function maybeGet(object|string $objectOrClass, bool $requireTable = false): ?Table
137137
{
138138
try {
139-
return $this->get(...func_get_args());
139+
return $this->get($objectOrClass, $requireTable);
140140
} catch (LogicException) {
141141
return null;
142142
}

data-access-kit/src/Repository/Compiler.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ public function compile(Result $result): Result
207207
}
208208
$method->setReturnType(static::phpType($result, $rm->getReturnType()));
209209

210+
if ($methodCompilerAttribute === null) {
211+
throw new CompilerException("Method compiler attribute should not be null at this point");
212+
}
210213
$methodCompiler->compile($result, $method, $methodCompilerAttribute);
211214
}
212215

phpstan.neon

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
parameters:
2-
level: 8
2+
level: 9
33
paths:
44
- data-access-kit/src
55
- data-access-kit-symfony/src
66

77
# Handle the Symfony config bundle issue
88
ignoreErrors:
99
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\)#'
10+
# Ignore template covariance issues that don't affect runtime
11+
- '#DataAccessKit\\Repository\\Compiler::registerMethodCompiler\(\) expects DataAccessKit\\Repository\\MethodCompilerInterface<mixed>#'
1012

1113
# Be more lenient with some type issues during migration
1214
treatPhpDocTypesAsCertain: false

0 commit comments

Comments
 (0)