tuyakhov / yii2-json-api Goto Github PK
View Code? Open in Web Editor NEWImplementation of JSON API specification for the Yii framework
Implementation of JSON API specification for the Yii framework
Without parameter
GET /v1/user
Response result:
{
"data":{
"id":"23",
"type":"user",
"attributes":{
"id":23,
"username":"test_user",
"email":"[email protected]",
"created-at":1504866581,
"updated-at":1507177386,
"last-login-at":1507177386
}
}
}
With parameter
GET /v1/user?include=profile
Response result:
{
"data":{
"id":"23",
"type":"user",
"attributes":{
"id":23,
"username":"test_user",
"email":"[email protected]",
"created-at":1504866581,
"updated-at":1507177386,
"last-login-at":1507177386
},
"relationships":{
"profile":{
"data":{
"id":"23",
"type":"profile"
}
}
}
},
"included":[
{
"id":"23",
"type":"profile",
"attributes":{
"name":"yyyooo",
"firstname":"xxx",
"lastname":"xxx",
"public-email":null,
"gender":"male",
"age":null,
"avatar":null,
"created-at":1504866582,
"updated-at":1507030787
}
}
]
}
Here is my model code
User.php
<?php
namespace common\models\api;
use Yii;
use tuyakhov\jsonapi\ResourceTrait;
use tuyakhov\jsonapi\ResourceInterface;
/**
*/
class User extends \common\models\User implements ResourceInterface
{
use ResourceTrait;
public function fields()
{
$fields = [
'id' => 'id',
'username' => 'username',
'email' => 'email',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
'last_login_at' => 'last_login_at',
];
return $fields;
}
public function extraFields()
{
return ['profile'];
}
/**
* @return \yii\db\ActiveQuery
*/
public function getProfile()
{
return $this->hasOne(Profile::className(), ['user_id' => 'id']);
}
}
Profile.php
<?php
namespace common\models\api;
use Yii;
use tuyakhov\jsonapi\ResourceTrait;
use tuyakhov\jsonapi\ResourceInterface;
/**
*
*/
class Profile extends \common\models\Profile implements ResourceInterface
{
use ResourceTrait;
public function fields()
{
$fields = [
'name' => 'name',
'firstname' => 'firstname',
'lastname' => 'lastname',
'public_email' => 'public_email',
'gender' => 'gender',
'age' => 'age',
'avatar' => 'avatar',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
];
return $fields;
}
}
Questions:
This is similar to #40 but I believe is a bit different.
I have two models (MaintenanceRecommendation
and Car
). If I do this request (/api/maintenance-recommendations/fix-brakes?include=car
), I will properly send back both the particular recommendation and the related car model.
But in my particular use case, I know the client will have the car
model loaded on the client side by the time this request is made, so there is no reason to request that the full car
relationship be included. The car
model is also quite heavy and takes additional processing that I would like to skip.
What I'd like to do is send back hint data from the backend so that the client can properly wire the recommendation data to the appropriate car without sending the full payload for the car.
Ideally, I would call GET /api/maintenance-recommendations/fix-brakes
and see the following (where the car
relationship data is populated, but there is no included
payload added as well):
{
"data": {
"id": "FancyCar-fix-brakes",
"type": "maintenance-recommendations",
"attributes": {
...
},
"relationships": {
"car": {
"data": {
"id": "FancyCar",
"type": "cars"
}
}
}
}
}
Any thoughts on that? From what I've seen in Twitter conversations with the spec author, this type of response would be valid per the spec ... It's in essence server-side hinting that the backend is allowed to add without it being required ...
I try to use default yii rest, but model doesn't save anything.
I did request with ember to POST http://localhost/v1/users with data
http://image.prntscr.com/image/b450d336c78e4dfdb26574d55e5784b3.png
After some investigations, i found the reason.
https://github.com/tuyakhov/yii2-json-api/blob/master/src/JsonApiParser.php#L33 make data format like this:
array(1) {
["User"]=>
array(2) {
["email"]=>
string(5) "eeeee"
["password"]=>
string(4) "cccc"
}
}
in yii\rest\CreateAction https://github.com/yiisoft/yii2/blob/master/framework/rest/CreateAction.php#L51 load wrong set attributes that are empy after setAttributes and $model->save() can't save it and always return validator error
"errors": [
{
"source": {
"pointer": "/data/attributes/email"
},
"detail": "Email cannot be blank."
},
{
"source": {
"pointer": "/data/attributes/password"
},
"detail": "Password cannot be blank."
}
]
}
I have not seen that there is Authentication and authorizations for CRUD operations, do you plan to implement it?
I was found a small mistake when a model with errors serialized. Model attributes not convert according to JSON API standard.
yii2-json-api/src/Serializer.php
Line 299 in 641a17e
Better not to simply use attribute name, but conver attribute name to a member name:
$memberName = \tuyakhov\jsonapi\Inflector::var2member($name);
and then use it:
'source' => ['pointer' => "/data/attributes/{$memberName}"],
as in jsonapi filtering
unlink/unlinkAll/link are awful functions in the Yii framework, they assume everything is going to go correctly and fire off database errors instead of validation errors.
Would a PR without unlink/unlinkAll/link be desirable?
Hi!
Can't create resource without relationships.
"message": "Argument 2 passed to tuyakhov\jsonapi\actions\Action::linkRelationships() must be of the type array...
Thx for your work.
I'm dealing with a scenario where I do a POST to an endpoint (/api/user
) that is actually backed by two DB tables instead of one (see http://www.yiiframework.com/doc-2.0/guide-input-multiple-models.html for an example of what I'm trying to do).
In this scenario, defining a create or update controller action doesn't do what I'm after, as CreateAction.php#L42 and the following lines specifically assume a Yii2 ActiveRecord model instead of a Yii2 form model.
Could we define an JsonAPIActiveRecordInterface
that a form model could implement? Doing that would allow form models to return needed values while also letting app authors know that they would continue to work with the library create and update actions ...
Seems like we'd need to define the following methods in our interface ...
getPrimaryKey()
save()
primaryKey()
findOne()
getRelation
unlinkAll
link
Hi!
If I send a null or an empty array as a data member like:
PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": null // or []
}
Is this normal behavior? Because in the JSON API specification I read that:
The PATCH request MUST include a top-level member named data containing one of:
- a resource identifier object corresponding to the new related resource.
- null, to remove the relationship.
....
For all request types, the body MUST contain a data member whose value is an empty array or an array of resource identifier objects.
....
Thanks for this extension!
I support a client on an older version of PHP 5 at the moment (5.4! 😢 ) and the addition of class constants that are arrays broke us when we upgraded to 1.0.6 just now. I'm not asking to support PHP 5.4 for this extension (though the Yii 2.0.x series does support it ...) but could we note somewhere the minimum PHP version that is supported?
Per the Scrutinizer config I found, it looks like we're testing against PHP 5.6. Should we be doing multi-environment builds to test against PHP 7.0+ as well?
https://scrutinizer-ci.com/docs/build/multiple_test_environment
Would send in a PR to add it but I'm not at a computer atm ...
extrafields doesn't work to me.
//in search dataprovider
$query = \common\models\Node::find()->joinWith(['device', 'device.contract']);
// In Model by ResourceInterface
public function extraFields()
{
return ['device',];
}
Is it possible to display information relationship many to many?
Hello, I have an issue with how the relationship objects are implemented. In order to prevent fetching of the same things over and over, i have some global dependencies that are fetched on app load. I store them (via redux in JS, because they are nice and normalized), and afterwards connect them with the object by using the ids. The way that the library is implemented prevents me from doing this. I can see that it was done to speed things up, but there is really no cost of adding the relationship objects as that data is already in the table, i just need the id.
Is this something that can be changed?
Hi, thanks for your work on this. I'm trying to implement your extension but can't find any example code to play with and I can't figure out how to use it looking at the code. Thanks
First of all, thanks for your great extension.
This is a first Yii2 REST JSONAPI for me. And I think I found a bug in sparse fieldsets standard. When I want to specify fields I will do like this:-
?fields[customers]=firstName,lastName,status
But it doesn't work. When I look at code in Serializer.php.
protected function serializeModel(ResourceInterface $model)
{
$fields = $this->getRequestedFields();
$attributes = isset($fields[$model->getType()]) ? $fields[$model->getType()] : [];
$model->getType() return singular resource name. But my resource is plural.
If I change to
?fields[customer]=firstName,lastName,status
It works. Do this require modification?
Sorry for my English.
Thanks.
Hi
Could you put basic instructions and usage like installation, usage, what is covered and what is missing?
Thanks
http://jsonapi.org/recommendations/#naming
Member names SHOULD start and end with the characters “a-z” (U+0061 to U+007A)
Member names SHOULD contain only the characters “a-z” (U+0061 to U+007A), “0-9” (U+0030 to U+0039), and the hyphen minus (U+002D HYPHEN-MINUS, “-“) as separator between multiple words.
Some clients used this by default. For example ember.js
// models/player.js
import DS from "ember-data";
export default DS.Model.extend({
firstName: DS.attr(),
});
expected response
"data": [
{
"attributes": {
"first-name": "Benfica",
},
Now there is no way to set it by Serializer
The library is still young, but need to be listed at (jsonapi.org)[http://jsonapi.org/implementations/] so that much more people will discover it
http://jsonapi.org/format/#document-resource-object-attributes
Although has-one foreign keys (e.g. author_id) are often stored internally alongside other information to be represented in a resource object, these keys SHOULD NOT appear as attributes.
class NodeController extends ActiveController
{
public $serializer = 'tuyakhov\jsonapi\Serializer';
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'contentNegotiator' => [
'class' => ContentNegotiator::className(),
'formats' => [
'application/vnd.api+json' => Response::FORMAT_JSON,
],
],
]);
}
public function actions()
{
return array_merge(
parent::actions(),
[
'index' => [
'class' => 'yii\rest\IndexAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'prepareDataProvider' => function ($action) {
$model = new $this->modelClass;
return $model->search(\Yii::$app->request->get());
}
]
]
);
}
public $modelClass = 'api\models\Node';
}
class Node extends \common\models\Node implements ResourceInterface
{
public $name; // filter
use ResourceTrait;
public function extraFields()
{
return ['device',];
}
public function fields()
{
return ActiveRecord::fields();
}
public function search($params)
{
$query = self::find()->joinWith(['device', 'device.contract']);
$dataProvider = new ActiveDataProvider(
[
'query' => $query,
]
);
$query ->andFilterWhere(['like', 'node.name', $this->name])
return $dataProvider;
}
}
Model has public $name; // filter
:
api/nodes
<response>
<data>
<item>
<id>4025</id>
<type>nodes</type>
<attributes>
<id>4025</id>
<name/> //<-------- empty
var_dump($dataProvider->getModels());die();
returns
array (size=3)
0 =>
object(api\models\Node)[225]
public 'name' => null
private '_attributes' (yii\db\BaseActiveRecord) =>
array (size=63)
'id' => int 4025
'name' => string 'Тестовый узел 2' (length=27)
where unlink/unlinkAll are used in the codebase it would be desirable to pass the 3rd parameter in somehow.
Would a PR with this feature be useful?
In general, it is not clear what parts of jsonapi standard is covered by the extension and what is left for the implementor or maybe will be included in future updates of the extension.
There are two areas in specific that are not covered by the readme:
For example, the readme says nothing that it uses eaxtraFields() in the parent model to allow the use of ?include=child
. I had to extract it from the sources.
i.e in Countries.php
public function extraFields()
{
return ['cities']
}
public function getCities()
{
return $this->hasMany(Cities::className(), ['country_id' => 'id']);
}
and in Cities.php
public function getLinks()
{
return [
Url::home(true) . 'v1/cities/' . $this->id,
];
}
to be able to use something like v1/countries/10?include=cities
UpdateRelationshipAction.php
?In short, some more info with usage examples would be much welcomed
I have a tables user <-> user_group <-> group and user model
class User implements ResourceInterface
{
use ResourceTrait;
public function getGroups()
{
return $this->hasMany(Group::class, ['id' => 'group_id'])->viaTable('user_group', ['user_id' => 'id']);
}
}
When I try to update a resource, I get an error.
Integrity constraint violation: 1048 Column 'user_id' cannot be null The SQL being executed was: UPDATE `user_group` SET `user_id`=NULL WHERE `user_id`=2"
The problem occurs in this part of the code.
public function setResourceRelationship($name, $relationship)
{
/** @var $this ActiveRecordInterface */
if (!$this instanceof ActiveRecordInterface) {
return;
}
if (!is_array($relationship)) {
$relationship = [$relationship];
}
$this->unlinkAll($name);
foreach ($relationship as $key => $value) {
if ($value instanceof ActiveRecordInterface) {
$this->link($name, $value);
}
}
}
Need to replace for relations many to many
$this->unlinkAll($name, true);
Json example
{
"data": {
"id": "2",
"type": "users",
"relationships": {
"groups": {
"data": [
{
"id": "1",
"type": "groups"
},
{
"id": "2",
"type": "groups"
}
]
}
},
},
}
If use
class Controller extends \yii\rest\Controller
{
public $serializer = 'tuyakhov\jsonapi\Serializer';
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'contentNegotiator' => [
'class' => ContentNegotiator::className(),
'formats' => [
'application/vnd.api+json' => Response::FORMAT_JSON,
],
]
]);
}
}
Servers MUST send all JSON API data in response documents with the header Content-Type: application/vnd.api+json without any media type parameters.
http://jsonapi.org/format/#content-negotiation-servers
Active Record uses fields() by default and it expected when work by API.
Your implement return always [] attributes.
But removal of this method would create another issue. Because ResourceTrait may be used in others non ActiveRecord classes. Probably it requires more efficient approach.
Thank you for this extension. I'm starting to use it and found some incomptability with the jsonapi 1.0 standard.
Here is how a sample error looks like with the extension:
{
"errors": [
{
"name": "Not Found",
"message": "Page not found.",
"code": 0,
"status": 404,
"type": "yii\\web\\NotFoundHttpException",
"previous": {
"name": "Invalid Route",
"message": "Unable to resolve the request \"contries/index\".",
"code": 0,
"type": "yii\\base\\InvalidRouteException"
}
}
]
}
This is because the API response formatter just put the data Yii generated data inside the error section
$apiDocument = ['errors' => $response->data];
A JSONAPI error has a different structure
What is missing IMHO is a mapping between Yii way of representing errors to JSONAPI compatible format.
For example if we have an error like above, the formatter should convert it to something like
{
"errors": [
{
"status": "404",
"title": "Resource Not Found",
"detail": Resource not found. Caused by Invalid Route. Unable to resolve the request "contries/index"
}
]
}
Version |
---|
1.0.3 |
TL;DR: In scenarios where there are many relationships but a client only wants to refresh a single relationship, the serializer seems to require eager-loading all relationships before filtering down to a specific relationship to include.
In the theoretical example below:
class Blog implements ResourceInterface, LinksInterface
{
use ResourceTrait;
public function extraFields()
{
return [
'author',
'comments',
'likes',
];
}
public function getAuthor()
{
return $this->hasOne(Author::className(), ['id' => 'author_id']);
}
public function getComments()
{
return $this->hasMany(Comment::className(), ['id' => 'blog_id']);
}
public function getLikes()
{
// complicated SQL packaged up into a model runs here ...
}
}
If an API client wants to do a /api/blogs/3?include=comments
without triggering the complicated SQL in the likes
relationship, the getLikes()
will still have been loaded using the default ResourceTrait
and default Serializer
due to the resolveFields()
foreach
here ...
yii2-json-api/src/ResourceTrait.php
Lines 56 to 68 in f2bee3d
Specifically, line 65 there eager-loads all relationships in the extraFields
method even if those relationships are not included in any way ...
Do you think we can work out how to not pre-populate those relationships until we're sure we want them in the payload? Combining that desire with the option to add links could get a bit tricky ...
Interested in your thoughts!
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.