Comments (14)
@lindyhopchris haven't had a chance to check out the branch yet. I ended up implementing my own quick-and-dirty handling for pagination through the use of a parent controller, a custom method for building the pagination links and using the pagination object to get at the array of models. I'm not 100% sure I'm keen on the custom Paginator, I think I'd much rather see the encoder handle the Eloquent paginator and take that as an indicator to automatically append Link & Meta data to anything passes in the content() call. If it didn't automatically append the Link & Meta data by default then I'd would go with a protected parameter that could be set to indicate whether that should happen.
from laravel-json-api.
Hi! The neomerx/json-api
package that this package is built on top tries to cast several of the Laravel database builder results to objects for encoding, rather than iterating over them. The paginator one is an example.
Luckily the solution is easy - just convert the LengthAwarePaginator
to an array before passing it out of the controller to the reply encoding.
from laravel-json-api.
Awesome thanks!
from laravel-json-api.
Note to self... for the future change this line:
So that it also does casting of Laravel builder objects.
from laravel-json-api.
Just saw your note to self above, that helped me massively - until you are able to implement, I've had to temporarily extend the ResponsesHelper to add in use Illuminate\Database\Eloquent\Collection as EloquentCollection;
and check for that at if ($data instanceof Collection || $data instanceof EloquentCollection)
I might be doing something wrong here as well, but I've found I need to do the same check on the ->relationship()
method. I pass in an Eloquent Collection and it triggers the same Schema not registered for.. error.
from laravel-json-api.
Ah, great thanks for letting me know about the relationship method as well - will fix that at the same time.
Not sure when I'll be able to get round to doing the fix as I have a massive deadline on my current project at the beginning of Feb. I'll try to get to it soon, but if not I'll fix it very early Feb.
from laravel-json-api.
Good luck on the deadline!
Just on the pagination note, you might end up implementing it a completely different way but this is what I ended up doing to handle the response so far which might help anyone looking in the short term.
Looking at the flow it seems like it would be might be cleaner to check for LengthAwarePaginator in the ResponsesHelper and append the links and meta there if it detects that it needs it? Or maybe even a separate method like paginatedContent()
?
Additionally, in my project I eventually want to spin off the validation etc into a quasi-traditional Laravel way, where I have a separate XyzRequest.php
file for each endpoint in the App\Http\Requests
namespace and typehint it into the controller, rather than doing the validation in the controller.
Pagination
- Creating a new Pagination Service Provider
<?php
namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
class JsonApiPaginationServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public
function register()
{
Paginator::currentPathResolver(function ()
{
return $this->app['request']->url();
});
Paginator::currentPageResolver(function ()
{
$page = $this->app['request']->input('page.number');
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1)
{
return $page;
}
return 1;
});
}
}
- Overriding the default Pagination Service provider in
config/app.php
'providers' => [
/*
* Laravel Framework Service Providers...
*/
...
Illuminate\Mail\MailServiceProvider::class,
//Illuminate\Pagination\PaginationServiceProvider::class, (commented this out)
Illuminate\Pipeline\PipelineServiceProvider::class,
...
/*
* JSONAPI Service Providers (Must be added before Route Service Provider)
*/
CloudCreativity\JsonApi\ServiceProvider::class,
App\Providers\JsonApiPaginationServiceProvider::class, // (added this)
- Extending the JsonApiController (my app controllers extend this..)
<?php
namespace App\Http\Controllers;
use CloudCreativity\JsonApi\Http\Controllers\JsonApiController;
use Neomerx\JsonApi\Schema\Link;
abstract
class ApiController extends JsonApiController
{
protected $allowedPagingParameters = ['number', 'size'];
protected
function getPaginationParameters()
{
$params = $this->getParameters()->getPaginationParameters();
return [
'size' => isset($params['size']) ? intval($params['size']) : 15,
'number' => isset($params['number']) ? intval($params['number']) : 1
];
}
protected
function generatePaginationLinks($resource, $url, $appends = [])
{
$page = $this->getPaginationParameters();
$append = "";
foreach($appends as $key => $value) {
$append .= "&" . $key . "=" . $value;
}
$links = [
Link::FIRST => new Link(sprintf('/%s?page[number]=%s&page[size]=%s%s', $url, 1, $page['size'], $append)),
Link::NEXT => new Link(sprintf('/%s?page[number]=%s&page[size]=%s%s', $url, $page['number'] < $resource->lastPage() ? $page['number'] + 1 : $page['number'], $page['size'], $append)),
Link::PREV => new Link(sprintf('/%s?page[number]=%s&page[size]=%s%s', $url, $page['number'] > 1 ? $page['number'] - 1 : $page['number'], $page['size'], $append)),
Link::LAST => new Link(sprintf('/%s?page[number]=%s&page[size]=%s%s', $url, $resource->lastPage(), $page['size'], $append))
];
return $links;
}
protected
function readRelated($resource, $relationshipName)
{
if (!$resource)
{
$this->notFound();
}
$resource->load($relationshipName);
return $this
->reply()
->content($resource->{$relationshipName});
}
protected
function readRelatedRelationship($resource, $relationshipName)
{
if (!$resource)
{
$this->notFound();
}
$resource->load($relationshipName);
return $this
->reply()
->relationship($resource, $relationshipName, $resource->{$relationshipName});
}
}
- Adding a check in the ResponsesHelper (hate doing this, way too hacky but I figure it's just temporary)
<?php
namespace CloudCreativity\JsonApi\Http\Responses;
...
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection as EloquentCollection; // Added this
use Illuminate\Pagination\LengthAwarePaginator; // Added this
....
public
function content($data, array $links = [], $meta = null, $statusCode = Response::HTTP_OK, array $headers = [])
{
/** Eloquent collections do not encode properly, so we'll get all just in case it's an Eloquent collection */
if ($data instanceof Collection || $data instanceof EloquentCollection || $data instanceof LengthAwarePaginator) // Added EloquentCollection and LengthAwarePaginator
{
$data = $data->all();
}
$content = $this
->getEncoder()
->withLinks($links)
->withMeta($meta)
->encodeData($data, $this->environment->getParameters());
return $this->respond($statusCode, $content, $headers);
}
- Then in an an app controller I need paginated
<?php
namespace App\Http\Controllers\Contact;
use App\Http\Controllers\ApiController;
use App\Http\Requests\Contact\ContactRequest;
use App\Models\Contact\Contact;
class ContactController extends ApiController
{
/*
* Getters
*/
public
function index()
{
$page = $this->getPaginationParameters();
$contacts = Contact::with('codes', 'status')->paginate($page['size']);
if (!$contacts)
{
$this->notFound();
}
$links = $this->generatePaginationLinks($contacts);
$meta = [
'total_pages' => $contacts->lastPage()
];
return $this
->reply()
->content($contacts, $links, $meta);
}
from laravel-json-api.
@samsebastian the changes you made to ResponsesHelper was that done via some sort of extends or did you actually change the code in the vendors
source for laravel-json-api? I need to get this working now in my own project and would like to use your unofficial fix for now. I guess worst case would be that I would just fork this repository and add the features in my own fork and add my fork to my composer.json instead of the real package until it's been added.
from laravel-json-api.
I've got time to look at this Mon-Weds next week, and can see what I might add to the laravel-json-api
repository. I'm not sure I'd bring all of this code in this way - need to review it.
What features from it are you planning to use?
from laravel-json-api.
The biggest piece that I need is the ability to use the Laravel pagination results in my JSON API response so that my client can request additional pages. I don't need any Request magic as I can handle that via an extended controller.
from laravel-json-api.
The above were really just quick hacks to test out what I ended up implementing, I had to dig way further into a lot of things such as ->getResourceObjectValidator()
(inside the vendor folder) to deal with pagination and relationships that were failing validation, and creating a custom ApiRequest.php
like class ApiRequest extends Request implements ValidatesWhenResolved
to try and quasi-replicate the traditional Laravel request functionality (ie typehint into the controller class and auto validate, access methods like $request->all()
, $request->input('foo')
etc, plus because the JSONAPI packets come in with relationships separate, I need to automatically pickup 'hasOnerelationships and convert them to a new field
foo_idso I can do things like
Bar::create($request->all())` which I wanted to still be able to use.).
As an example (not the full ApiRequest
class, just a snippet, the validate() method is triggered by the ValidatesWhenResolved
)
public
function validate()
{
if (!$this->authorize())
{
throw new HttpResponseException(new JsonResponse('Authorization Exception here', 401));
}
if ($this->isMethod('post') || $this->isMethod('patch') || $this->isMethod('put'))
{
if ($this->rules() || $this->relationships())
{
$validator = $this
->getResourceObjectValidator(
// the expected resource type
$this->resourceType,
// the expected id (use null for new resources)
$this->id(),
// the rules for the attributes - uses the Laravel validation implementation.
$this->rules(),
// Laravel validation messages, if you need to customise them
$this->validationMessages(),
// whether the attributes member must be present in the resource object
$this->required(),
// the rules for expected relationships
$this->relationships()
);
}
else
{
$validator = null;
}
try
{
$object = $this->getResourceObject($validator);
}
catch (MultiErrorException $e)
{
throw new HttpResponseException(new JsonResponse('Body Content Exception here', 401));
}
$this->resource = $object;
if ($object->hasRelationships() && $this->relationships())
{
foreach ($this->relationships() as $name => $type)
{
$relationship = $object->getRelationships()->get($name)->getData();
if (!empty($relationship))
{
if ($type == "hasOne")
{
if ($relationship->hasId())
{
$relationshipMap = $this->relationshipMap();
if (array_key_exists($name, $relationshipMap))
{
$name = $relationshipMap[ $name ];
}
$this->resource->attributes->{strtolower($name) . '_id'} = $relationship->getId();
$this->hasOneRelationships[ $name ] = $relationship->getId();
}
}
elseif ($type == "hasMany")
{
foreach ($relationship as $member)
{
$this->hasManyRelationships[ $member->type ][] = $member->id;
}
}
}
}
}
// Strip any meta data we don't want to mass assign
$metas = ['created_at', 'updated_at', 'deleted_at'];
foreach ($metas as $meta)
{
unset($this->resource->attributes->{$meta});
}
$this->sanitize();
}
else
{
//var_dump($this->method());
}
// Should automatically do an Exception if wrong
if ($this->isMethod('get')) {
$this->filters = $this->getFilteringParameters();
}
}
And then for requests I know will be paged:
<?php namespace App\Http\Requests;
abstract
class PagedApiRequest extends ApiRequest
{
protected $allowedPagingParameters = ['number', 'size'];
protected $page;
protected $defaultPageSize = 15;
protected
function getPaginationParameters()
{
$params = $this->getParameters()->getPaginationParameters();
return [
'size' => isset($params['size']) ? intval($params['size']) : $this->defaultPageSize,
'number' => isset($params['number']) ? intval($params['number']) : 1
];
}
public
function page($key = null)
{
if ($key)
{
return isset($this->page[ $key ]) ? $this->page[ $key ] : null;
}
return $this->page;
}
public
function validate()
{
parent::validate();
$this->page = $this->getPaginationParameters();
}
}
And then finally extending to the specific Request, in this case a "Note" request..
<?php
namespace App\Http\Requests\Note;
use App\Http\Requests\PagedApiRequest;
class NoteRequest extends PagedApiRequest
{
protected $resourceType = 'notes';
protected $allowedFilteringParameters = ['contact_id', 'user_id'];
public
function rules()
{
return [
'content' => 'string'
];
}
public
function relationships()
{
return [
'contact' => 'hasOne',
'user' => 'hasOne',
'note-type' => 'hasOne',
];
}
public
function authorize()
{
return true;
}
}
One thing I did have to change in the vendor folder was this src\Validator\Resource\IlluminateResourceValidator.php
I had no idea what I was doing here but I had to change as the relationships kept crashing when I validated. (Relationship definitions come from the extended ApiRequest class)
public
function __construct(
$expectedType,
$expectedId = null,
array $attributesRules = [],
array $attributesValidationMessages = [],
$attributesRequired = true,
array $relationshipsRules = [],
$relationshipsRequired = false
)
{
$this->expectedType = $expectedType;
$this->expectedId = $expectedId;
$this->attributes =
new RulesValidator($attributesRules, $attributesValidationMessages, $attributesRequired);
$relationshipsValidators = [];
foreach ($relationshipsRules as $relationship => $type)
{
switch ($type)
{
case "hasOne":
$relationshipsValidators[ $relationship ] = new HasOneValidator(str_plural($relationship));
break;
case "hasMany":
default:
$relationshipsValidators[ $relationship ] = new HasManyValidator($relationship);
break;;
}
}
$this->relationships = new RelationshipsValidator($relationshipsValidators);
}
I wouldn't suggest looking at the stuff above (or even anything I ended up stitching together) as anything other than a potential starting point, because I was in a rush and trying to rapidly figure out what was going on within this package and two levels down in the dependencies, so I'm expecting it to just get me through until a real solution is released.
from laravel-json-api.
Hi everyone!
I have a working version of pagination on the feature/pagination
branch.
In your controller, apply the following trait:
CloudCreativity\JsonApi\Http\Controllers\PaginatedTrait
Then in your controller method you can do:
/** @var CloudCreativity\JsonApi\Pagination\Paginator */
$paginator = $this->getPaginator();
$query = Comment::paginate($paginated->getPerPage(25, 25));
return $this
->reply()
->content(
$query,
$paginator->getLinks($query),
$paginator->getMeta($query)
);
The syntax of the getPerPage()
function is:
getPerPage($default = 15, $max = null)
I.e. a default if the client hasn't provided a page[number]
parameter. The $max
is if you want to limit the maximum they can request per page (use null
to indicate no limit).
I've used page[number]
and page[size]
as the two query params. I've intentionally used single words because different applications may adopt different policies on whether they are e.g. hyphenating or underscoring their keys.
The getPaginator()
method from the PaginatedTrait
allows you to override what the keys are.
The slight complication is on constructing the links that should be returned from the Paginator::getLinks()
method. What I've done is created a links class that reconstructs the url from the page, filter and sort parameters received from the client - as my view was that those three are going to affect what you're paging over.
I'd be interested on what people think about this and the feature/pagination
branch. I've intentionally not merged it yet because feedback would be good. Obviously let me know if you have any questions!
from laravel-json-api.
@timnolte thanks, I think that sounds like a better approach. I'll have another go at re-factoring this.
from laravel-json-api.
Closing this as the v0.4
has fully support for pagination. Pagination meta and links are automatically created if you return a Laravel paginator result.
from laravel-json-api.
Related Issues (20)
- Unable to create json api resource with attribute name "type" HOT 3
- Self-relation doesn't include model fields HOT 5
- Arguement 3 passed to controller must be an instance of model, string given
- Support for multiple operations in a single request HOT 2
- use Errors Handling in Laravel 8 HOT 1
- Why does json-api trimming spaces in my attributes when updating? HOT 3
- filter slug and underscore HOT 1
- Multiple primary keys - Mysql Error HOT 1
- Sparse fieldsets not working with included relationships HOT 3
- How to attach a middleware to a specific resource relationship? HOT 3
- How I can filter included resources by their fields HOT 8
- Migrate from cloudcreativity/laravel-json-api to laravel-json-api/laravel HOT 15
- How to stop this library from making asterix queries? HOT 1
- Make a request to one endpoint from another controller HOT 5
- Insert on duplicate key upd when inserting a record? HOT 6
- Handle HasMany relationship with extra field. HOT 2
- [5.x] Missing meta HOT 8
- bug: service provider boot throws "No application encryption key has been specified." HOT 1
- Error in tests HOT 1
- getResourceLinks function HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from laravel-json-api.