Skip to content

Commit 2a8e237

Browse files
committed
Add PHPStan level 8, update CI workflow, add repo configs
Add phpstan.neon.dist at level 8 (matching Validation) with only 3 remaining ignores for irreducible __set magic method tests. Fix source: type Instantiator constructor with @param class-string, use assert(is_callable()) for call_user_func, assert(Closure|string) for ReflectionFunction, use ReflectionNamedType, null-safe key(), cast preg_replace results. Fix tests: replace magic __get reads with getItem()/get(), magic __call with __invoke + getItem, bare expressions with get(), parse_ini_string with parseIni() helper, getcwd() with assertion, class_alias constant with original class. Replace single-job CI with 3 parallel jobs: tests, code coverage with Codecov, static analysis. Add codecov.yml, .gitattributes, dependabot.yml.
1 parent 029f15f commit 2a8e237

File tree

12 files changed

+192
-97
lines changed

12 files changed

+192
-97
lines changed

.gitattributes

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* export-ignore
2+
3+
# Project files
4+
/README.md -export-ignore
5+
/composer.json -export-ignore
6+
/src -export-ignore

.github/dependabot.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: 2
2+
updates:
3+
4+
# Maintain dependencies for GitHub Actions
5+
- package-ecosystem: "github-actions"
6+
directory: "/"
7+
schedule:
8+
interval: "weekly"
9+
10+
# Maintain dependencies for Composer
11+
- package-ecosystem: "composer"
12+
directory: "/"
13+
schedule:
14+
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,42 @@ on:
66
pull_request:
77

88
jobs:
9-
test:
9+
tests:
10+
name: Tests
1011
runs-on: ubuntu-latest
11-
strategy:
12-
matrix:
13-
php: [ '8.5' ]
1412
steps:
15-
- name: Checkout repository
16-
uses: actions/checkout@v4
17-
18-
- name: Setup PHP
19-
uses: shivammathur/setup-php@v2
13+
- uses: actions/checkout@v6
14+
- uses: shivammathur/setup-php@v2
2015
with:
21-
php-version: ${{ matrix.php }}
16+
php-version: '8.5'
17+
- uses: ramsey/composer-install@v3
18+
- run: composer phpunit
2219

23-
- name: Get composer cache
24-
uses: actions/cache@v4
20+
code-coverage:
21+
name: Code Coverage
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v6
25+
- uses: shivammathur/setup-php@v2
2526
with:
26-
path: ~/.cache/composer
27-
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
28-
restore-keys: |
29-
${{ runner.os }}-composer-
30-
31-
- name: Install dependencies
32-
run: composer install --no-progress --no-suggest --prefer-dist --no-interaction
27+
php-version: '8.5'
28+
coverage: pcov
29+
- uses: ramsey/composer-install@v3
30+
- name: Generate coverage report
31+
run: vendor/bin/phpunit --coverage-clover=coverage.xml
32+
- name: Upload to Codecov
33+
uses: codecov/codecov-action@v5
34+
with:
35+
token: ${{ secrets.CODECOV_TOKEN }}
3336

34-
- name: Run PHPUnit
35-
run: vendor/bin/phpunit --configuration phpunit.xml.dist
37+
static-analysis:
38+
name: Static Analysis
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v6
42+
- uses: shivammathur/setup-php@v2
43+
with:
44+
php-version: '8.5'
45+
- uses: ramsey/composer-install@v3
46+
- run: composer phpcs
47+
- run: composer phpstan

codecov.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: 95% # the required coverage value
6+
threshold: 1% # the leniency in hitting the target

phpcs.xml.dist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
<exclude-pattern>src/Instantiator.php</exclude-pattern>
2323
</rule>
2424

25+
<!-- @var class-string narrowing for PHPStan on dynamic class names from INI -->
26+
<rule ref="SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable">
27+
<exclude-pattern>src/</exclude-pattern>
28+
</rule>
29+
2530
<!-- Test files contain inline fixture classes required by the DI container tests -->
2631
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
2732
<exclude-pattern>tests/</exclude-pattern>

phpstan.neon.dist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
parameters:
2+
level: 8
3+
paths:
4+
- src/
5+
- tests/
6+
ignoreErrors:
7+
# __set writes that test Container's Instantiator replacement logic
8+
-
9+
message: '/Access to an undefined property Respect\\Config\\Container::\$/'
10+
path: tests/ContainerTest.php
11+
count: 1
12+
-
13+
message: '/Access to an undefined property Respect\\Config\\Container::\$/'
14+
path: tests/LazyLoadTest.php
15+
count: 2

src/Container.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
use Psr\Container\ContainerInterface;
1111
use ReflectionClass;
1212
use ReflectionFunction;
13+
use ReflectionNamedType;
1314

1415
use function array_filter;
1516
use function array_map;
17+
use function assert;
1618
use function constant;
1719
use function count;
1820
use function current;
@@ -32,6 +34,7 @@
3234
use function str_contains;
3335
use function trim;
3436

