|
1 | | -# DataLoaderPhp |
| 1 | +# DataLoaderPHP |
2 | 2 |
|
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 |
4 | 4 | fetching layer to provide a simplified and consistent API over various remote |
5 | 5 | data sources such as databases or web services via batching and caching. |
6 | 6 |
|
7 | | -This package is a PHP port of the [JS version](https://github.com/facebook/dataloader). |
8 | | - |
9 | 7 | [](https://travis-ci.org/overblog/dataloader-php) |
10 | 8 | [](https://coveralls.io/github/overblog/dataloader-php?branch=master) |
11 | 9 | [](https://packagist.org/packages/overblog/dataloader-php) |
12 | 10 | [](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. |
0 commit comments