Skip to content

Commit

Permalink
Merge pull request #17 from ipalaus/feature/cursors-docs
Browse files Browse the repository at this point in the history
How to use cursors with Fractal
  • Loading branch information
Phil Sturgeon committed Jan 24, 2014
2 parents 6b3feec + 407af48 commit 694d954
Showing 1 changed file with 57 additions and 26 deletions.
83 changes: 57 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
[![Total Downloads](https://poser.pugx.org/league/fractal/downloads.png)](https://packagist.org/packages/league/fractal)
[![Latest Stable Version](https://poser.pugx.org/league/fractal/v/stable.png)](https://packagist.org/packages/league/fractal)

When building an API it is common for people to just grab stuff from the database and pass it
to `json_encode()`. This might be passable for "trivial" API's but if they are in use by the public,
When building an API it is common for people to just grab stuff from the database and pass it
to `json_encode()`. This might be passable for "trivial" API's but if they are in use by the public,
or used by an iPhone application then this will quickly lead to inconsistent output.

This package aims to do a few simple things:
Expand Down Expand Up @@ -54,13 +54,13 @@ In your controllers you can then create "resources", of which there are three ty
* **League\Fractal\Resource\Item** - A singular resource, probably one entry in a data store
* **League\Fractal\Resource\Collection** - A collection of resources

The `Item` and `Collection` constructors will take any kind of data you wish to send it
as the first argument, and then a "transformer" as the second argument. This can be callable or a string
containing a fully-qualified class name.
The `Item` and `Collection` constructors will take any kind of data you wish to send it
as the first argument, and then a "transformer" as the second argument. This can be callable or a string
containing a fully-qualified class name.

The transformer will the raw data passed back into it, so if you pass an instance of `BookModel` into an
`ItemResource` then you can expect this instance to be `BookModel`. If you passed an array or a collection
(an object implementing [ArrayIterator][]) of `BookModel` instances then this transform method will be run
The transformer will the raw data passed back into it, so if you pass an instance of `BookModel` into an
`ItemResource` then you can expect this instance to be `BookModel`. If you passed an array or a collection
(an object implementing [ArrayIterator][]) of `BookModel` instances then this transform method will be run
on each of those instances.

``` php
Expand All @@ -76,7 +76,7 @@ $resource = new Fractal\Resource\Collection($books, function(BookModel $book) {
```

If you want to reuse your transformers (recommended) then create classes somewhere and pass in the name.
Assuming you use an autoloader of course. These classes must extend `League\Fractal\TransformerAbstract` and
Assuming you use an autoloader of course. These classes must extend `League\Fractal\TransformerAbstract` and
contain a transform method, much like the callback example: `public function transform(Foo $foo)`.

``` php
Expand All @@ -89,11 +89,11 @@ $resource = new Fractal\Resource\Collection($books, new BookTransformer);

### Embedding Data

Your transformer at this point is mainly just giving you a method to handle array conversion from
you data source (or whatever your model is returning) to a simple array. Embedding data in an
intelligent way can be tricky as data can have all sorts of relationships. Many developers try to
find a perfect balance between not making too many HTTP requests and not downloading more data than
they need to, so flexibility is also important.
Your transformer at this point is mainly just giving you a method to handle array conversion from
you data source (or whatever your model is returning) to a simple array. Embedding data in an
intelligent way can be tricky as data can have all sorts of relationships. Many developers try to
find a perfect balance between not making too many HTTP requests and not downloading more data than
they need to, so flexibility is also important.

Sticking with the book example, the `BookTransformer` might contain an optional embed for an author.

Expand Down Expand Up @@ -142,19 +142,19 @@ class BookTransformer extends TransformerAbstract
}
```

So if a client application were to call the URL `/books?embed=author` then they would see author data in the
response. These can be nested with dot notation, as far as you like.
So if a client application were to call the URL `/books?embed=author` then they would see author data in the
response. These can be nested with dot notation, as far as you like.

**E.g:** `/books?embed=author,publishers,publishers.somethingelse`

This example happens to be using the lazy-loading functionality of an ORM for `$book->author`, but there is no
reason that eager-loading could not also be used by inspecting the `$_GET['embed']` list of requested scopes. This
This example happens to be using the lazy-loading functionality of an ORM for `$book->author`, but there is no
reason that eager-loading could not also be used by inspecting the `$_GET['embed']` list of requested scopes. This
would just be a translation array, turning scopes into eager-loading requirements.

### Outputting Processed Data

When ready to output this data, you must convert the "resource" back into data. Calling
`$fractal->createData();` with a resource argument will run the transformers (any any
When ready to output this data, you must convert the "resource" back into data. Calling
`$fractal->createData();` with a resource argument will run the transformers (any any
nested transformer calls) and convert everything to an array for you to output:

``` php
Expand All @@ -165,26 +165,26 @@ $data = $fractal->createData($resource)->toArray();
$json = $fractal->createData($resource)->toJson();
```

If you want to use something other than JSON then you'll need to think that one up yourself. If
you're using horribly complicated XML for example, then you will probably need to create some
If you want to use something other than JSON then you'll need to think that one up yourself. If
you're using horribly complicated XML for example, then you will probably need to create some
specific view files, which negates the purpose of using this system entirely. Auto-generated XML,
YAML or anything similar could easily be set up in a switch, just check against the `Accept` header.

### Pagination

When working with a large data set it obviously makes sense to offer pagination options to the endpoint,
When working with a large data set it obviously makes sense to offer pagination options to the endpoint,
otherwise that data can get very slow. To avoid writing your own pagination output into every endpoint you
can utilize the the `League\Fractal\Resource\Collection::setPaginator()` method.

The paginator passed to `setPaginator()` must implement `League\Fractal\Pagination\PaginatorInterface`
The paginator passed to `setPaginator()` must implement `League\Fractal\Pagination\PaginatorInterface`
and it's specified methods.

Fractal currently only ships with an adapter for Laravel's `illuminate/pagination` package as
Fractal currently only ships with an adapter for Laravel's `illuminate/pagination` package as
`League\Fractal\Pagination\IlluminatePaginatorAdapter`.

[Laravel Pagination]: http://laravel.com/docs/pagination

Inside of Laravel 4, using the Eloquent or Query Builder method `paginate()`, the following syntax is
Inside of Laravel 4, using the Eloquent or Query Builder method `paginate()`, the following syntax is
possible:

``` php
Expand All @@ -202,6 +202,37 @@ $resource->setPaginator(new IlluminatePaginatorAdapter($paginator));

[ArrayIterator]: http://php.net/ArrayIterator

### Navigate collections with cursors

When we have large sets of data and running a `SELECT COUNT(*) FROM whatever` isn't really an option, we need a proper
way of fetching results. One of the approches is to use cursors that will indicate to your backend where to start
fetching results. You can set a new cursor on your collections using the
`League\Fractal\Resource\Collection::setCursor()` method.

The cursor must implement `League\Fractal\Cursor\CursorInterface` and it's specified methods.

Fractal currently ships with a very basic adapter, `League\Fractal\Cursor\Cursor`. It's really easy to use:

```php
use Acme\Model\Book;
use Acme\Transformer\BookTransformer;
use League\Fractal\Cursor\Cursor;
use League\Fractal\Resource\Collection;

$books = new Book;

if ($current = Input::get('cursor', false)) {
$books = $books->where('id', '>', $current);
}

$books = $books->take(5)->get();

$cursor = new Cursor($current, $books->last()->id, $books->count());

$resource = new Collection($books, new BookTransformer);
$resource->setCursor($cursor);
```

## TODO

This is still in concept stage, and these issues are left to explore:
Expand Down

0 comments on commit 694d954

Please sign in to comment.