Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP v2 #5

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fcc7f6f
Work in progress on abstracting include logic
zackify Feb 12, 2017
530aa87
Remove resource keys if not paginated, allow non callbacks for items
zackify Feb 12, 2017
4d4d689
use passed model instance for eagerload
zackify Feb 12, 2017
dc5d432
Add Resource Template
zackify Feb 13, 2017
9330ac0
Spelling
zackify Feb 13, 2017
13184f1
Add doc block
zackify Feb 13, 2017
ab3a3b1
Set transformer and model, add traits
zackify Feb 22, 2017
ed7654b
Updated other methods
zackify Feb 22, 2017
20e1617
Remove resource key if no pagination
zackify Feb 22, 2017
5340866
Remove controller base
zackify Feb 22, 2017
0d124e3
v2 readme
Feb 27, 2017
3b851f1
respond with item change
Feb 27, 2017
842eb06
Add policy support
zackify Mar 6, 2017
c9bd96a
Make should authorize a public property
zackify Mar 6, 2017
d1bfd85
Specifying return types.
joshforbes Mar 6, 2017
b2e7d47
The resource key needs to be null by default so that it falls through…
joshforbes Mar 6, 2017
578318a
Fixing default assignment.
joshforbes Mar 6, 2017
0bf2dd2
Cleaning up formatting.
joshforbes Mar 6, 2017
17e9940
Removing unused methods.
joshforbes Mar 6, 2017
0ae12cc
I think I like the name ‘withIncludes’ better than prepareBuilder.. a…
joshforbes Mar 6, 2017
345825d
Updating test to match new structure.
joshforbes Mar 6, 2017
97ee391
Try no key for now? ;)
zackify Mar 6, 2017
ba4c492
If the collection passed in is a builder - then grab the available in…
joshforbes Mar 12, 2017
27c7a74
Rollback the resource key changes for now.
joshforbes Mar 12, 2017
a6beb0e
Make sure the setup method takes the proper default argument.
joshforbes Mar 12, 2017
ebcd0c9
Cleanup use statements.
joshforbes Mar 12, 2017
c5fa0e7
Lets use the default serializer for this test.
joshforbes Mar 12, 2017
00cd4cc
Fixing incorrect namespace.
joshforbes Mar 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ composer.lock
docs
vendor

.idea
.idea
.DS_Store
194 changes: 55 additions & 139 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,54 @@ The package has a publishable config file that allows you to chang the default s
php artisan vendor:publish --provider="NavJobs\Transmit\TransmitServiceProvider"
```

#### Full-featured Resource Routes
Transmit provides resource traits that will handle resource methods for you. If you plan on doing more than what is defined in them, ([see them here](/src/Traits)) simply don't import the Trait and respond with an item, collection, etc when you are finished.

```php
<?php

namespace App\Book\Http\Books\Controllers;

use Exception;
use Illuminate\Http\Request;
use App\Book\Domain\Books\Entities\Book;
use App\Book\Transformers\BookTransformer;

use NavJobs\Transmit\Controller;
use NavJobs\Transmit\Traits\{ Index, Show, Update, Store, Delete };

class BookController extends Controller
{
use Index, Show, Update, Store, Delete;

//this is optional and will call authorize methods on default traits

public $shouldAuthorize = true;

/**
* @param Book $bookModel
* @param BookTransformer $transformer
*/
public function __construct(Book $bookModel, BookTransformer $transformer)
{
parent::__construct();

$this
->setTransformer($transformer)
->setModel($bookModel);
}
}
```



#### Api
Transmit provides an abstract controller class that you should extend from:

```php
use NavJobs\Transmit\Controller as ApiController;
use NavJobs\Transmit\Controller;

