Skip to content

Commit d5f120b

Browse files
committed
Switch from Psalm to PHPStan and test on PHP 8.4
1 parent 9cc54f0 commit d5f120b

12 files changed

Lines changed: 59 additions & 52 deletions

File tree

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/.github/ export-ignore
22
/test/ export-ignore
33
/phpunit.xml export-ignore
4-
/psalm.xml export-ignore
4+
/phpstan.neon export-ignore

.github/workflows/php.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77

88
strategy:
99
matrix:
10-
php: [ '8.1', '8.2', '8.3' ]
10+
php: [ '8.1', '8.2', '8.3', '8.4' ]
1111

1212
services:
1313
mysql:
@@ -52,9 +52,9 @@ jobs:
5252
- name: Install Composer dependencies
5353
run: composer install --no-progress
5454

55-
- name: Run Psalm
56-
run: composer analyze -- --output-format=github
57-
if: ${{ matrix.php == '8.3' }}
55+
- name: Perform static analysis
56+
run: composer analyze -- --error-format=github
57+
if: ${{ matrix.php == '8.4' }}
5858

5959
- name: Run PHPUnit
6060
run: composer test-without-mssql

composer.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@
1919
"require": {
2020
"php": ">=8.1",
2121
"ext-pdo": "*",
22-
"devtheorem/peachy-sql": "^7.0",
22+
"devtheorem/peachy-sql": "^7.0.1",
2323
"psr/http-message": "^1.1 || ^2.0",
2424
"shrikeh/teapot": "^3.0"
2525
},
2626
"require-dev": {
2727
"friendsofphp/php-cs-fixer": "^3.64",
28-
"phpunit/phpunit": "^10.5",
29-
"psalm/plugin-phpunit": "^0.19",
30-
"vimeo/psalm": "^5.26"
28+
"phpstan/phpstan": "^2.1",
29+
"phpunit/phpunit": "^10.5"
3130
},
3231
"autoload": {
3332
"psr-4": {
@@ -43,7 +42,7 @@
4342
"sort-packages": true
4443
},
4544
"scripts": {
46-
"analyze": "psalm",
45+
"analyze": "phpstan analyze",
4746
"cs-fix": "php-cs-fixer fix -v",
4847
"test": "phpunit",
4948
"test-mssql": "phpunit --exclude-group mysql,pgsql",

phpstan.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
parameters:
2+
level: 10
3+
paths:
4+
- src
5+
- test

psalm.xml

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/Entities.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ abstract class Entities
1313
private string $idColumn;
1414
/** @var array<string, Prop> */
1515
private array $fullPropMap;
16+
/** @var array<string, mixed> */
1617
private array $map;
1718

1819
public function __construct(PeachySql $db)
@@ -34,7 +35,7 @@ public function __construct(PeachySql $db)
3435
}
3536

3637
$idParts = explode('.', $propMap[$this->idField]->col);
37-
$this->idColumn = array_pop($idParts);
38+
$this->idColumn = $idParts[array_key_last($idParts)];
3839
$this->fullPropMap = $propMap;
3940
$this->map = $this->getMap();
4041
}
@@ -152,6 +153,9 @@ public function deleteByIds(array $ids): int
152153
return $this->db->deleteFrom($this->getTableName(), [$this->idColumn => $ids]);
153154
}
154155

