diff --git a/README.md b/README.md index 953ba3e6..c637d746 100755 --- a/README.md +++ b/README.md @@ -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: @@ -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 @@ -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 @@ -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. @@ -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 @@ -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 @@ -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: