Skip to content

Commit ba33d6f

Browse files
authored
Merge pull request #3 from mcg-web/docs
Public API documentation
2 parents 66d7034 + 2393d74 commit ba33d6f

File tree

2 files changed

+237
-24
lines changed

2 files changed

+237
-24
lines changed

README.md

Lines changed: 213 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,221 @@
1-
# DataLoaderPhp
1+
# DataLoaderPHP
22

3-
DataLoader created by Facebook (c) is a generic utility to be used as part of your application's data
3+
DataLoaderPHP is a generic utility to be used as part of your application's data
44
fetching layer to provide a simplified and consistent API over various remote
55
data sources such as databases or web services via batching and caching.
66

7-
This package is a PHP port of the [JS version](https://github.com/facebook/dataloader).
8-
97
[![Build Status](https://travis-ci.org/overblog/dataloader-php.svg?branch=master)](https://travis-ci.org/overblog/dataloader-php)
108
[![Coverage Status](https://coveralls.io/repos/github/overblog/dataloader-php/badge.svg?branch=master)](https://coveralls.io/github/overblog/dataloader-php?branch=master)
119
[![Latest Stable Version](https://poser.pugx.org/overblog/dataloader-php/version)](https://packagist.org/packages/overblog/dataloader-php)
1210
[![License](https://poser.pugx.org/overblog/dataloader-php/license)](https://packagist.org/packages/overblog/dataloader-php)
11+
12+
## Requirements
13+
14+
* This library require [React/Promise](https://github.com/reactphp/promise) and PHP >= 5.5 to works.
15+
* The [React/EventLoop](https://github.com/reactphp/event-loop) component are **totally optional** (see `await` method for more details).
16+
17+
## Getting Started
18+
19+
First, install DataLoaderPHP using composer.
20+
21+
```sh
22+
composer require "overblog/dataloader-php"
23+
```
24+
25+
To get started, create a `DataLoader` object.
26+
27+
Batching is not an advanced feature, it's DataLoaderPHP's primary feature.
28+
Create loaders by providing a batch loading instance.
29+
30+
31+
```php
32+
use Overblog\DataLoader\DataLoader;
33+
34+
$myBatchGetUsers = function ($keys) { /* ... */ };
35+
36+
$userLoader = new DataLoader(new BatchLoadFn($myBatchGetUsers));
37+
```
38+
39+
A batch loading instance accepts a callable callback that accepts an Array of keys, and returns a Promise which
40+
resolves to an Array of values.
41+
42+
Then load individual values from the loader. DataLoaderPHP will coalesce all
43+
individual loads which occur within a single frame of execution (a single tick
44+
of the event loop if install or using `await` method) and then call your batch function with all requested keys.
45+
46+
```php
47+
$userLoader->load(1)
48+
->then(function ($user) use ($userLoader) { $userLoader->load($user->invitedByID); })
49+
->then(function ($invitedBy) { echo "User 1 was invited by $invitedBy"; }));
50+
51+
// Elsewhere in your application
52+
$userLoader->load(2)
53+
->then(function ($user) use ($userLoader) { $userLoader->load($user->invitedByID); })
54+
->then(function ($invitedBy) { echo "User 2 was invited by $invitedBy"; }));
55+
56+
// Synchronously waits on the promise to complete, if not using EventLoop.
57+
$userLoader->await();
58+
```
59+
A naive application may have issued four round-trips to a backend for the
60+
required information, but with DataLoaderPHP this application will make at most
61+
two.
62+
63+
DataLoaderPHP allows you to decouple unrelated parts of your application without
64+
sacrificing the performance of batch data-loading. While the loader presents an
65+
API that loads individual values, all concurrent requests will be coalesced and
66+
presented to your batch loading function. This allows your application to safely
67+
distribute data fetching requirements throughout your application and maintain
68+
minimal outgoing data requests.
69+
70+
### Caching (current PHP instance)
71+
72+
After being loaded once, the resulting value is cached, eliminating
73+
redundant requests.
74+
75+
In the example above, if User `1` was last invited by User `2`, only a single
76+
round trip will occur.
77+
78+
Caching results in creating fewer objects which may relieve memory pressure on
79+
your application:
80+
81+
```php
82+
$promise1A = $userLoader->load(1);
83+
$promise1B = userLoader->load(1);
84+
var_dump($promise1A === $promise1B); // bool(true)
85+
```
86+
87+
There are two common examples when clearing the loader's cache is necessary:
88+
89+
*Mutations:* after a mutation or update, a cached value may be out of date.
90+
Future loads should not use any possibly cached value.
91+
92+
Here's a simple example using SQL UPDATE to illustrate.
93+
94+
```php
95+
$sql = 'UPDATE users WHERE id=4 SET username="zuck"';
96+
if (true === $conn->query($sql)) {
97+
$userLoader->clear(4);
98+
}
99+
```
100+
101+
*Transient Errors:* A load may fail because it simply can't be loaded
102+
(a permanent issue) or it may fail because of a transient issue such as a down
103+
database or network issue. For transient errors, clear the cache:
104+
105+
```php
106+
$userLoader->load(1)->otherwise(function ($exception) {
107+
if (/* determine if error is transient */) {
108+
$userLoader->clear(1);
109+
}
110+
throw $exception;
111+
});
112+
```
113+
114+
## API
115+
116+
#### class DataLoader
117+
118+
DataLoaderPHP creates a public API for loading data from a particular
119+
data back-end with unique keys such as the `id` column of a SQL table or
120+
document name in a MongoDB database, given a batch loading function.
121+
122+
Each `DataLoaderPHP` instance contains a unique memoized cache. Use caution when
123+
used in long-lived applications or those which serve many users with different
124+
access permissions and consider creating a new instance per web request.
125+
126+
##### `new DataLoader(batchLoadFn $batchLoadFn [, Option $options])`
127+
128+
Create a new `DataLoaderPHP` given a batch loading instance and options.
129+
130+
- *$batchLoadFn*: A object which accepts a callable callback that accepts an Array of keys,
131+
and returns a Promise which resolves to an Array of values.
132+
- *$options*: An optional object of options:
133+
134+
- *batch*: Default `true`. Set to `false` to disable batching, instead
135+
immediately invoking `batchLoadFn` with a single load key.
136+
137+
- *maxBatchSize*: Default `Infinity`. Limits the number of items that get
138+
passed in to the `batchLoadFn`.
139+
140+
- *cache*: Default `true`. Set to `false` to disable caching, instead
141+
creating a new Promise and new key in the `batchLoadFn` for every load.
142+
143+
- *cacheKeyFn*: A function to produce a cache key for a given load key.
144+
Defaults to `key => key`. Useful to provide when JavaScript objects are keys
145+
and two similarly shaped objects should be considered equivalent.
146+
147+
- *cacheMap*: An instance of `CacheMap` to be
148+
used as the underlying cache for this loader. Default `new CacheMap()`.
149+
150+
##### `load($key)`
151+
152+
Loads a key, returning a `Promise` for the value represented by that key.
153+
154+
- *$key*: An key value to load.
155+
156+
##### `loadMany($keys)`
157+
158+
Loads multiple keys, promising an array of values:
159+
160+
```php
161+
list($a, $b) = DataLoader::await($myLoader->loadMany(['a', 'b']);
162+
```
163+
164+
This is equivalent to the more verbose:
165+
166+
```js
167+
list($a, $b) = await DataLoader::await(\React\Promise\all([
168+
$myLoader->load('a'),
169+
$myLoader->load('b')
170+
]);
171+
```
172+
173+
- *$keys*: An array of key values to load.
174+
175+
##### `clear($key)`
176+
177+
Clears the value at `$key` from the cache, if it exists. Returns itself for
178+
method chaining.
179+
180+
- *$key*: An key value to clear.
181+
182+
##### `clearAll()`
183+
184+
Clears the entire cache. To be used when some event results in unknown
185+
invalidations across this particular `DataLoaderPHP`. Returns itself for
186+
method chaining.
187+
188+
##### `prime($key, $value)`
189+
190+
Primes the cache with the provided key and value. If the key already exists, no
191+
change is made. (To forcefully prime the cache, clear the key first with
192+
`$loader->clear($key)->prime($key, $value)`.) Returns itself for method chaining.
193+
194+
##### `static await([$promise][, $unwrap])`
195+
196+
You can synchronously force promises to complete using DataLoaderPHP's await method.
197+
When an await function is invoked it is expected to deliver a value to the promise or reject the promise.
198+
Await method process all waiting promise in all dataLoaderPHP instances.
199+
200+
- *$promise*: Optional promise to complete.
201+
202+
- *$unwrap*: controls whether or not the value of the promise is returned for a fulfilled promise
203+
or if an exception is thrown if the promise is rejected. Default `true`.
204+
205+
## Using with Webonyx/GraphQL [WIP]
206+
207+
A [PR](https://github.com/webonyx/graphql-php/pull/67) is open on [Webonyx/GraphQL](https://github.com/webonyx/graphql-php)
208+
to supports DataLoaderPHP and more generally promise.
209+
Here [an example](https://github.com/mcg-web/sandbox-dataloader-graphql-php/blob/master/with-dataloader.php).
210+
211+
## Credits
212+
213+
Overblog/DataLoaderPHP is a port of [dataLoader NodeJS version](https://github.com/facebook/dataloader)
214+
by [Facebook](https://github.com/facebook).
215+
216+
Also, large parts of the documentation have been ported from the dataLoader NodeJS version
217+
[Docs](https://github.com/facebook/dataloader/blob/master/README.md).
218+
219+
## License
220+
221+
Overblog/DataLoaderPHP is released under the [MIT](https://github.com/overblog/dataloader-php/blob/master/LICENSE) license.

src/DataLoader.php

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,12 @@ public function __destruct()
221221
}
222222
}
223223

224-
public function needProcess()
224+
protected function needProcess()
225225
{
226226
return count($this->queue) > 0;
227227
}
228228

229-
public function process()
229+
protected function process()
230230
{
231231
if ($this->needProcess()) {
232232
$this->dispatchQueue();
@@ -237,30 +237,34 @@ public function process()
237237
* @param $promise
238238
* @param bool $unwrap controls whether or not the value of the promise is returned for a fulfilled promise or if an exception is thrown if the promise is rejected
239239
* @return mixed
240+
* @throws \Exception
240241
*/
241-
public static function await($promise, $unwrap = true)
242+
public static function await($promise = null, $unwrap = true)
242243
{
243-
$resolvedValue = null;
244-
$exception = null;
245-
246-
if (!is_callable([$promise, 'then'])) {
247-
throw new \InvalidArgumentException('Promise must have a "then" method.');
248-
}
249244
self::awaitInstances();
250245

251-
$promise->then(function ($values) use (&$resolvedValue) {
252-
$resolvedValue = $values;
253-
}, function ($reason) use (&$exception) {
254-
$exception = $reason;
255-
});
256-
if ($exception instanceof \Exception) {
257-
if (!$unwrap) {
258-
return $exception;
246+
if (null !== $promise) {
247+
$resolvedValue = null;
248+
$exception = null;
249+
250+
if (!is_callable([$promise, 'then'])) {
251+
throw new \InvalidArgumentException('Promise must have a "then" method.');
259252
}
260-
throw $exception;
261-
}
262253

263-
return $resolvedValue;
254+
$promise->then(function ($values) use (&$resolvedValue) {
255+
$resolvedValue = $values;
256+
}, function ($reason) use (&$exception) {
257+
$exception = $reason;
258+
});
259+
if ($exception instanceof \Exception) {
260+
if (!$unwrap) {
261+
return $exception;
262+
}
263+
throw $exception;
264+
}
265+
266+
return $resolvedValue;
267+
}
264268
}
265269

266270
private static function awaitInstances()

0 commit comments

Comments
 (0)