Skip to content

Commit 72a82d7

Browse files
authored
Add magical calls to get() via __call() and __get() (#24)
1 parent 39e6777 commit 72a82d7

File tree

7 files changed

+93
-10
lines changed

7 files changed

+93
-10
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/composer.lock
22
/vendor
33
/build
4+
.?*

.phan/config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
return [
66
'target_php_version' => '7.4',
77
'backward_compatibility_checks' => false,
8+
'read_mixin_annotations' => false,
89
'exclude_analysis_directory_list' => [
910
'vendor/',
1011
],

README.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ This rigorously tested fully-typed library just works. It neither defines nor th
77

88
# Install
99

10-
composer require sanmai/later
10+
```
11+
composer require sanmai/later
12+
```
1113

1214
The latest version requires PHP 7.4 or greater.
1315

14-
# Use
16+
# Use
1517

1618
To use this pattern you need a generator function, yielding a single item of type you want to produce lazily. Pass it to `later()`, a static wrapper returning a `Deferred` object:
1719

@@ -83,7 +85,7 @@ We can see, this simple, single-line, change in the original method freed our pr
8385

8486
The library is completely typed. [PHPStan](https://github.com/phpstan/phpstan), [Psalm](https://github.com/vimeo/psalm), and [Phan](https://github.com/phan/phan) are all routinely supported.
8587

86-
To exploit this capability it is recommended to declare a variable holding this object as `\Later\Interfaces\Deferred<Type>`.
88+
To use this capability it is recommended to declare a variable holding this object as `\Later\Interfaces\Deferred<Type>`.
8789

8890
In this example it will be `Deferred<DeepThought>`:
8991

@@ -116,7 +118,19 @@ final class HyperIntelligentMice
116118
}
117119
```
118120

119-
Following this approach, a static analyzer will be able to understand what is called, and what is returned.
121+
Following this approach, a static analyzer or IDE will be able to understand what is called, and what is returned.
122+
123+
### Proxy Syntax
124+
125+
The library supports convenient proxy syntax for accessing methods and properties directly from the deferred object:
126+
127+
```php
128+
/** @var \Later\Interfaces\Deferred<DeepThought> $deferred */
129+
$deferred->getAnswer(); // 42
130+
$deferred->answer; // 42
131+
```
132+
133+
With the explicit type IDEs such as PhpStorm can autocomplete proxied members.
120134

121135
## Eager Execution
122136

@@ -135,7 +149,7 @@ This deferred-but-not-deferred object implements the same interface, and can be
135149

136150
# Writing Tests
137151

138-
The underlying `Deferred` object is fairly lax about input types. It will be happy to accept any `iterable`, not just generators.
152+
The underlying `Deferred` object is fairly lax about input types. It will be happy to accept any `iterable`, not just generators.
139153

140154
This makes it super easy to use in mocks:
141155

@@ -170,8 +184,8 @@ $deferredMock
170184

171185
# API Overview
172186

173-
| Method | Takes | Returns |
174-
| ------------------ | ----------------------------- | ----------- |
175-
| `Later\lazy()` | `iterable<T>` | `\Later\Interfaces\Deferred<T>` |
176-
| `Later\later()` | A generator callback for `T` | `\Later\Interfaces\Deferred<T>` |
177-
| `Later\now()` | `T` | `\Later\Interfaces\Deferred<T>` |
187+
| Method | Takes | Returns |
188+
| --------------- | ---------------------------- | ------------------------------- |
189+
| `Later\lazy()` | `iterable<T>` | `\Later\Interfaces\Deferred<T>` |
190+
| `Later\later()` | A generator callback for `T` | `\Later\Interfaces\Deferred<T>` |
191+
| `Later\now()` | `T` | `\Later\Interfaces\Deferred<T>` |

src/Deferred.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* Deferred: a wrapper object.
2828
*
2929
* @template T
30+
* @mixin T
3031
*
3132
* @template-implements Interfaces\Deferred<T>
3233
*
@@ -86,4 +87,21 @@ public function get()
8687

8788
return $this->output;
8889
}
90+
91+
/**
92+
* Forwards the call to the resolved object; PHP throws \Error if the method is missing.
93+
* MixedMethodCall is suppressed because Psalm cannot infer methods on the generic type.
94+
*
95+
* @param array<mixed> $args Forwarded variadic arguments.
96+
* @psalm-suppress MixedMethodCall
97+
*/
98+
public function __call(string $name, array $args): mixed
99+
{
100+
return $this->get()->{$name}(...$args);
101+
}
102+
103+
public function __get(string $name): mixed
104+
{
105+
return $this->get()->$name;
106+
}
89107
}

src/Interfaces/Deferred.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
/**
2424
* @template T
25+
* @mixin T
2526
*/
2627
interface Deferred
2728
{

tests/DeferredTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
use Later\Deferred;
2424
use ReflectionClass;
2525
use InvalidArgumentException;
26+
use Tests\Later\Examples\MagicalFoo;
27+
28+
use function Later\later;
2629

2730
/**
2831
* @covers \Later\Deferred
@@ -124,4 +127,20 @@ private function generatorThrows(bool $throw = false): iterable
124127

125128
yield 1;
126129
}
130+
131+
public function testMagicCall(): void
132+
{
133+
$deferred = later(fn() => yield new MagicalFoo());
134+
135+
/** @var Deferred<MagicalFoo> $deferred */
136+
$this->assertSame(42, $deferred->getAnswer());
137+
}
138+
139+
public function testMagicGet(): void
140+
{
141+
$deferred = later(fn() => yield new MagicalFoo());
142+
143+
/** @var Deferred<MagicalFoo> $deferred */
144+
$this->assertSame(42, $deferred->answer);
145+
}
127146
}

tests/Examples/MagicalFoo.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2020 Alexey Kopytko <[email protected]>
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
namespace Tests\Later\Examples;
20+
21+
class MagicalFoo
22+
{
23+
public int $answer = 42;
24+
25+
public function getAnswer(): int
26+
{
27+
return 42;
28+
}
29+
}

0 commit comments

Comments
 (0)