neomerx / json-api Goto Github PK
View Code? Open in Web Editor NEWFramework agnostic JSON API (jsonapi.org) implementation
License: Apache License 2.0
Framework agnostic JSON API (jsonapi.org) implementation
License: Apache License 2.0
Is it possible to have a 'selfSubUrl' such as /users/{id}/followers.
Apologies if I missed it in the docs.
Servers MUST respond with a 406 Not Acceptable status code if a request's Accept header contains the JSON API media type and all instances of that media type are modified with media type parameters.
Hi,
There are some use cases where the resource type that is defined with Encoder::instance()
will not match the one that another schema is returning, but instead is a subclass. One of them is when you return a proxy object in the getRelationships
method of a schema.
I will give a code example. Let's say we have a PostSchema
that has the following relationships:
public function getRelationships($post)
{
return [
'author' => [
self::DATA => $post->getAuthor() // will not return an Author object, but an AuthorProxy which extends Author
]
];
}
Now when you want to encode a Post
, it will give you an error, saying that there is no provider for AuthorProxy
. You see the problem? AuthorProxy
IS an Author
, but the encoder cannot handle this.
This is caused by Container::getSchema($resource)
:
public function getSchema($resource)
{
$resourceType = get_class($resource);
// ...
}
I can propose two solutions. One is that we check with instanceof
if it can provide a schema for $resource
. The second solution is that it supports Doctrine
natively, we can do this by extending the Container
with:
public function getSchema($resource)
{
$resourceType = get_class($resource);
// support for Doctrine Proxies
if (class_exists('Doctrine\Common\Util\ClassUtils')) {
$resourceType = ClassUtils::getRealClass($resourceType);
}
// ...
}
Also, it could have it's own ClassUtils
class that will get the correct class name if there is __CG__
(common marker name for proxy classes) in the name. Then it will support Doctrine
too and probably more libraries that implement the Proxy
pattern.
Hi.
I am getting:
"FatalErrorException in Encoder.php line 145:
syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable
(T_VARIABLE) or '{' or '$'"
when I try to instantiate the Encoder using:
$encoder = Encoder::instance([
'\My\Path\Models\MyModel' => '\My\Path\Schemas\MyModelSchema',
], new EncoderOptions(JSON_PRETTY_PRINT));
I am running PHP 5.4. Is "json-api" compatible with this version or do I need to upgrade to 5.5 please?
Thanks.
Hi,
When you have an associative array somewhere in your $data
array that you pass to the Encoder, the Parser will choke when trying to get the schema.
// Parser.php
if (is_array($data) === true) {
$isOriginallyArrayed = true;
$schema = $this->container->getSchema($data[0]); // better would be to use reset($data)
} else {
$isOriginallyArrayed = false;
$schema = $this->container->getSchema($data);
$data = [$data];
}
In limoncello-collins tests fail as it calls the render with $request
and $exception
as first two parameters (it's kinda interface for all renders). Render should be fixed accordingly.
Related to #52
The prefix could be used for defining full URLs with HTTP schema and domains. For instance if prefix set to 'http://example.com/api/v1' and resource self sub-URL is set to '/authors' self URL should be set to 'http://example.com/api/v1/authors/{id}'
Related to #55
If you decide to add in a non-spec parameter to the request such as page=2
, the application errors out. This should be handled and result in a 400 Bad Request instead.
The affected parameters I'm aware of are:
page
filter
This is the from the error log:
[2015-08-05 13:50:57] local.ERROR: exception 'ErrorException' with message 'Argument 6 passed to Neomerx\JsonApi\Factories\Factory::createParameters() must be of the type array, string given, called in /home/vagrant/www/work/lyf/vendor/neomerx/json-api/src/Parameters/ParametersParser.php on line 100 and defined' in /home/vagrant/www/work/lyf/vendor/neomerx/json-api/src/Factories/Factory.php:173
Stack trace:
#0 /home/vagrant/www/work/lyf/vendor/neomerx/json-api/src/Factories/Factory.php(173): Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(4096, 'Argument 6 pass...', '/home/vagrant/w...', 173, Array)
#1 /home/vagrant/www/work/lyf/vendor/neomerx/json-api/src/Parameters/ParametersParser.php(100): Neomerx\JsonApi\Factories\Factory->createParameters(Object(Neomerx\JsonApi\Parameters\Headers\Header), Object(Neomerx\JsonApi\Parameters\Headers\AcceptHeader), NULL, NULL, NULL, '2', NULL, NULL)
#2 /home/vagrant/www/work/lyf/vendor/neomerx/limoncello/src/Http/JsonApiTrait.php(305): Neomerx\JsonApi\Parameters\ParametersParser->parse(Object(Lyf\Http\Controllers\JsonApi\LaravelIntegration), Object(Neomerx\Limoncello\Errors\ExceptionThrower))
#3 /home/vagrant/www/work/lyf/vendor/neomerx/limoncello/src/Http/JsonApiTrait.php(316): Lyf\Http\Controllers\JsonApi\JsonApiController->getUncheckedParameters()
#4 /home/vagrant/www/work/lyf/vendor/neomerx/limoncello/src/Http/JsonApiTrait.php(334): Lyf\Http\Controllers\JsonApi\JsonApiController->checkParameters()
#5 /home/vagrant/www/work/lyf/vendor/neomerx/limoncello/src/Http/JsonApiTrait.php(398): Lyf\Http\Controllers\JsonApi\JsonApiController->getParameters()
#6 /home/vagrant/www/work/lyf/app/Http/Controllers/JsonApi/JsonApiController.php(46): Lyf\Http\Controllers\JsonApi\JsonApiController->getContentResponse(Array, 200, NULL, NULL)
#7 /home/vagrant/www/work/lyf/app/Http/Controllers/Api/UsersController.php(31): Lyf\Http\Controllers\JsonApi\JsonApiController->getResponse(Object(Illuminate\Database\Eloquent\Collection))
#8 [internal function]: Lyf\Http\Controllers\Api\UsersController->index()
As JSON-API spec says it is agnostic about the pagination strategy as there are at least a few possibilities e.g. page-based, offset-based, and cursor-based. For this reason pagination strategy is not detailed in the spec.
Currently paging links could be generated like this
$links = [
Link::FIRST => new Link('/authors?page=1'),
Link::LAST => new Link('/authors?page=4'),
Link::NEXT => new Link('/authors?page=6'),
Link::LAST => new Link('/authors?page=9'),
];
$encoder->withLinks($links)->encodeData($authors);
While this approach is almost fine it would've better if Encoder
looked into $authors
and took sub-URL part /authors
from AuthorSchema
.
The question is what interface such a method could have to be flexible and easy to use? There are a few thinks that should be in mind: URLs might be relative as in the sample above or absolute http://example.com/api/authors?...
, links might have meta information and different paging strategies are possible.
One of the possible approach
$encoder
->withPagingLinks([Link::FIRST, Link::LAST, ...], $pageLinksGenerator)
->encodeData(...);
Where first parameter is a list of desired links (all possible values are defined by the spec) and $pageLinksGenerator
is an instance of a class which implements
interface PageLinksGeneratorInterface
{
public function getLink(string $linkKey, string $schemaSubUrl);
}
So PageLinksGeneratorInterface
implementation e.g. PageBasedLinksGenerator
will do it for particular strategy and user can chose which one to use.
To follow semver, shouldn't the version numbers be more like 0.2.0
, so that you can do something like ~0.2.0
in composer.json. Not that it matters much as long as the version isn't 1.0.
I'm guessing you're planning to tag v1.0.0 when the spec becomes stable?
Relationships could be hidden by default with only meta or links shown by default. If include
parameter has the path it should overrideSHOW_DATA
to true
and render the data.
Related to #48
From spec: A server MAY choose to expose a deeply nested relationship such as comments.author as a direct relationship with an alias such as comment-authors. This would allow a client to request /articles/1?include=comment-authors instead of /articles/1?include=comments.author. By abstracting the nested relationship with an alias, the server can still provide full linkage in compound documents without including potentially unwanted intermediate resources.
It used to be excluded by default for nested relationships (and it is implemented in RC3). Now this functionality has to be changed to work with aliases. See related #15
I have the following code:
public function getRelationships($user)
{
return [
'followers' => [
self::SHOW_DATA => false,
self::DATA => function () use ($user) {
return $user->followers;
},
self::SHOW_RELATED => true,
]
];
}
It would be nice if that closure wasn't evaluated until needed. In my case when include=followers
in which case you would then call and render.
It seems like I have to potentially run multiple SQL statements even if the data isn't returned in the JSON.
The package uses multiple factories for its components. If replaced with one factory it will simplify package extensions.
It used to be 'ext' param only
Servers MUST respond with a 415 Unsupported Media Type status code if a request specifies the header Content-Type: application/vnd.api+json with any media type parameters.
jsonapi: an object describing the server's implementation
Hi,
I am trying to have a relationship that does not include the data object and only use the links. This way my API can async load them later.
This is what I tried, but my relationship does not have any value, just null
:
public function getRelationships($product)
{
return [
'translations' => [
self::SHOW_SELF => true,
self::SHOW_RELATED => true
];
}
Is this possible and how would I accomplish this?
Test snippet
$actual = $endcoder->encode([], null, null, new EncodingParameters(null, [
// include only these attributes and links
'authors' => [Author::ATTRIBUTE_FIRST_NAME, Author::LINK_COMMENTS],
]));
PHP Fatal error: Call to a member function getIncludePaths() on a non-object in /json-api/src/Encoder/Encoder.php on line 196
It's not clear from the examples in the README what exactly the instance
method requires for input. A DocBlock or a standard function description including data types would be the minimum requirement here, but it appears that the inputs are complex enough to merit more thorough descriptions.
For example, in the following snippet:
$encoder = Encoder::instance([
Author::class => AuthorSchema::class,
], new JsonEncodeOptions(JSON_PRETTY_PRINT));
echo $encoder->encode($author) . PHP_EOL;
What exactly is "Author::class" or "AuthorSchema::class"? Is "::class" a class constant that was not defined in the code included in the "sample" directory? Or are you trying to demonstrate that the input format should map a model class with a schema class? Even after looking at the source code, I'm not entirely sure. Also, since the $author
instantiation isn't shown, it's unclear what exactly it is.
A few more explanations here would go a long way and be much appreciated. Thanks.
If Accept
header is missing it should be implied client requests JSON API for response.
Hello,
Is were any way to provide only ids of related models to the main object (not model objects)?
Given the following attributes:
"attributes": {
"username": "vivalacrowe",
"private": {
"email": "[email protected]",
"name": "Rob"
}
}
How do you filter to only get the email address. I've tried:
$params = new \Neomerx\JsonApi\Parameters\EncodingParameters(
null,
[
'user' => ['private.email']
]
);
But this returns:
{
"data": {
"type": "user",
"id": "12287",
"links": {
"self": "https:\/\/api.crowdcube.com\/users\/12287\/followers"
}
}
}
Filtering seems to only work on top level keys?
// PostsController
protected $allowedFieldSetTypes = [
'posts' => ['title', 'body'],
];
Some exceptions (e.g. TooManyRequestsHttpException) have properties that are designed as HTTP headers.
A $headers
parameter should be added to error renders so such properties could be added to HTTP responses.
They just should absent.
This bug introduced in #57
If relationship data is array and the array contains only 1 item the array is converted into single object.
Instead of
{
"data" : {
"type" : "posts",
"id" : "1",
"attributes" : {
"title" : "Title",
"body" : "Body"
},
"relationships" : {
"comments" : {
"data" : [
{ "type":"comments", "id":"5" }
]
}
},
"links" : {
"self" : "http://example.com/posts/1"
}
}
}
The encoder returns (note: relationships --> comments --> data)
{
"data" : {
"type" : "posts",
"id" : "1",
"attributes" : {
"title" : "Title",
"body" : "Body"
},
"relationships" : {
"comments" : {
"data" : { "type":"comments", "id":"5" }
}
},
"links" : {
"self" : "http://example.com/posts/1"
}
}
}
Needed for InactiveProjects/limoncello#5
Hey there,
looking at various packages to implement an API conform to the JSON-API spec atm and it seems yours is the most complete one so far. Looking through the readme, the only things I noticed missing were cursor-based pagination and the attributes top-level object to hold information about the resource. Would you be interested in PRs for these things or is this maybe already in development? Cheers!
It used to be that intermediate resources had to be excluded.
Now: Because compound documents require full linkage, intermediate resources in a multi-part path must be returned along with the leaf nodes. For example, a response to a request for comments.author should automatically include comments as well as the author of each of those comments.
However they should be excluded within relationships with 'aliases'. See related #16
According to the wiki it's possible to do something like:
$error = new Error(
'some-id',
new Link('about-link'),
'some-status',
'some-code',
'some-title',
'some-detail',
['source' => 'data'],
['some' => 'meta']
);
Encoder::instance([])->encodeError($error);
but it does not seem possible to add multiple errors. Or is it? If so what would be the way to do so?
My use case is the following:
Is this possible?
Should support responses with 'self' and 'related'
{
"links": {
"self": "/articles/1/relationships/tags",
"related": "/articles/1/tags"
},
"data": [
{ "type": "tags", "id": "2" },
{ "type": "tags", "id": "3" }
]
}
in method setRelationshipTo
assert('$parentExists === true')
fails
way to reproduce
Encoder::instance([
Author::class => AuthorSchema::class,
Comment::class => CommentSchema::class,
Post::class => PostSchema::class,
Site::class => SiteSchema::class,
], $this->encoderOptions)->encode([$this->site, $this->site]);
Author
must have null
relationship to Comments
otherwise the error does no occures
How can one add links to a resource object such as
{
"data": {
"type": "leads",
"id": "29",
"attributes": {
"status": 0
},
"links": {
"self": "localhost:4000\/leads\/29",
"somelink": "link"
}
}
}
where "somelink" is the link to be added? Thanks!
Github shows there is a problem with build when Travis says it's totally fine. Must be a caching issue.
Schema has excessive number of parameters for tuning if meta/data/links/etc should be shown or hidden. It should be reviewed and simplified.
Helper method for checking inclusion, field set, sorting, paging, filtering and unrecognized parameters.
spotted by @yoanisgil in #54
Not very often used feature however it should give options to add custom links on resource level
{
"data":
{
"type": "someType",
"id": "123",
"attributes": {
"name": "some name"
},
"links": {
"custom-link": "..."
}
}
}
Links on 'relationships' level could be used while it's not added
Encoder should reflect this change in the spec
json-api/json-api@33f61a7
json-api/json-api@d191637
json-api/json-api@68bc78b
Current implementation uses JSON API RC3 convention that relations with empty data ([]
or null
) are encoded as null
or []
. Since version 1.0 relationship can have links
and meta
with data
set to null
or []
.
Thus instead of
{
...
"relationships" : {
"author" : null
},
...
}
It should be
{
...
"relationships" : {
"author" : {"data" : null}
},
...
}
Hi,
Just wondering how relationship endpoints are meant to be encoded? The encoder interface doesn't seem to have a method for encoding relationships, and I can't find any mention in your wiki about it.
The example instance from the JSON-API spec would be:
GET /articles/1/relationships/author HTTP/1.1
Accept: application/vnd.api+json
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": {
"type": "people",
"id": "12"
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.