37+
/** @extends ArrayObject<string, mixed> */
3538
class Container extends ArrayObject implements ContainerInterface
3639
{
3740
public function __construct(protected mixed $configurator = null)
@@ -191,7 +194,7 @@ protected function parseStandardItem(string $key, mixed &$value): void
191194

192195
protected function removeDuplicatedSpaces(string $string): string
193196
{
194-
return preg_replace('/\s+/', ' ', $string);
197+
return (string) preg_replace('/\s+/', ' ', $string);
195198
}
196199

197200
protected function parseInstantiator(string $key, mixed $value): void
@@ -202,6 +205,7 @@ protected function parseInstantiator(string $key, mixed $value): void
202205
$keyName = $keyClass;
203206
}
204207

208+
/** @var class-string $keyClass */
205209
$instantiator = new Instantiator($keyClass);
206210

207211
if (is_array($value)) {
@@ -297,7 +301,7 @@ protected function parseBrackets(string $value): mixed
297301

298302
protected function parseVariables(string $value): string
299303
{
300-
return preg_replace_callback(
304+
return (string) preg_replace_callback(
301305
'/\[(\w+)\]/',
302306
fn(array $match): string => $this[$match[1]] ?: '',
303307
$value,
@@ -337,17 +341,16 @@ public function __invoke(mixed $spec): mixed
337341
$mirror = $class->getMethod($method);
338342
} else {
339343
$object = false;
344+
assert($spec instanceof Closure || is_string($spec));
340345
$mirror = new ReflectionFunction($spec);
341346
}
342347

343348
$container = $this;
344349
$arguments = array_map(
345350
static function ($param) use ($container) {
346351
$paramClass = $param->getType();
347-
if ($paramClass) {
348-
$paramClassName = $paramClass->getName();
349-
350-
return $container->getItem($paramClassName);
352+
if ($paramClass instanceof ReflectionNamedType) {
353+
return $container->getItem($paramClass->getName());
351354
}
352355

353356
return null;

src/Instantiator.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
use ReflectionClass;
66

77
use function array_key_exists;
8+
use function assert;
89
use function call_user_func;
910
use function call_user_func_array;
1011
use function count;
1112
use function end;
1213
use function explode;
1314
use function func_get_args;
1415
use function is_array;
16+
use function is_callable;
1517
use function is_object;
1618
use function key;
1719
use function str_contains;
@@ -25,6 +27,7 @@ class Instantiator
2527

2628
protected mixed $instance = null;
2729

30+
/** @var ReflectionClass<object> */
2831
protected ReflectionClass $reflection;
2932

3033
/** @var array<string, mixed> */
@@ -44,11 +47,14 @@ class Instantiator
4447

4548
protected string|false $mode = self::MODE_DEPENDENCY;
4649

50+
/** @param class-string $className */
4751
public function __construct(protected string $className)
4852
{
4953
if (str_contains(strtolower($className), ' ')) {
5054
[$mode, $className] = explode(' ', $className, 2);
5155
$this->mode = $mode;
56+
/** @var class-string $className */
57+
$this->className = $className;
5258
}
5359

5460
$this->reflection = new ReflectionClass($className);
@@ -158,7 +164,12 @@ public function getParams(): array
158164
protected function cleanupParams(array $params): array
159165
{
160166
while (end($params) === null) {
161-
unset($params[key($params)]);
167+
$key = key($params);
168+
if ($key === null) {
169+
break;
170+
}
171+
172+
unset($params[$key]);
162173
}
163174

164175
foreach ($params as &$p) {
@@ -173,7 +184,11 @@ protected function lazyLoad(mixed $value): mixed
173184
return $value instanceof self ? $value->getInstance() : $value;
174185
}
175186

176-
/** @return array<string, mixed> */
187+
/**
188+
* @param ReflectionClass<object> $class
189+
*
190+
* @return array<string, mixed>
191+
*/
177192
protected function findConstructorParams(ReflectionClass $class): array
178193
{
179194
$params = [];
@@ -233,16 +248,19 @@ protected function performMethodCalls(
233248
$resultCallback ??= static function (): void {
234249
};
235250

251+
$callable = [$class, $methodName];
252+
assert(is_callable($callable));
253+
236254
foreach ($calls as $arguments) {
237255
if (is_array($arguments)) {
238256
$resultCallback(call_user_func_array(
239-
[$class, $methodName],
257+
$callable,
240258
$this->cleanupParams($arguments),
241259
));
242260
} elseif ($arguments !== null) {
243-
$resultCallback(call_user_func([$class, $methodName], $this->lazyLoad($arguments)));
261+
$resultCallback(call_user_func($callable, $this->lazyLoad($arguments)));
244262
} else {
245-
$resultCallback(call_user_func([$class, $methodName]));
263+
$resultCallback(call_user_func($callable));
246264
}
247265
}
248266
}

0 commit comments

Comments
 (0)