class BookController extends ApiController
class BookController extends Controller
{
...
```
Expand All @@ -43,16 +84,23 @@ The controller class provides a number of methods that make API responses easy:

```php
//Return the specified item, transformed
$this->respondWithItem($item, $optionalTransformer);
$this->respondWithItem($item);

//or pass a callback and model instance, this way includes will be parsed

return $this->respondWithItem($this->model, function ($model) use ($id) {
return $model->findOrFail($id);
});


//Sets the status code to 201 and return the specified item, transformed
$this->respondWithItemCreated($item, $optionalTransformer);
$this->respondWithItemCreated($item, $cb = null);

//Return the specified collection, transformed
$this->respondWithCollection($collection, $optionalTransformer);
$this->respondWithCollection($collection);

//Paginate the specified collection
$this->respondWithPaginatedCollection($collection, $optionalTransformer, $perPage = 10);
$this->respondWithPaginatedCollection($collection, $perPage = 10);

//Set the status code to 204, and return no content
$this->respondWithNoContent();
Expand Down Expand Up @@ -97,141 +145,9 @@ class BookTransformer extends BaseTransformer
...
```

The transformer allows you to easily determine which relationships should be allowed to be eager loaded. This is determined by matching the requested includes against the available and default includes.

```php
//Pass in either an array or csv string
//Returns an array of includes that should be eager loaded
$this->getEagerLoads($requestedIncludes);
```

#### Implementation Example
These methods can be combined to quickly create expressive api controllers. The following is an example of what that implementation might look like:

```php
<?php

namespace App\Book\Http\Books\Controllers;

use Exception;
use Illuminate\Http\Request;
use App\Book\Domain\Books\Entities\Book;
use App\Book\Transformers\BookTransformer;
use App\Book\Http\Books\Requests\BookRequest;
use NavJobs\Transmit\Controller as ApiController;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class BookController extends ApiController
{
protected $bookModel;
protected $transformer;
protected $fractal;

/**
* @param Book $bookModel
* @param BookTransformer $transformer
*/
public function __construct(Book $bookModel, BookTransformer $transformer)
{
parent::__construct();

$this->transformer = $transformer;
$this->bookModel = $bookModel;
}

/**
* Show a list of Books.
*
* @param Request $request
* @return mixed
*/
public function index(Request $request)
{
$includes = $this->transformer->getEagerLoads($this->fractal->getRequestedIncludes());
$books = $this->eagerLoadIncludes($this->bookModel, $includes);
$books = $this->applyParameters($books, $request->query);

return $this->respondWithPaginatedCollection($books, $this->transformer);
}

/**
* Show a book by the specified id.
*
* @param $bookId
* @return mixed
*/
public function show($bookId)
{
try {
$includes = $this->transformer->getEagerLoads($this->fractal->getRequestedIncludes());
$books = $this->eagerLoadIncludes($this->bookModel, $includes);
The transformer allows you to easily determine which relationships should be allowed to be eager loaded. This is determined by matching the requested includes against the available and default includes. Transmit does this in the background.

$book = $books->findOrFail($bookId);
} catch (ModelNotFoundException $e) {
return $this->errorNotFound();
}

return $this->respondWithItem($book, $this->transformer);
}

/**
* Handle the request to persist a Book.
*
* @param bookRequest $request
* @return array
*/
public function store(BookRequest $request)
{
$book = $this->bookModel->create($request->all());

return $this->respondWithItemCreated($book, $this->transformer);
}


/**
* Handle the request to update a Book.
*
* @param BookRequest $request
* @param $bookId
* @return mixed
*/
public function update(BookRequest $request, $bookId)
{
try {
$book = $this->bookModel->findOrFail($bookId);
} catch (ModelNotFoundException $e) {
return $this->errorNotFound();
}

$book->update($request->all());

return $this->respondWithNoContent();
}

/**
* Handle the request to delete a Book.
*
* @param $bookId
* @return mixed
*/
public function destroy($bookId)
{
try {
$book = $this->bookModel->findOrFail($bookId);
} catch (ModelNotFoundException $e) {
return $this->errorNotFound();
}

try {
$book->delete();
} catch (Exception $e) {
return $this->errorInternalError();
}

return $this->respondWithNoContent();
}
}
```

#### Usage
This implementation allows endpoints to take includes as well as query string parameters. To apply parameters to the current resource:
Expand Down
89 changes: 70 additions & 19 deletions src/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

namespace NavJobs\Transmit;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Input;
use NavJobs\Transmit\Traits\ErrorResponsesTrait;
use Illuminate\Database\Eloquent\Builder;
use NavJobs\Transmit\Traits\QueryHelperTrait;
use NavJobs\Transmit\Traits\ErrorResponsesTrait;
use Illuminate\Routing\Controller as BaseController;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;

Expand All @@ -15,15 +14,42 @@ abstract class Controller extends BaseController
use QueryHelperTrait, ErrorResponsesTrait;

protected $statusCode = 200;
protected $fractal;
protected $resourceKey = null;
protected $fractal, $transformer;

public function __construct()
{
$this->fractal = App::make(Fractal::class);
$this->fractal = app(Fractal::class);

$this->parseIncludes();
}

/**
* Sets the fractal transformer
*
* @param $transformer
* @return mixed
*/
public function setTransformer($transformer)
{
$this->transformer = $transformer;

return $this;
}

/**
* Sets resource key for fractal
*
* @param $resourceKey
* @return mixed
*/
public function setResourceKey($resourceKey)
{
$this->resourceKey = $resourceKey;

return $this;
}

/**
* Parses includes from either the header or query string.
*
Expand Down Expand Up @@ -65,18 +91,37 @@ protected function setStatusCode($statusCode)
return $this;
}

/**
* Eager load any available includes and apply query parameters.
*
* @param $builder
* @return mixed
*/
protected function withIncludes($builder)
{
$includes = $this->transformer->getEagerLoads($this->fractal->getRequestedIncludes());
$includedItems = $this->eagerLoadIncludes($builder, $includes);
$this->applyParameters($includedItems, request()->query);

return $builder;
}

/**
* Returns a json response that contains the specified resource
* passed through fractal and optionally a transformer.
*
* @param $item
* @param null $callback
* @param null $resourceKey
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithItem($item, $callback = null, $resourceKey = null)
protected function respondWithItem($item, $callback = null)
{
$rootScope = $this->fractal->item($item, $callback, $resourceKey);
if ($callback) {
$builder = $this->withIncludes($item);
$item = $callback($builder);
}

$rootScope = $this->fractal->item($item, $this->transformer, $this->resourceKey);

return $this->respondWithArray($rootScope->toArray());
}
Expand All @@ -87,13 +132,17 @@ protected function respondWithItem($item, $callback = null, $resourceKey = null)
*
* @param $item
* @param null $callback
* @param null $resourceKey
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithItemCreated($item, $callback = null, $resourceKey = null)
protected function respondWithItemCreated($item, $callback = null)
{
if ($callback) {
$builder = $this->withIncludes($item);
$item = $callback($builder);
}

$this->setStatusCode(201);
$rootScope = $this->fractal->item($item, $callback, $resourceKey);
$rootScope = $this->fractal->item($item, $this->transformer, $this->resourceKey);

return $this->respondWithArray($rootScope->toArray());
}
Expand All @@ -103,13 +152,15 @@ protected function respondWithItemCreated($item, $callback = null, $resourceKey
* passed through fractal and optionally a transformer.
*
* @param $collection
* @param $callback
* @param null $resourceKey
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithCollection($collection, $callback, $resourceKey = null)
protected function respondWithCollection($collection)
{
$rootScope = $this->fractal->collection($collection, $callback, $resourceKey);
if (is_a($collection, Builder::class)) {
$collection = $this->withIncludes($collection);
}

$rootScope = $this->fractal->collection($collection, $this->transformer, $this->resourceKey);

return $this->respondWithArray($rootScope->toArray());
}
Expand All @@ -119,18 +170,18 @@ protected function respondWithCollection($collection, $callback, $resourceKey =
* passed through fractal and optionally a transformer.
*
* @param $builder
* @param $callback
* @param int $perPage
* @param null $resourceKey
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithPaginatedCollection($builder, $callback, $perPage = 10, $resourceKey = null)
protected function respondWithPaginatedCollection($builder, $perPage = 10)
{
$builder = $this->withIncludes($builder);

$paginator = $builder->paginate($perPage);
$paginator->appends($this->getQueryParameters());

$rootScope = $this->fractal
->collection($paginator->getCollection(), $callback, $resourceKey)
->collection($paginator->getCollection(), $this->transformer, $this->resourceKey)
->paginateWith(new IlluminatePaginatorAdapter($paginator));

return $this->respondWithArray($rootScope->toArray());
Expand Down
Loading