https://github.com/sanmai/later
Later: a deferred object manager. Zero callbacks required. Futures for PHP.
https://github.com/sanmai/later
deferred-execution php-library
Last synced: about 1 year ago
JSON representation
Later: a deferred object manager. Zero callbacks required. Futures for PHP.
- Host: GitHub
- URL: https://github.com/sanmai/later
- Owner: sanmai
- License: apache-2.0
- Created: 2020-03-11T06:27:58.000Z (about 6 years ago)
- Default Branch: main
- Last Pushed: 2025-05-07T09:44:39.000Z (about 1 year ago)
- Last Synced: 2025-05-07T10:37:22.198Z (about 1 year ago)
- Topics: deferred-execution, php-library
- Language: PHP
- Homepage:
- Size: 94.7 KB
- Stars: 67
- Watchers: 3
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[](https://packagist.org/packages/sanmai/later)

[](https://coveralls.io/github/sanmai/later?branch=main)
[](https://shepherd.dev/github/sanmai/later)
This rigorously tested fully-typed library just works. It neither defines nor throws any exceptions.
# Install
```
composer require sanmai/later
```
The latest version requires PHP 7.4 or greater.
# Use
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:
For example:
```php
use function Later\later;
$deferred = later(function () {
$deepThought = new DeepThought();
$deepThought->solveTheQuestion();
yield $deepThought;
});
```
And then call `get()` when needed, as many times as needed:
```php
$deferred->get()->getAnswer(); // 42
$deferred->get()->getAnswer(); // same 42
```
Using a generator instead of a traditional callback comes with a major benefit: any generator is guaranteed by the language to be used exactly once. You can be sure that it won't be called twice.
But that's not all: read on.
## No Callbacks Required
Making a closure generator on the spot isn't always convenient. And not to say these closures are much different from all-too-familiar callbacks. Not at all different from the looks of them.
The power of this pattern is in its ability to make use of any function, previously returning a single value, without any need for any additional callbacks or closures.
Consider this diff:
```diff
private function makeFooBar()
{
//...
- return $foo;
+ yield $foo;
}
```
After adding `Later\lazy` to the mix:
```diff
use function Later\lazy;
public function __construct()
{
- $this->fooBar = $this->makeFooBar();
+ $this->lazyFooBar = lazy($this->makeFooBar());
}
public function usesFooBar()
{
if ($fooBarReallyRequired) {
- $this->fooBar->getResult();
+ $this->lazyFooBar->get()->getResult();
}
}
```
We can see, this simple, single-line, change in the original method freed our program from creating things it may not need, postponing this process until the last moment, while also avoiding any use of callbacks.
## Type Transparency
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.
To use this capability it is recommended to declare a variable holding this object as `\Later\Interfaces\Deferred`.
In this example it will be `Deferred`:
```php
use Later\Interfaces\Deferred;
use function Later\lazy;
final class HyperIntelligentMice
{
/** @var Deferred */
private $supercomputer;
public function __construct(DeepThought $deepThought)
{
$this->supercomputer = lazy(self::updateDeepThought($deepThought));
}
/** @return iterable */
private static function updateDeepThought(DeepThought $deepThought): iterable
{
$deepThought->solveTheQuestion();
yield $deepThought;
}
public function getAnswer(): int
{
return $this->supercomputer->get()->getAnswer();
}
}
```
Following this approach, a static analyzer or IDE will be able to understand what is called, and what is returned.
### Proxy Syntax
The library supports convenient proxy syntax for accessing methods and properties directly from the deferred object:
```php
/** @var \Later\Interfaces\Deferred $deferred */
$deferred->getAnswer(); // 42
$deferred->answer; // 42
```
With the explicit type IDEs such as PhpStorm can autocomplete proxied members.
## Eager Execution
What if a program calls for `Deferred` object, but lazy evaluation is not required? For example, because a result is already available being loaded from a cache.
No problem, there's a function for this:
```php
use function Later\now;
$deferred = now($result);
$deferred->get(); // returns $result
```
This deferred-but-not-deferred object implements the same interface, and can be used anywhere where a normal `Deferred` object would go.
# Writing Tests
The underlying `Deferred` object is fairly lax about input types. It will be happy to accept any `iterable`, not just generators.
This makes it super easy to use in mocks:
```php
use function Later\lazy;
$this->lazyDependency = lazy([
$myDependency,
]);
```
Yet for constant and already-known answers best to use a non-deferred variant:
```php
use function Later\now;
$this->lazyDependency = now($myDependency);
```
And that's it. No need to go through loops assembling closures and whatnot.
If nothing else, one can make a common mock for it:
```php
$deferredMock = $this->createMock(\Later\Interfaces\Deferred::class);
$deferredMock
->expects($this->once())
->method('get')
->willReturn($myDependency)
;
```
# API Overview
| Method | Takes | Returns |
| --------------- | ---------------------------- | ------------------------------- |
| `Later\lazy()` | `iterable` | `\Later\Interfaces\Deferred` |
| `Later\later()` | A generator callback for `T` | `\Later\Interfaces\Deferred` |
| `Later\now()` | `T` | `\Later\Interfaces\Deferred` |