Skip to content

Commit f6abf7e

Browse files
author
Juuso Leinonen
committed
Draft some documentation
1 parent 2189d03 commit f6abf7e

File tree

2 files changed

+141
-30
lines changed

2 files changed

+141
-30
lines changed

README.md

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# PHP dataloader
2-
Quick port of the [Facebook's DataLoader](https://github.com/facebook/dataloader) to PHP. Async superpowers from [ReactPHP](https://github.com/reactphp)
1+
# PHP DataLoader
2+
Port of the [Facebook's DataLoader](https://github.com/facebook/dataloader) to PHP. Async superpowers from [ReactPHP](https://github.com/reactphp).
3+
4+
DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.
35

46
[![Build Status](https://travis-ci.org/lordthorzonus/php-dataloader.svg?branch=master)](https://travis-ci.org/lordthorzonus/php-dataloader)
57
[![Code Coverage](https://scrutinizer-ci.com/g/lordthorzonus/php-dataloader/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/lordthorzonus/php-dataloader/?branch=master)
@@ -10,15 +12,140 @@ Quick port of the [Facebook's DataLoader](https://github.com/facebook/dataloader
1012
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/lordthorzonus/php-dataloader/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/lordthorzonus/php-dataloader/?branch=master)
1113
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/44a2e0f3-cde6-48b9-b484-8243a64145de/mini.png)](https://insight.sensiolabs.com/projects/44a2e0f3-cde6-48b9-b484-8243a64145de)
1214

15+
Table of contents
16+
=================
17+
18+
* [Installation](#installation)
19+
* [Usage](#usage)
20+
* [Batch function](#batch-function)
21+
* [Caching](#caching)
22+
* [Usage with common ORM's](#usage-with-common-orms)
23+
* [API](#api)
24+
25+
## Installation
26+
27+
Require this package, with [Composer](https://getcomposer.org/), in the root directory of your project.
28+
29+
```bash
30+
composer require leinonen/php-dataloader
31+
```
32+
33+
## Usage
34+
To create a loader you must provide a batching function, an internal memoization cache and the global event loop from ReactPHP.
35+
36+
```php
37+
use leinonen\DataLoader\Dataloader;
38+
use React\EventLoop\Factory;
39+
40+
$eventLoop = Factory::create();
41+
42+
$bandLoader = new DataLoader(
43+
function ($keys) {
44+
// Batch load users with given keys.
45+
},
46+
$eventLoop,
47+
new CacheMap()
48+
);
49+
```
50+
51+
Then load individual values from the loader. DataLoader will coalesce all individual loads which occur within a single tick of the event loop and then call your batch function with all requested keys.
52+
53+
```php
54+
$bandLoader->load(1)->then(function ($band) {
55+
echo "Band #${$band->getId()} loaded";
56+
});
57+
58+
$bandLoader->load(2)->then(function ($band) {
59+
echo "Band #${$band->getId()} loaded";
60+
});
61+
62+
$eventLoop->run();
63+
```
64+
65+
Calling the load function returns `React\Promise\Promise`s. To have a better understanding how to use promises within PHP refer to the [ReactPHP docs](https://github.com/reactphp/promise).
66+
67+
### Batch Function
68+
69+
The batch loading function accepts an array of keys, and must return a Promise which resolves to an Array of values. There are a few other constraints:
70+
71+
- The Array of values must be the same length as the Array of keys.
72+
- Each index in the Array of values must correspond to the same index in the Array of keys i.e. The order of the batch loaded results must be the same as the order of given keys.
73+
74+
For example, if your batch function was provided the Array of keys: `[2, 9, 6, 1]`, and the batch loaded results were:
75+
```php
76+
[
77+
['id' => 1, 'name' => 'Mojo Waves'],
78+
['id' => 2, 'name' => 'Pleasure Hazard'],
79+
['id' => 9, 'name' => 'Leka'],
80+
]
81+
```
82+
83+
The loaded results are in a different order that we requested which is quite common with most of the relation dbs for example. Also result for key `6` is omitted which we can interpret as no value existing for that key.
84+
85+
To satisfy the constraints of the batch function we need to modify the results to be the same length as the Array of keys and re-order them to ensure each index aligns with the original keys:
86+
87+
```php
88+
[
89+
['id' => 2, 'name' => 'Pleasure Hazard'],
90+
['id' => 9, 'name' => 'Leka'],
91+
null,
92+
['id' => 1, 'name' => 'Mojo Waves'],
93+
]
94+
```
95+
96+
### Caching
97+
DataLoader provides a memoization cache for all loads which occur in a single request to your application. After `load()` is called once with a given key, the resulting value is cached to eliminate redundant loads.
98+
99+
In addition to relieving load on your data storage, caching results per-request also creates fewer objects which may relieve memory pressure on your application:
100+
101+
```php
102+
$promise1 = $bandLoader->load(1);
103+
$promise2 = $bandLoader->load(2);
104+
105+
($promise1 === $promise2) // true
106+
```
107+
108+
DataLoader caching does not replace Redis, Memcache, or any other shared application-level cache. DataLoader is first and foremost a data loading mechanism, and its cache only serves the purpose of not repeatedly loading the same data in the context of a single request to your Application. To do this it utilizes the CacheMap given as a constructor argument.
109+
110+
This package provides a simple CacheMap (`leinonen\DataLoader\CacheMap`) implementation to be used with DataLoader. You can also use your custom CacheMap with various different [cache algorithms](https://en.wikipedia.org/wiki/Cache_algorithms) by implementing the `leinonen\DataLoader\CacheMapInterface`.
13111

112+
### Usage with common ORM's
14113

15-
## Todo
16-
- [x] Primary API
17-
- [x] Error / rejected promise handling
18-
- [x] Options support
19-
- [x] Abuse tests and meaningful exceptions
20-
- [ ] Documentation for the API and usage examples
21-
- [ ] Abstract event loop and promises to be usable with any implementation?
114+
#### Eloquent (Laravel)
115+
116+
```php
117+
$userByIdLoader = new DataLoader(function ($ids) {
118+
$users = User::findMany($ids);
119+
120+
// Make sure that the users are on the same order as the given ids for the loader
121+
$orderedUsers = collect($ids)->map(function ($id) use ($users) {
122+
return $users->first(function ($user) use ($id) {
123+
return $user->id === $id;
124+
});
125+
});
126+
127+
return \React\Promise\resolve($orderedUsers);
128+
}, $eventLoopFromIoCContainer, $cacheMapFromIoCContainer);
129+
```
130+
131+
#### ActiveRecord (Yii2)
132+
```php
133+
$usersByIdLoader = new DataLoader(function ($ids) {
134+
$users = User::find()->where(['id' => $ids])->all();
135+
136+
$orderedUsers = \array_map(function ($id) use ($users) {
137+
foreach ($users as $user) {
138+
if ($user->id === $id) {
139+
return $user;
140+
}
141+
}
142+
143+
return null;
144+
}, $ids);
145+
146+
return \React\Promise\resolve($orderedUsers);
147+
}, $eventLoopFromDiContainer, $cacheMapImplementationFromDiContainer);
148+
```
22149

23150
## API
24151

@@ -34,7 +161,7 @@ Loads multiple keys, promising an array of values.
34161

35162
This is equivalent to the more verbose:
36163

37-
```js
164+
```php
38165
$promises = \React\Promise\all([
39166
$myLoader->load('a'),
40167
$myLoader->load('b')
@@ -60,23 +187,5 @@ Primes the cache with the provided key and value. If the key already exists, no
60187
change is made. (To forcefully prime the cache, clear the key first with
61188
`$loader->clear($key)->prime($key, $value)`. Returns itself for method chaining.
62189

63-
## Usage with common ORM's
64-
65-
### Eloquent (Laravel)
66-
67-
```php
68-
$userByIdLoader = new DataLoader(function ($ids) {
69-
$users = User::findMany($ids);
70-
71-
// Make sure that the users are on the same order as the given ids for the loader
72-
$orderedUsers = collect($ids)->map(function ($id) use ($users) {
73-
return $users->first(function ($user) use ($id) {
74-
return $user->id === $id;
75-
});
76-
});
77-
78-
return \React\Promise\resolve($orderedUsers);
79-
}, $eventLoopFromIoCContainer, $cacheMapFromIoCContainer);
80-
```
81190

82191

src/DataLoader.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function __construct(
5050
$this->batchLoadFunction = $batchLoadFunction;
5151
$this->eventLoop = $loop;
5252
$this->promiseCache = $cacheMap;
53-
$this->options = empty($options) ? new DataLoaderOptions() : $options;
53+
$this->options = $options === null ? new DataLoaderOptions() : $options;
5454
}
5555

5656
/**
@@ -147,7 +147,7 @@ public function prime($key, $value)
147147
private function scheduleDispatch()
148148
{
149149
if ($this->options->shouldBatch()) {
150-
$this->eventLoop->nextTick(
150+
$this->eventLoop->futureTick(
151151
function () {
152152
$this->dispatchQueue();
153153
}
@@ -264,6 +264,8 @@ private function handleFailedDispatch(array $batch, \Exception $error)
264264
*
265265
* @param array $values Values from resolved promise.
266266
* @param array $keys Keys which the DataLoaders load was called with
267+
*
268+
* @throws DataLoaderException
267269
*/
268270
private function validateBatchPromiseOutput($values, $keys)
269271
{

0 commit comments

Comments
 (0)