Skip to content

Commit 58749df

Browse files
Add TemplateCovariant and TemplateContravariant attributes
1 parent d480b33 commit 58749df

8 files changed

+227
-11
lines changed

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,16 @@ And then install any needed extensions/plugins for the tools that you use.
9292

9393
These are the available attributes and their corresponding PHPDoc annotations:
9494

95-
| Attribute | PHPDoc Annotations |
96-
|---------------------------------------|--------------------|
97-
| [IsReadOnly](doc/IsReadOnly.md) | `@readonly` |
98-
| [Param](doc/Param.md) | `@param` |
99-
| [Property](doc/Property.md) | `@property` `@var` |
100-
| [PropertyRead](doc/PropertyRead.md) | `@property-read` |
101-
| [PropertyWrite](doc/PropertyWrite.md) | `@property-write` |
102-
| [Returns](doc/Returns.md) | `@return` |
103-
| [Template](doc/Template.md) | `@template` |
104-
| [Type](doc/Type.md) | `@var` `@return` |
95+
| Attribute | PHPDoc Annotations |
96+
|-------------------------------------------------------|---------------------------|
97+
| [IsReadOnly](doc/IsReadOnly.md) | `@readonly` |
98+
| [Param](doc/Param.md) | `@param` |
99+
| [Property](doc/Property.md) | `@property` `@var` |
100+
| [PropertyRead](doc/PropertyRead.md) | `@property-read` |
101+
| [PropertyWrite](doc/PropertyWrite.md) | `@property-write` |
102+
| [Returns](doc/Returns.md) | `@return` |
103+
| [Template](doc/Template.md) | `@template` |
104+
| [TemplateContravariant](doc/TemplateContravariant.md) | `@template-contravariant` |
105+
| [TemplateCovariant](doc/TemplateCovariant.md) | `@template-covariant` |
106+
| [Type](doc/Type.md) | `@var` `@return` |
105107

doc/TemplateContravariant.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `TemplateContravariant` Attribute
2+
3+
This attribute is the equivalent of the `@template-contravariant` annotation. It can be applied to a class, trait or interface.
4+
5+
## Arguments
6+
7+
The attribute accepts one string that defines the name of the type variable and an optional string that defines its type. The attribute itself does not have a knowledge of which type variables are valid and which are not and this will depend on the implementation for each particular tool.
8+
9+
We aim to accept all the type variables accepted by static analysis tools for the `@template-contravariant` annotation.
10+
11+
If the class has more than one type variable, you can add a list of `TemplateContravariant` attributes.
12+
13+
## Example usage
14+
15+
```php
16+
<?php
17+
18+
use Exception;
19+
use PhpStaticAnalysis\Attributes\TemplateContravariant;
20+
21+
#[TemplateContravariant('T')]
22+
#[TemplateContravariant('T', Exception::class)]
23+
class TemplateContravariantExample
24+
{
25+
}
26+
```

doc/TemplateCovariant.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `TemplateCovariant` Attribute
2+
3+
This attribute is the equivalent of the `@template-covariant` annotation. It can be applied to a class, trait or interface.
4+
5+
## Arguments
6+
7+
The attribute accepts one string that defines the name of the type variable and an optional string that defines its type. The attribute itself does not have a knowledge of which type variables are valid and which are not and this will depend on the implementation for each particular tool.
8+
9+
We aim to accept all the type variables accepted by static analysis tools for the `@template-covariant` annotation.
10+
11+
If the class has more than one type variable, you can add a list of `TemplateCovariant` attributes.
12+
13+
## Example usage
14+
15+
```php
16+
<?php
17+
18+
use Exception;
19+
use PhpStaticAnalysis\Attributes\TemplateCovariant;
20+
21+
#[TemplateCovariant('T')]
22+
#[TemplateCovariant('T', Exception::class)]
23+
class TemplateCovariantExample
24+
{
25+
}
26+
```

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ parameters:
1313
- '#^Parameter \#1 \.*\$[a-zA-Z]+ of attribute class PhpStaticAnalysis\\Attributes\\[a-zA-Z]+ constructor expects string, int given.$#'
1414
- '#^PHPDoc tag @[a-zA-Z]+ has invalid value \(\): Unexpected token "\\n ", expected type at offset [0-9]+$#'
1515
- '#^Attribute class PhpStaticAnalysis\\Attributes\\[a-zA-Z]+ constructor invoked with [0-9]+ parameter(s)?, [0-9]+ required.$#'
16-
- '#^Attribute class PhpStaticAnalysis\\Attributes\\[a-zA-Z]+ is not repeatable but is already present above the (property|method).$#'
16+
- '#^Attribute class PhpStaticAnalysis\\Attributes\\[a-zA-Z]+ is not repeatable but is already present above the (property|method).$#'

src/TemplateContravariant.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStaticAnalysis\Attributes;
6+
7+
use Attribute;
8+
9+
#[Attribute(
10+
Attribute::TARGET_CLASS |
11+
Attribute::TARGET_METHOD |
12+
Attribute::TARGET_FUNCTION |
13+
Attribute::IS_REPEATABLE
14+
)]
15+
final class TemplateContravariant
16+
{
17+
public function __construct(
18+
string $name,
19+
?string $of = null
20+
) {
21+
}
22+
}

