Skip to content

Commit f553c28

Browse files
Merge pull request #5 from DaveLiddament/feature/add-injectable-version
ADD InjectableVersion Attribute
2 parents 08cbf7a + e8a7bb9 commit f553c28

12 files changed

+401
-1
lines changed

README.md

+72-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The intention, at least initially, is that these extra language features are enf
55

66
**Language feature added:**
77
- [Friend](#friend)
8+
- [InjectableVersion](#injectableVersion)
89
- [Package](#package)
910
- [Sealed](#sealed)
1011
- [TestTag](#testtag)
@@ -16,8 +17,9 @@ The intention, at least initially, is that these extra language features are enf
1617
- [PHPStan](#phpstan)
1718
- [Psalm](#psalm)
1819
- [New Language Features](#new-language-features)
19-
- [Package](#package)
2020
- [Friend](#friend)
21+
- [InjectableVersion](#injectableVersion)
22+
- [Package](#package)
2123
- [Sealed](#sealed)
2224
- [TestTag](#testtag)
2325
- [Further examples](#further-examples)
@@ -271,6 +273,75 @@ NOTES:
271273
- Assume all classes within a namespace is test code. See [namespace config option](https://github.com/DaveLiddament/phpstan-php-language-extensions#exclude-checks-based-on-test-namespace).
272274

273275

276+
277+
## InjectableVersion
278+
279+
The `#[InjectableVersion]` is used in conjunction with dependency injection.
280+
`#[InjectableVersion]` is applied to a class or interface.
281+
It denotes that it is this version and not any classes that implement/extend that should be used in the codebase.
282+
283+
E.g.
284+
285+
```php
286+
287+
#[InjectableVersion]
288+
class PersonRepository {...} // This is the version that should be type hinted in constructors.
289+
290+
class DoctrinePersonRepository extends PersonRepository {...}
291+
292+
class PersonCreator {
293+
public function __construct(
294+
private PersonRepository $personRepository, // OK - using the injectable version
295+
)
296+
}
297+
class PersonUpdater {
298+
public function __construct(
299+
private DoctrinePersonRepository $personRepository, // ERROR - not using the InjectableVersion
300+
)
301+
}
302+
```
303+
304+
This also works for collections:
305+
306+
```php
307+
308+
#[InjectableVersion]
309+
interface Validator {...} // This is the version that should be type hinted in constructors.
310+
311+
class NameValidator implements Validator {...}
312+
class AddressValidator implements Validator {...}
313+
314+
class PersonValidator {
315+
/** @param Validator[] $validators */
316+
public function __construct(
317+
private array $validators, // OK - using the injectable version
318+
)
319+
}
320+
```
321+
322+
By default, only constructor arguments are checked. Most DI should be done via constructor injection.
323+
324+
In cases where dependencies are injected by methods that aren't constructors, the method must be marked with a `#[CheckInjectableVersion]`:
325+
326+
```php
327+
328+
#[InjectableVersion]
329+
interface Logger {...}
330+
331+
class FileLogger implements Logger {...}
332+
333+
class MyService
334+
{
335+
#[CheckInjectableVersion]
336+
public function setLogger(Logger $logger): void {} // OK - Injectable Version injected
337+
338+
public function addLogger(FileLogger $logger): void {} // No issue raised because addLogger doesn't have the #[CheckInjectableVersion] attribute.
339+
}
340+
341+
```
342+
343+
344+
274345
## Further examples
275346

276347
More detailed examples of how to use attributes is found in [examples](examples/).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace InjectableVersionCheckOnMethod;
6+
7+
use DaveLiddament\PhpLanguageExtensions\CheckInjectableVersion;
8+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
9+
10+
#[InjectableVersion]
11+
abstract class Repository
12+
{
13+
}
14+
15+
class DoctrineRepository extends Repository
16+
{
17+
}
18+
19+
class InjectingCorrectVersion
20+
{
21+
public Repository $repository;
22+
23+
#[CheckInjectableVersion]
24+
public function setRepository(Repository $repository): void // OK
25+
{
26+
$this->repository = $repository;
27+
}
28+
}
29+
30+
class InjectingWrongVersion
31+
{
32+
public Repository $repository;
33+
34+
#[CheckInjectableVersion]
35+
public function setRepository(DoctrineRepository $repository): void // ERROR
36+
{
37+
$this->repository = $repository;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace InjectableVersionOnClass;
6+
7+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
8+
9+
#[InjectableVersion]
10+
abstract class Repository
11+
{
12+
}
13+
14+
class DoctrineRepository extends Repository
15+
{
16+
}
17+
18+
19+
class InjectingCorrectVersion
20+
{
21+
public function __construct(
22+
public Repository $repository,
23+
public int $int
24+
) {} // OK
25+
}
26+
27+
class InjectingWrongVersion
28+
{
29+
/** @param mixed $unknownType */
30+
public function __construct(
31+
public string $string,
32+
public $unknownType,
33+
public DoctrineRepository $repository
34+
) {} // ERROR
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace InjectableVersionOnExtendsThenImplements;
6+
7+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
8+
9+
#[InjectableVersion]
10+
interface Repository
11+
{
12+
}
13+
14+
class AbstractDoctrineRepository implements Repository
15+
{
16+
}
17+
18+
19+
class DoctrineRepository extends AbstractDoctrineRepository
20+
{
21+
}
22+
23+
class InjectingWrongVersion1
24+
{
25+
public function __construct(public AbstractDoctrineRepository $repository) {} // ERROR
26+
}
27+
28+
class InjectingWrongVersion2
29+
{
30+
public function __construct(public DoctrineRepository $repository) {} // ERROR
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace InjectableVersionOnInterface;
6+
7+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
8+
9+
#[InjectableVersion]
10+
interface Repository
11+
{
12+
}
13+
14+
interface DoctrineRepository extends Repository
15+
{
16+
}
17+
18+
19+
class InjectingCorrectVersion
20+
{
21+
public function __construct(public Repository $repository) {}
22+
}
23+
24+
class InjectingWrongVersion
25+
{
26+
public function __construct(public DoctrineRepository $repository) {}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace InjectableVersionRulesIgnoredForTestNamespace {
6+
7+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
8+
9+
#[InjectableVersion]
10+
interface Repository
11+
{
12+
}
13+
}
14+
15+
16+
namespace InjectableVersionRulesIgnoredForTestNamespace\Test {
17+
18+
use InjectableVersionRulesIgnoredForTestNamespace\Repository;
19+
20+
class RepositoryDouble implements Repository
21+
{
22+
}
23+
24+
class ServiceDouble
25+
{
26+
public function __construct(public RepositoryDouble $repository) // OK as in test namespace and this excluded from checks
27+
{
28+
}
29+
}
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace IterableInjectableVersion;
6+
7+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
8+
9+
#[InjectableVersion]
10+
abstract class Repository
11+
{
12+
}
13+
14+
class DoctrineRepository extends Repository
15+
{
16+
}
17+
18+
19+
class InjectingCorrectVersion
20+
{
21+
/** @param Repository[] $repositories */
22+
public function __construct(public array $repositories) {} // OK
23+
}
24+
25+
class InjectingWrongVersion
26+
{
27+
/** @param DoctrineRepository[] $repositories */
28+
public function __construct(public iterable $repositories) {} // ERROR
29+
}
30+
31+
class InjectingWrongVersion2
32+
{
33+
/** @param DoctrineRepository[] $repositories */
34+
public function __construct($repositories) {var_export($repositories);} // ERROR
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MultipleLevelsOfInheritanceOnInjectableVersionOnInterface;
6+
7+
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;
8+
9+
#[InjectableVersion]
10+
interface CorrectVersion
11+
{
12+
}
13+
14+
interface FirstLevelOfInheritance extends CorrectVersion
15+
{
16+
}
17+
18+
19+
class SecondLevelOfInheritance implements FirstLevelOfInheritance
20+
{
21+
}
22+
23+
24+
class InjectingCorrectVersion
25+
{
26+
public function __construct(public CorrectVersion $repository) {} // OK
27+
}
28+
29+
class InjectingWrongVersion1
30+
{
31+
public function __construct(public FirstLevelOfInheritance $repository) {} // ERROR
32+
}
33+
34+
class InjectingWrongVersion2
35+
{
36+
public function __construct(public SecondLevelOfInheritance $repository) {} // ERROR
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MultipleLevelsOfInheritanceNoInjectableVersionOnClass;
6+
7+
8+
class CorrectVersion
9+
{
10+
}
11+
12+
class FirstLevelOfInheritance extends CorrectVersion
13+
{
14+
}
15+
16+
17+
class SecondLevelOfInheritance extends FirstLevelOfInheritance
18+
{
19+
}
20+
21+
22+
class InjectingCorrectVersion
23+
{
24+
public function __construct(public CorrectVersion $repository) {} // OK
25+
}
26+
27+
class InjectingWrongVersion1
28+
{
29+
public function __construct(public FirstLevelOfInheritance $repository) {} // OK
30+
}
31+
32+
class InjectingWrongVersion2
33+
{
34+
public function __construct(public SecondLevelOfInheritance $repository) {} // OK
35+
}

0 commit comments

Comments
 (0)