156+
/**
157+
* @param mixed[] $data
158+
*/
155159
public function updateById(int|string $id, array $data): int
156160
{
157161
$row = Helpers::allPropertiesToColumns($this->map, $this->processValues($data, [$id]));
@@ -163,6 +167,7 @@ public function updateById(int|string $id, array $data): int
163167
/**
164168
* Update one or more rows via a JSON Merge Patch (https://tools.ietf.org/html/rfc7396)
165169
* @param list<string|int> $ids
170+
* @param mixed[] $mergePatch
166171
*/
167172
public function patchByIds(array $ids, array $mergePatch): int
168173
{
@@ -212,11 +217,13 @@ public function addEntities(array $entities): array
212217
foreach ($existingIds as $offset => $id) {
213218
array_splice($ids, $offset, 0, [$id]);
214219
}
220+
/** @phpstan-ignore return.type */
215221
return $ids;
216222
}
217223

218224
/**
219225
* @param string[] $fields
226+
* @return mixed[]
220227
*/
221228
public function getEntityById(int|string $id, array $fields = []): array
222229
{
@@ -230,7 +237,9 @@ public function getEntityById(int|string $id, array $fields = []): array
230237
}
231238

232239
/**
240+
* @param list<int|string> $ids
233241
* @param string[] $fields
242+
* @param mixed[] $sort
234243
* @return list<array>
235244
*/
236245
public function getEntitiesByIds(array $ids, array $fields = [], array $sort = []): array
@@ -243,7 +252,9 @@ public function getEntitiesByIds(array $ids, array $fields = [], array $sort = [
243252
}
244253

245254
/**
255+
* @param mixed[] $filter
246256
* @param string[] $fields
257+
* @param mixed[] $sort
247258
* @return list<array>
248259
*/
249260
public function getEntities(array $filter = [], array $fields = [], array $sort = [], int $offset = 0, int $limit = 0): array
@@ -258,8 +269,8 @@ public function getEntities(array $filter = [], array $fields = [], array $sort
258269
$fieldProps = Helpers::getFieldPropMap($fields, $this->fullPropMap);
259270
$queryOptions = new QueryOptions($processedFilter, $filter, $sort, $fieldProps);
260271

261-
/** @psalm-suppress MixedArgumentTypeCoercion */
262272
$select = $this->db->select($this->getBaseSelect($queryOptions))
273+
/** @phpstan-ignore argument.type */
263274
->where(self::propertiesToColumns($selectMap, $processedFilter))
264275
->orderBy(self::propertiesToColumns($selectMap, $sort, complexValues: false));
265276

@@ -281,8 +292,8 @@ public function countEntities(array $filter = []): int
281292
$prop = new Prop('count', 'COUNT(*)', false, true, 'count');
282293
$queryOptions = new QueryOptions($processedFilter, $filter, [], [$prop]);
283294

284-
/** @psalm-suppress MixedArgumentTypeCoercion */
285295
$select = $this->db->select($this->getBaseSelect($queryOptions))
296+
/** @phpstan-ignore argument.type */
286297
->where(self::propertiesToColumns($selectMap, $processedFilter));
287298

288299
/** @var array{count: int} $row */
@@ -293,6 +304,8 @@ public function countEntities(array $filter = []): int
293304

294305
/**
295306
* Converts nested properties to an array of columns and values using a map.
307+
* @param array<string, mixed> $map
308+
* @param mixed[] $properties
296309
* @return array<string, mixed>
297310
*/
298311
public static function propertiesToColumns(

src/Helpers.php

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class Helpers
1111
{
1212
/**
13+
* @param array<string, mixed> $map
1314
* @return array<string, Prop>
1415
*/
1516
public static function selectMapToPropMap(array $map, string $context = ''): array
@@ -21,7 +22,7 @@ public static function selectMapToPropMap(array $map, string $context = ''): arr
2122
}
2223

2324
/**
24-
* @var string|array $val
25+
* @var string|array<string, mixed> $val
2526
*/
2627
foreach ($map as $key => $val) {
2728
$newKey = $context . $key;
@@ -38,6 +39,7 @@ public static function selectMapToPropMap(array $map, string $context = ''): arr
3839

3940
/**
4041
* @param Prop[] $map
42+
* @return array<string, mixed>
4143
*/
4244
public static function propMapToSelectMap(array $map): array
4345
{
@@ -53,21 +55,21 @@ public static function propMapToSelectMap(array $map): array
5355
/**
5456
* @param array<string, mixed> $arr
5557
* @param string[] $path
56-
* @psalm-suppress UnusedParam
5758
*/
5859
private static function setNestedValue(array &$arr, array $path, mixed $value): void
5960
{
6061
$_arr = &$arr;
6162

6263
foreach ($path as $key) {
64+
/** @phpstan-ignore offsetAccess.nonOffsetAccessible */
6365
$_arr = &$_arr[$key];
6466
}
6567

6668
$_arr = $value;
6769
}
6870

6971
/**
70-
* @param \Generator<int, array> $rows
72+
* @param \Generator<int, mixed[]> $rows
7173
* @param Prop[] $fieldProps
7274
* @return list<array>
7375
*/
@@ -87,10 +89,8 @@ public static function mapRows(\Generator $rows, array $fieldProps): array
8789

8890
foreach ($aliasMap as $colName => $prop) {
8991
if ($prop->getValue !== null) {
90-
/** @psalm-suppress MixedAssignment */
9192
$value = ($prop->getValue)($row);
9293
} else {
93-
/** @psalm-suppress MixedAssignment */
9494
$value = $row[$colName];
9595

9696
if ($prop->type !== null) {
@@ -192,7 +192,7 @@ public static function getFieldPropMap(array $fields, array $propMap): array
192192
if ($data->nullGroup) {
193193
// check if any selected field is a child
194194
$parents = $data->parents;
195-
$parent = array_pop($parents);
195+
$parent = $parents[array_key_last($parents)];
196196
$length = strlen($parent);
197197

198198
foreach ($fieldProps as $field => $_val) {
@@ -266,7 +266,10 @@ public static function propMapToAliasMap(array $map): array
266266
}
267267

268268
/**
269-
* Converts nested properties to an array of columns and values using a map. All properties in the map are required.
269+
* Converts nested properties to an array of columns and values using a map.
270+
* All properties in the map are required.
271+
* @param array<string, mixed> $map
272+
* @param mixed[] $properties
270273
* @return array<string, mixed>
271274
*/
272275
public static function allPropertiesToColumns(array $map, array $properties): array
@@ -277,6 +280,8 @@ public static function allPropertiesToColumns(array $map, array $properties): ar
277280
}
278281

279282
/**
283+
* @param mixed[] $map
284+
* @param mixed[] $properties
280285
* @param array<string, mixed> $columns
281286
* @return array<string, mixed>
282287
*/
@@ -305,7 +310,7 @@ public static function propsToColumns(
305310
throw new HttpException("{$errMsg} {$contextProp} property", StatusCode::BAD_REQUEST);
306311
}
307312

308-
/** @var array|mixed $newMap */
313+
/** @var array<string, mixed>|scalar|null|object $newMap */
309314
$newMap = $map[$property]; // might be value
310315

311316
if (is_array($newMap)) {
@@ -338,7 +343,6 @@ public static function propsToColumns(
338343
throw new HttpException($msg, StatusCode::BAD_REQUEST);
339344
}
340345

341-
/** @psalm-suppress MixedAssignment */
342346
$columns[$newMap] = $value;
343347
}
344348
}

src/QueryOptions.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
class QueryOptions
99
{
1010
/**
11+
* @param mixed[] $filter
12+
* @param mixed[] $originalFilter
13+
* @param mixed[] $sort
1114
* @param Prop[] $fieldProps
1215
*/
1316
public function __construct(

src/RouteHandler.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public function search(string $class, int $defaultLimit = 25, int $maxLimit = 10
3333
'limit' => $defaultLimit,
3434
];
3535

36+
/**
37+
* @var mixed[]|string $value
38+
*/
3639
foreach ($request->getQueryParams() as $param => $value) {
3740
if (!array_key_exists($param, $params)) {
3841
throw new HttpException("Unrecognized parameter '{$param}'", StatusCode::BAD_REQUEST);
@@ -94,7 +97,7 @@ public function search(string $class, int $defaultLimit = 25, int $maxLimit = 10
9497
$output = ['data' => $entities];
9598
}
9699

97-
$response->getBody()->write(json_encode($output));
100+
$response->getBody()->write(json_encode($output, JSON_THROW_ON_ERROR));
98101
return $response->withHeader('Content-Type', 'application/json');
99102
};
100103
}
@@ -123,7 +126,7 @@ public function count(string $class): callable
123126
$instance = $factory->createEntities($class);
124127
$count = $instance->countEntities($query);
125128

126-
$response->getBody()->write(json_encode(['count' => $count]));
129+
$response->getBody()->write(json_encode(['count' => $count], JSON_THROW_ON_ERROR));
127130
return $response->withHeader('Content-Type', 'application/json');
128131
};
129132
}
@@ -145,7 +148,8 @@ public function getById(string $class): callable
145148
/** @var array<string, string> $params */
146149
$params = $request->getQueryParams();
147150
$fields = isset($params['fields']) ? explode(',', $params['fields']) : [];
148-
$response->getBody()->write(json_encode(['data' => $instance->getEntityById($args['id'], $fields)]));
151+
$entity = $instance->getEntityById($args['id'], $fields);
152+
$response->getBody()->write(json_encode(['data' => $entity], JSON_THROW_ON_ERROR));
149153
return $response->withHeader('Content-Type', 'application/json');
150154
};
151155
}
@@ -175,7 +179,7 @@ public function insert(string $class): callable
175179
$body = ['id' => $ids[0] ?? $data[$instance->idField]];
176180
}
177181

178-
$response->getBody()->write(json_encode($body));
182+
$response->getBody()->write(json_encode($body, JSON_THROW_ON_ERROR));
179183
return $response->withHeader('Content-Type', 'application/json');
180184
};
181185
}
@@ -200,7 +204,7 @@ public function update(string $class): callable
200204
}
201205

202206
$affected = $instance->updateById($args['id'], $body);
203-
$response->getBody()->write(json_encode(['affected' => $affected]));
207+
$response->getBody()->write(json_encode(['affected' => $affected], JSON_THROW_ON_ERROR));
204208
return $response->withHeader('Content-Type', 'application/json');
205209
};
206210
}
@@ -225,7 +229,7 @@ public function patch(string $class): callable
225229
}
226230

227231
$affected = $instance->patchByIds(explode(',', $args['id']), $body);
228-
$response->getBody()->write(json_encode(['affected' => $affected]));
232+
$response->getBody()->write(json_encode(['affected' => $affected], JSON_THROW_ON_ERROR));
229233
return $response->withHeader('Content-Type', 'application/json');
230234
};
231235
}

test/EntitiesTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
class EntitiesTest extends TestCase
1010
{
11+
/** @var array<string, mixed> */
1112
private array $propertyMap = [
1213
'name' => 'UserName',
1314
'client' => [

0 commit comments

Comments
 (0)