Skip to content

Commit

Permalink
Merge pull request #5 from DaveLiddament/feature/add-injectable-version
Browse files Browse the repository at this point in the history
ADD InjectableVersion Attribute
  • Loading branch information
DaveLiddament committed May 30, 2022
2 parents 08cbf7a + e8a7bb9 commit f553c28
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 1 deletion.
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The intention, at least initially, is that these extra language features are enf

**Language feature added:**
- [Friend](#friend)
- [InjectableVersion](#injectableVersion)
- [Package](#package)
- [Sealed](#sealed)
- [TestTag](#testtag)
Expand All @@ -16,8 +17,9 @@ The intention, at least initially, is that these extra language features are enf
- [PHPStan](#phpstan)
- [Psalm](#psalm)
- [New Language Features](#new-language-features)
- [Package](#package)
- [Friend](#friend)
- [InjectableVersion](#injectableVersion)
- [Package](#package)
- [Sealed](#sealed)
- [TestTag](#testtag)
- [Further examples](#further-examples)
Expand Down Expand Up @@ -271,6 +273,75 @@ NOTES:
- 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).



## InjectableVersion

The `#[InjectableVersion]` is used in conjunction with dependency injection.
`#[InjectableVersion]` is applied to a class or interface.
It denotes that it is this version and not any classes that implement/extend that should be used in the codebase.

E.g.

```php

#[InjectableVersion]
class PersonRepository {...} // This is the version that should be type hinted in constructors.

class DoctrinePersonRepository extends PersonRepository {...}

class PersonCreator {
public function __construct(
private PersonRepository $personRepository, // OK - using the injectable version
)
}
class PersonUpdater {
public function __construct(
private DoctrinePersonRepository $personRepository, // ERROR - not using the InjectableVersion
)
}
```

This also works for collections:

```php

#[InjectableVersion]
interface Validator {...} // This is the version that should be type hinted in constructors.

class NameValidator implements Validator {...}
class AddressValidator implements Validator {...}

class PersonValidator {
/** @param Validator[] $validators */
public function __construct(
private array $validators, // OK - using the injectable version
)
}
```

By default, only constructor arguments are checked. Most DI should be done via constructor injection.

In cases where dependencies are injected by methods that aren't constructors, the method must be marked with a `#[CheckInjectableVersion]`:

```php

#[InjectableVersion]
interface Logger {...}

class FileLogger implements Logger {...}

class MyService
{
#[CheckInjectableVersion]
public function setLogger(Logger $logger): void {} // OK - Injectable Version injected

public function addLogger(FileLogger $logger): void {} // No issue raised because addLogger doesn't have the #[CheckInjectableVersion] attribute.
}

```



## Further examples

More detailed examples of how to use attributes is found in [examples](examples/).
Expand Down
39 changes: 39 additions & 0 deletions examples/injectableVersion/InjectableVersionCheckOnMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace InjectableVersionCheckOnMethod;

use DaveLiddament\PhpLanguageExtensions\CheckInjectableVersion;
use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
abstract class Repository
{
}

class DoctrineRepository extends Repository
{
}

class InjectingCorrectVersion
{
public Repository $repository;

#[CheckInjectableVersion]
public function setRepository(Repository $repository): void // OK
{
$this->repository = $repository;
}
}

class InjectingWrongVersion
{
public Repository $repository;

#[CheckInjectableVersion]
public function setRepository(DoctrineRepository $repository): void // ERROR
{
$this->repository = $repository;
}
}
35 changes: 35 additions & 0 deletions examples/injectableVersion/InjectableVersionOnClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace InjectableVersionOnClass;

use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
abstract class Repository
{
}

class DoctrineRepository extends Repository
{
}


class InjectingCorrectVersion
{
public function __construct(
public Repository $repository,
public int $int
) {} // OK
}

class InjectingWrongVersion
{
/** @param mixed $unknownType */
public function __construct(
public string $string,
public $unknownType,
public DoctrineRepository $repository
) {} // ERROR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace InjectableVersionOnExtendsThenImplements;

use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
interface Repository
{
}

class AbstractDoctrineRepository implements Repository
{
}


class DoctrineRepository extends AbstractDoctrineRepository
{
}

class InjectingWrongVersion1
{
public function __construct(public AbstractDoctrineRepository $repository) {} // ERROR
}

class InjectingWrongVersion2
{
public function __construct(public DoctrineRepository $repository) {} // ERROR
}
27 changes: 27 additions & 0 deletions examples/injectableVersion/InjectableVersionOnInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace InjectableVersionOnInterface;

use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
interface Repository
{
}

interface DoctrineRepository extends Repository
{
}


class InjectingCorrectVersion
{
public function __construct(public Repository $repository) {}
}

class InjectingWrongVersion
{
public function __construct(public DoctrineRepository $repository) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace InjectableVersionRulesIgnoredForTestNamespace {

use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
interface Repository
{
}
}


namespace InjectableVersionRulesIgnoredForTestNamespace\Test {

use InjectableVersionRulesIgnoredForTestNamespace\Repository;

class RepositoryDouble implements Repository
{
}

class ServiceDouble
{
public function __construct(public RepositoryDouble $repository) // OK as in test namespace and this excluded from checks
{
}
}

}
35 changes: 35 additions & 0 deletions examples/injectableVersion/IterableInjectableVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace IterableInjectableVersion;

use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
abstract class Repository
{
}

class DoctrineRepository extends Repository
{
}


class InjectingCorrectVersion
{
/** @param Repository[] $repositories */
public function __construct(public array $repositories) {} // OK
}

class InjectingWrongVersion
{
/** @param DoctrineRepository[] $repositories */
public function __construct(public iterable $repositories) {} // ERROR
}

class InjectingWrongVersion2
{
/** @param DoctrineRepository[] $repositories */
public function __construct($repositories) {var_export($repositories);} // ERROR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace MultipleLevelsOfInheritanceOnInjectableVersionOnInterface;

use DaveLiddament\PhpLanguageExtensions\InjectableVersion;

#[InjectableVersion]
interface CorrectVersion
{
}

interface FirstLevelOfInheritance extends CorrectVersion
{
}


class SecondLevelOfInheritance implements FirstLevelOfInheritance
{
}


class InjectingCorrectVersion
{
public function __construct(public CorrectVersion $repository) {} // OK
}

class InjectingWrongVersion1
{
public function __construct(public FirstLevelOfInheritance $repository) {} // ERROR
}

class InjectingWrongVersion2
{
public function __construct(public SecondLevelOfInheritance $repository) {} // ERROR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace MultipleLevelsOfInheritanceNoInjectableVersionOnClass;


class CorrectVersion
{
}

class FirstLevelOfInheritance extends CorrectVersion
{
}


class SecondLevelOfInheritance extends FirstLevelOfInheritance
{
}


class InjectingCorrectVersion
{
public function __construct(public CorrectVersion $repository) {} // OK
}

class InjectingWrongVersion1
{
public function __construct(public FirstLevelOfInheritance $repository) {} // OK
}

class InjectingWrongVersion2
{
public function __construct(public SecondLevelOfInheritance $repository) {} // OK
}
Loading

0 comments on commit f553c28

Please sign in to comment.