Skip to content

Commit f73456c

Browse files
Merge branch 'main' into TestTagOnClass
2 parents 1161a8b + 99e3149 commit f73456c

6 files changed

Lines changed: 264 additions & 0 deletions

File tree

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ The intention, at least initially, is that these extra language features are enf
1515

1616
**Language feature added:**
1717
- [Friend](#friend)
18+
- [MustUseResult](#mustuseresult)
1819
- [NamespaceVisibility](#namespaceVisibility)
1920
- [InjectableVersion](#injectableVersion)
2021
- [Override](#override)
22+
- [RestrictTraitTo](#restricttraitto)
2123
- [Sealed](#sealed)
2224
- [TestTag](#testtag)
2325

@@ -29,9 +31,11 @@ The intention, at least initially, is that these extra language features are enf
2931
- [Psalm](#psalm)
3032
- [New Language Features](#new-language-features)
3133
- [Friend](#friend)
34+
- [MustUseResult](#mustuseresult)
3235
- [NamespaceVisibility](#namespaceVisibility)
3336
- [InjectableVersion](#injectableVersion)
3437
- [Override](#override)
38+
- [RestrictTraitTo](#restricttraitto)
3539
- [Sealed](#sealed)
3640
- [TestTag](#testtag)
3741
- Deprecated
@@ -129,7 +133,40 @@ $person = new Person();
129133
```
130134
- This is currently limited to method calls (including `__construct`).
131135

136+
## MustUseResult
132137

138+
Add #[MustUseResult] attribute that can be used on methods. This enforces the result from the method call must be used.
139+
140+
E.g. if you have a class like this:
141+
142+
```php
143+
144+
class Money {
145+
146+
public function __construct(public readonly int $pence)
147+
{}
148+
149+
#[MustUseResult]
150+
public function add(int $pence): self
151+
{
152+
return new self($pence + $this->pence);
153+
}
154+
}
155+
```
156+
157+
You might misuse the `add` method in this way:
158+
159+
```php
160+
$cost = new Money(5);
161+
$cost->add(6); // ERROR - This statement has no effect.
162+
```
163+
164+
But this would be OK:
165+
166+
```php
167+
$cost = new Money(5);
168+
$updatedCost = $cost->add(6); // OK - The return from add method is being used.
169+
```
133170

134171
## NamespaceVisibility
135172

@@ -382,6 +419,32 @@ NOTE:
382419
- If you are using PHP 8.3 then use the real `#[Override]` attribute.
383420
- This implementation doesn't consider traits.
384421

422+
## RestrictTraitTo
423+
424+
This limits the use of a Trait to only be used by a specified class of a child of that class.
425+
426+
E.g. this trait is limited to classes that are or extend `Controller`
427+
428+
```php
429+
#[RestrictTraitTo(Controller::class)]
430+
trait ControllerHelpers {}
431+
```
432+
433+
This would be allowed:
434+
```php
435+
class LoginController extends Controller {
436+
use ControllerHelpers;
437+
}
438+
```
439+
440+
But this would NOT be allowed:
441+
```php
442+
class Repository {
443+
use ControllerHelpers;
444+
}
445+
```
446+
447+
385448
## Sealed
386449

387450
This is inspired by the rejected [sealed classes RFC](https://wiki.php.net/rfc/sealed_classes)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace MustUseResultOnMethod {
4+
5+
6+
use DaveLiddament\PhpLanguageExtensions\MustUseResult;
7+
8+
class AClass {
9+
10+
#[MustUseResult]
11+
public function mustUseResult(): int
12+
{
13+
return 1;
14+
}
15+
16+
public function dontNeedToUseResult(): int
17+
{
18+
return 2;
19+
}
20+
21+
}
22+
23+
24+
$class = new AClass();
25+
26+
$class->dontNeedToUseResult(); // OK
27+
28+
$class->mustUseResult(); // ERROR
29+
30+
echo $class->mustUseResult(); // OK;
31+
32+
$value = 1 + $class->mustUseResult(); // OK
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace MustUseResultOnMethod {
4+
5+
6+
use DaveLiddament\PhpLanguageExtensions\MustUseResult;
7+
8+
class AClass {
9+
10+
#[MustUseResult]
11+
public static function mustUseResult(): int
12+
{
13+
return 1;
14+
}
15+
16+
public static function dontNeedToUseResult(): int
17+
{
18+
return 2;
19+
}
20+
21+
}
22+
23+
24+
AClass::dontNeedToUseResult(); // OK
25+
26+
AClass::mustUseResult(); // ERROR
27+
28+
echo AClass::mustUseResult(); // OK;
29+
30+
$value = 1 + AClass::mustUseResult(); // OK
31+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RestrictTraitTo;
6+
7+
use DaveLiddament\PhpLanguageExtensions\RestrictTraitTo;
8+
9+
trait UseAnywhere {}
10+
11+
#[RestrictTraitTo(Interface1::class)]
12+
trait UseOnlyOnInterface1 {}
13+
14+
#[RestrictTraitTo(AbstractClass1::class)]
15+
trait UseOnlyOnAbstractClass1 {}
16+
17+
#[RestrictTraitTo(Class2::class)]
18+
trait UseOnlyOnClass2 {}
19+
20+
21+
interface Interface1 {}
22+
23+
24+
class AClass {
25+
use UseAnywhere; // OK
26+
use UseOnlyOnInterface1; // ERROR RestrictTraitTo\Interface1
27+
use UseOnlyOnAbstractClass1; // ERROR RestrictTraitTo\AbstractClass1
28+
use UseOnlyOnClass2; // ERROR RestrictTraitTo\Class2
29+
}
30+
31+
32+
class ImplementsInterface1 implements Interface1 {
33+
use UseAnywhere; // OK
34+
use UseOnlyOnInterface1; // OK
35+
use UseOnlyOnAbstractClass1; // ERROR RestrictTraitTo\AbstractClass1
36+
use UseOnlyOnClass2; // ERROR RestrictTraitTo\Class2
37+
}
38+
39+
abstract class AbstractClass1
40+
{
41+
42+
}
43+
44+
class ExtendsAbstractClass1 extends AbstractClass1 {
45+
use UseAnywhere; // OK
46+
use UseOnlyOnInterface1; // ERROR RestrictTraitTo\Interface1
47+
use UseOnlyOnAbstractClass1; // OK
48+
use UseOnlyOnClass2; // ERROR RestrictTraitTo\Class2
49+
}
50+
51+
class Class2 {
52+
use UseAnywhere; // OK
53+
use UseOnlyOnInterface1; // ERROR RestrictTraitTo\Interface1
54+
use UseOnlyOnAbstractClass1; // ERROR RestrictTraitTo\AbstractClass1
55+
use UseOnlyOnClass2; // OK
56+
}

src/MustUseResult.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpLanguageExtensions;
6+
7+
/**
8+
* Enforces that the result from a method call must be used.
9+
*
10+
* Assume the following class:
11+
* ```
12+
* class Money {
13+
*
14+
* public function __construct(public readonly int $pence)
15+
* {}
16+
*
17+
* #[MustUseResult]
18+
* public function add(int $pence): self
19+
* {
20+
* return new self($pence + $this->pence);
21+
* }
22+
* }
23+
* ```
24+
*
25+
* You might misuse the `add` method in this way:
26+
*
27+
* ```
28+
* $cost = new Money(5);
29+
* $cost->add(6); // ERROR - This statement has no effect.
30+
* ```
31+
*
32+
* But this would be OK:
33+
*
34+
* ```
35+
* $cost = new Money(5);
36+
* $updatedCost = $cost->add(6); // OK - The return from add method is being used.
37+
* ```
38+
*/
39+
#[\Attribute(\Attribute::TARGET_METHOD)]
40+
final class MustUseResult
41+
{
42+
}

src/RestrictTraitTo.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DaveLiddament\PhpLanguageExtensions;
6+
7+
/**
8+
* Enforces that the trait can only be used on the specified class, or children of that class.
9+
*
10+
* E.g. this trait is limited to classes that are or extend `Controller`
11+
*
12+
* ```
13+
* #[RestrictTraitTo(Controller::class)]
14+
* trait ControllerHelpers {}
15+
* ```
16+
*
17+
* This would be allowed:
18+
* ```
19+
* class LoginController extends Controller {
20+
* use ControllerHelpers;
21+
* }
22+
* ```
23+
*
24+
* But this would NOT be allowed:
25+
* ```
26+
* class Repository {
27+
* use ControllerHelpers;
28+
* }
29+
* ```
30+
*/
31+
#[\Attribute(\Attribute::TARGET_CLASS)]
32+
final class RestrictTraitTo
33+
{
34+
/** @param class-string $className */
35+
public function __construct(
36+
public string $className,
37+
) {
38+
}
39+
}

0 commit comments

Comments
 (0)