Skip to content

Commit d480b33

Browse files
Add the PropertyRead and PropertyWrite attributes
1 parent 07f1ede commit d480b33

File tree

7 files changed

+198
-8
lines changed

7 files changed

+198
-8
lines changed

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,14 @@ 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-
| [Returns](doc/Returns.md) | `@return` |
101-
| [Template](doc/Template.md) | `@template` |
102-
| [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+
| [Type](doc/Type.md) | `@var` `@return` |
103105

doc/PropertyRead.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# `PropertyRead` Attribute
2+
3+
This attribute is the equivalent of the `@property-read` annotation and is used to specify the type of properties accessed through magic `__get` methods. These properties can only be read and not written to. It can also be used to override wrong property types from a parent class.
4+
5+
## Arguments
6+
7+
The attribute accepts one or more strings which describe the type of the properties. The attribute itself does not have a knowledge of which types are valid and which are not and this will depend on the implementation for each particular tool.
8+
9+
We expect that the attribute will be able to accept both basic types like `string` or `array` and more advanced types like `array<string>` or `Collection<int>`. We aim to accept all the types accepted by static analysis tools for the `@property` annotation.
10+
11+
The arguments can be named arguments and the type is applied to the properties with the same name in the class.
12+
13+
They can also be unnamed arguments with a string that contains both the type and the name of the property, but we recommend using named arguments.
14+
15+
If the class has more than one property that we want to specify, the types for the different properties can either be declared as a list of strings for a single `PropertyRead` attribute or as a list of `PropertyRead` attributes (or even a combination of both, though we don't expect this to be actually used).
16+
17+
## Example usage
18+
19+
```php
20+
<?php
21+
22+
use PhpStaticAnalysis\Attributes\PropertyRead;
23+
24+
#[PropertyRead(name: 'string')]
25+
#[PropertyRead('int $age')]
26+
#[PropertyRead(
27+
index1: 'string[]',
28+
index2: 'string[]',
29+
)]
30+
class PropertyReadExample
31+
{
32+
...
33+
}
34+
```

doc/PropertyWrite.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# `PropertyWrite` Attribute
2+
3+
This attribute is the equivalent of the `@property-write` annotation and is used to specify the type of properties accessed through magic `__get` methods. These properties can only be written to and not read. It can also be used to override wrong property types from a parent class.
4+
5+
## Arguments
6+
7+
The attribute accepts one or more strings which describe the type of the properties. The attribute itself does not have a knowledge of which types are valid and which are not and this will depend on the implementation for each particular tool.
8+
9+
We expect that the attribute will be able to accept both basic types like `string` or `array` and more advanced types like `array<string>` or `Collection<int>`. We aim to accept all the types accepted by static analysis tools for the `@property` annotation.
10+
11+
The arguments can be named arguments and the type is applied to the properties with the same name in the class.
12+
13+
They can also be unnamed arguments with a string that contains both the type and the name of the property, but we recommend using named arguments.
14+
15+
If the class has more than one property that we want to specify, the types for the different properties can either be declared as a list of strings for a single `PropertyWrite` attribute or as a list of `PropertyWrite` attributes (or even a combination of both, though we don't expect this to be actually used).
16+
17+
## Example usage
18+
19+
```php
20+
<?php
21+
22+
use PhpStaticAnalysis\Attributes\PropertyWrite;
23+
24+
#[PropertyWrite(name: 'string')]
25+
#[PropertyWrite('int $age')]
26+
#[PropertyWrite(
27+
index1: 'string[]',
28+
index2: 'string[]',
29+
)]
30+
class PropertyWriteExample
31+
{
32+
...
33+
}
34+
```

src/PropertyRead.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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::IS_REPEATABLE
12+
)]
13+
final class PropertyRead
14+
{
15+
public function __construct(
16+
string ...$params
17+
) {
18+
}
19+
}

src/PropertyWrite.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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::IS_REPEATABLE
12+
)]
13+
final class PropertyWrite
14+
{
15+
public function __construct(
16+
string ...$params
17+
) {
18+
}
19+
}

tests/PropertyReadTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpStaticAnalysis\Attributes\PropertyRead;
6+
use PHPUnit\Framework\TestCase;
7+
8+
#[PropertyRead(name: 'string')]
9+
#[PropertyRead('int $age')]
10+
#[PropertyRead(
11+
index1: 'string[]',
12+
index2: 'string[]',
13+
)]
14+
class PropertyReadTest extends TestCase
15+
{
16+
public function testClassProperties(): void
17+
{
18+
$reflection = new ReflectionClass($this);
19+
$this->assertEquals([
20+
0 => 'int $age',
21+
'name' => 'string',
22+
'index1' => 'string[]',
23+
'index2' => 'string[]',
24+
], self::getPropertiesFromReflection($reflection));
25+
}
26+
27+
public static function getPropertiesFromReflection(
28+
ReflectionProperty | ReflectionClass $reflection
29+
): array {
30+
$attributes = $reflection->getAttributes();
31+
$properties = [];
32+
foreach ($attributes as $attribute) {
33+
if ($attribute->getName() === PropertyRead::class) {
34+
$attribute->newInstance();
35+
$properties = array_merge($properties, $attribute->getArguments());
36+
}
37+
}
38+
39+
return $properties;
40+
}
41+
}

tests/PropertyWriteTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpStaticAnalysis\Attributes\PropertyWrite;
6+
use PHPUnit\Framework\TestCase;
7+
8+
#[PropertyWrite(name: 'string')]
9+
#[PropertyWrite('int $age')]
10+
#[PropertyWrite(
11+
index1: 'string[]',
12+
index2: 'string[]',
13+
)]
14+
class PropertyWriteTest extends TestCase
15+
{
16+
public function testClassProperties(): void
17+
{
18+
$reflection = new ReflectionClass($this);
19+
$this->assertEquals([
20+
0 => 'int $age',
21+
'name' => 'string',
22+
'index1' => 'string[]',
23+
'index2' => 'string[]',
24+
], self::getPropertiesFromReflection($reflection));
25+
}
26+
27+
public static function getPropertiesFromReflection(
28+
ReflectionProperty | ReflectionClass $reflection
29+
): array {
30+
$attributes = $reflection->getAttributes();
31+
$properties = [];
32+
foreach ($attributes as $attribute) {
33+
if ($attribute->getName() === PropertyWrite::class) {
34+
$attribute->newInstance();
35+
$properties = array_merge($properties, $attribute->getArguments());
36+
}
37+
}
38+
39+
return $properties;
40+
}
41+
}

0 commit comments

Comments
 (0)