Giter Club home page Giter Club logo

yii2-json-api's People

Contributors

acorncom avatar kfreiman avatar patrick-yi-82 avatar tuyakhov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yii2-json-api's Issues

Have question about relationships.

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:

  1. aways need '?include=profile'?
  2. Result is correct?

Allow addition of relationship information without full include data

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 ...

Default REST doesn't work

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."
        }
    ]
}

Field name should be convert according to recommendations for JSON API implementations

I was found a small mistake when a model with errors serialized. Model attributes not convert according to JSON API standard.

'source' => ['pointer' => "/data/attributes/{$name}"],

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}"],

moving away from unlink/unlinkAll/link

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?

Can't create resource without relationships.

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.

Add an ActiveRecordInterface that allows using form models?

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()
  • static primaryKey()
  • findOne()
  • getRelation
  • unlinkAll
  • link

It is impossible to clear the relations by sending PATCH request to relationship 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 []
}
  1. JsonParser::parseRelationships() will not process an empty relationship data.
  2. Action::linkRelations(), for obvious reasons, will not clear the relationship.

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.

....

jsonapi.org

Thanks for this extension!

Note which versions of PHP we support?

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

How to use ExtraFields ?

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',];
    }

image

Include relationship object in every response (even when omitted from include)

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?

Sparse filedsets: query naming problem.

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.

Basic Instructions

Hi
Could you put basic instructions and usage like installation, usage, what is covered and what is missing?
Thanks

Naming

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

List the library at jsonapi.org

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

Empty value if is same as filter-attribute

Controller
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';
}
Model
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)

$_GET is missed

I used ember.js with his default JsonApi adapted.

image

But on server I can't get Yii::$app->request->get()

My config is

 'request' => [
            'parsers' => [
                'application/vnd.api+json' => 'tuyakhov\jsonapi\JsonApiParser',
            ]
        ],

unlink delete parameter

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?

Update readme about relationships

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:

  1. resources and sub-resources

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

  1. Relationships
    It is not clear how they work and how to use them. What is covered? just serializing the output? or more than that since the source have UpdateRelationshipAction.php?

In short, some more info with usage examples would be much welcomed

Update many to many relation not working

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"
                    }
                ]
            }
        },
    },
}

Correct Content-Type

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,
                ],
            ]
        ]);
    }
}

then you get
image

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

Improve to ResourceTrait:fields

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.

#3

Errors format is not compatible with JSONAPI

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"
    }
  ]
}

feature request - shift to lazily loaded resource relationships in some way?

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 ...

public function getResourceRelationships()
{
$relationships = [];
$fields = [];
if ($this instanceof Arrayable) {
$fields = $this->extraFields();
}
foreach ($this->resolveFields($fields) as $name => $definition) {
$relationships[$name] = is_string($definition) ? $this->$definition : call_user_func($definition, $this, $name);
}
return $relationships;
}

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!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.