src/TemplateCovariant.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStaticAnalysis\Attributes;
6+
7+
use Attribute;
8+
9+
#[Attribute(
10+
Attribute::TARGET_CLASS |
11+
Attribute::TARGET_METHOD |
12+
Attribute::TARGET_FUNCTION |
13+
Attribute::IS_REPEATABLE
14+
)]
15+
final class TemplateCovariant
16+
{
17+
public function __construct(
18+
string $name,
19+
?string $of = null
20+
) {
21+
}
22+
}

tests/TemplateContravariantTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpStaticAnalysis\Attributes\Param;
6+
use PhpStaticAnalysis\Attributes\TemplateContravariant;
7+
use PHPUnit\Framework\TestCase;
8+
9+
#[TemplateContravariant('TClass', Exception::class)]
10+
class TemplateContravariantTest extends TestCase
11+
{
12+
public function testClassTemplateContravariant(): void
13+
{
14+
$reflection = new ReflectionClass($this);
15+
$this->assertEquals(['TClass of Exception'], self::getTemplateContravariantsFromReflection($reflection));
16+
}
17+
18+
public function testTraitTemplateContravariant(): void
19+
{
20+
$reflection = new ReflectionClass(TemplateContravariantTestTrait::class);
21+
$this->assertEquals(['TTrait'], self::getTemplateContravariantsFromReflection($reflection));
22+
}
23+
24+
public function testInterfaceTemplateContravariant(): void
25+
{
26+
$reflection = new ReflectionClass(TemplateContravariantTestInterface::class);
27+
$this->assertEquals(['TInterface'], self::getTemplateContravariantsFromReflection($reflection));
28+
}
29+
30+
public static function getTemplateContravariantsFromReflection(
31+
ReflectionClass $reflection
32+
): array {
33+
$attributes = $reflection->getAttributes();
34+
$templates = [];
35+
foreach ($attributes as $attribute) {
36+
if ($attribute->getName() === TemplateContravariant::class) {
37+
$attribute->newInstance();
38+
$templateData = $attribute->getArguments();
39+
$templateValue = $templateData[0];
40+
if (isset($templateData[1]) && $templateData[1] !== null) {
41+
$templateValue .= ' of ' . $templateData[1];
42+
}
43+
$templates[] = $templateValue;
44+
}
45+
}
46+
47+
return $templates;
48+
}
49+
}
50+
51+
#[TemplateContravariant('TTrait')]
52+
trait TemplateContravariantTestTrait
53+
{
54+
}
55+
56+
#[TemplateContravariant('TInterface')]
57+
interface TemplateContravariantTestInterface
58+
{
59+
}

tests/TemplateCovariantTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpStaticAnalysis\Attributes\Param;
6+
use PhpStaticAnalysis\Attributes\TemplateCovariant;
7+
use PHPUnit\Framework\TestCase;
8+
9+
#[TemplateCovariant('TClass', Exception::class)]
10+
class TemplateCovariantTest extends TestCase
11+
{
12+
public function testClassTemplateCovariant(): void
13+
{
14+
$reflection = new ReflectionClass($this);
15+
$this->assertEquals(['TClass of Exception'], self::getTemplateCovariantsFromReflection($reflection));
16+
}
17+
18+
public function testTraitTemplateCovariant(): void
19+
{
20+
$reflection = new ReflectionClass(TemplateCovariantTestTrait::class);
21+
$this->assertEquals(['TTrait'], self::getTemplateCovariantsFromReflection($reflection));
22+
}
23+
24+
public function testInterfaceTemplateCovariant(): void
25+
{
26+
$reflection = new ReflectionClass(TemplateCovariantTestInterface::class);
27+
$this->assertEquals(['TInterface'], self::getTemplateCovariantsFromReflection($reflection));
28+
}
29+
30+
public static function getTemplateCovariantsFromReflection(
31+
ReflectionClass $reflection
32+
): array {
33+
$attributes = $reflection->getAttributes();
34+
$templates = [];
35+
foreach ($attributes as $attribute) {
36+
if ($attribute->getName() === TemplateCovariant::class) {
37+
$attribute->newInstance();
38+
$templateData = $attribute->getArguments();
39+
$templateValue = $templateData[0];
40+
if (isset($templateData[1]) && $templateData[1] !== null) {
41+
$templateValue .= ' of ' . $templateData[1];
42+
}
43+
$templates[] = $templateValue;
44+
}
45+
}
46+
47+
return $templates;
48+
}
49+
}
50+
51+
#[TemplateCovariant('TTrait')]
52+
trait TemplateCovariantTestTrait
53+
{
54+
}
55+
56+
#[TemplateCovariant('TInterface')]
57+
interface TemplateCovariantTestInterface
58+
{
59+
}

0 commit comments

Comments
 (0)