Skip to content

Commit 6f3eb09

Browse files
committed
implementation of links object for relationships
1 parent 53c7260 commit 6f3eb09

File tree

7 files changed

+149
-13
lines changed

7 files changed

+149
-13
lines changed

README.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ or add
2323

2424
to the require section of your `composer.json` file.
2525

26-
27-
Usage
28-
==============================
29-
Once the extension is installed, simply use it in your code by :
3026
Data Serializing and Content Negotiation:
3127
-------------------------------------------
3228
Controller:
@@ -49,7 +45,6 @@ class Controller extends \yii\rest\Controller
4945
}
5046
```
5147
Defining models:
52-
5348
1) Let's define `User` model and declare an `articles` relation
5449
```php
5550
use tuyakhov\jsonapi\ResourceTrait;
@@ -115,6 +110,51 @@ component in the application configuration like the following:
115110
],
116111
],
117112
```
113+
Links
114+
---------------------------
115+
Your resource classes may support HATEOAS by implementing the `LinksInterface`.
116+
The interface contains `getLinks()` method which should return a list of links.
117+
Typically, you should return at least the `self` link representing the URL to the resource object itself.
118+
In order to appear the links in relationships `getLinks()` method should return `self` link.
119+
Based on this link each relationship will generate `self` and `related` links.
120+
By default it happens by appending a relationship name at the end of the `self` link of the primary model,
121+
you can simply change that behavior by overwriting `getRelationshipLinks()` method.
122+
For example,
123+
```php
124+
class User extends ActiveRecord implements ResourceInterface, LinksInterface
125+
{
126+
use ResourceTrait;
127+
128+
public function getLinks()
129+
{
130+
return [
131+
Link::REL_SELF => Url::to(['user/view', 'id' => $this->id], true),
132+
];
133+
}
134+
}
135+
```
136+
As the result:
137+
```javascript
138+
{
139+
"data": {
140+
"type": "users",
141+
"id": "1",
142+
// ... this user's attributes
143+
"relationships": {
144+
"articles": {
145+
// ... article's data
146+
"links": {
147+
"self": {"href": "http://yourdomain.com/users/1/relationships/articles"},
148+
"related": {"href": "http://yourdomain.com/users/1/articles"}
149+
}
150+
}
151+
}
152+
"links": {
153+
"self": {"href": "http://yourdomain.com/users/1"}
154+
}
155+
}
156+
}
157+
```
118158
Enabling JSON Input
119159
---------------------------
120160
To let the API accept input data in JSON API format, configure the [[yii\web\Request::$parsers|parsers]] property of the request application component to use the [[tuyakhov\jsonapi\JsonApiParser]] for JSON input

src/LinksInterface.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
/**
3+
* @link http://www.stombox.com/
4+
* @copyright Copyright (c) 2015 Stombox LLC
5+
* @license http://www.stombox.com/license/
6+
*/
7+
8+
namespace tuyakhov\jsonapi;
9+
10+
use \yii\web\Linkable;
11+
12+
interface LinksInterface extends Linkable
13+
{
14+
public function getRelationshipLinks($name);
15+
}

src/ResourceTrait.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use yii\base\Arrayable;
99
use yii\db\ActiveRecordInterface;
1010
use yii\helpers\Inflector;
11+
use yii\web\Link;
12+
use yii\web\Linkable;
1113

1214
trait ResourceTrait
1315
{
@@ -85,6 +87,29 @@ public function setResourceRelationship($name, $relationship)
8587
}
8688
}
8789

90+
/**
91+
* @param string $name the case sensitive name of the relationship.
92+
* @return array
93+
*/
94+
public function getRelationshipLinks($name)
95+
{
96+
if (!$this instanceof Linkable) {
97+
return [];
98+
}
99+
$primaryLinks = $this->getLinks();
100+
if (!array_key_exists(Link::REL_SELF, $primaryLinks)) {
101+
return [];
102+
}
103+
$resourceLink = is_string($primaryLinks[Link::REL_SELF]) ? rtrim($primaryLinks[Link::REL_SELF], '/') : null;
104+
if (!$resourceLink) {
105+
return [];
106+
}
107+
return [
108+
Link::REL_SELF => "{$resourceLink}/relationships/{$name}",
109+
'related' => "{$resourceLink}/{$name}",
110+
];
111+
}
112+
88113
/**
89114
* @param array $fields
90115
* @param array $fieldSet

src/Serializer.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,15 @@ protected function serializeModel($model)
110110
} elseif ($items instanceof ResourceIdentifierInterface) {
111111
$relationship = ['id' => $items->getId(), 'type' => $items->getType()];
112112
}
113+
113114
if (!empty($relationship)) {
114115
$data['relationships'][$name]['data'] = $relationship;
116+
if ($model instanceof LinksInterface) {
117+
$links = $model->getRelationshipLinks($name);
118+
if (!empty($links)) {
119+
$data['relationships'][$name]['links'] = Link::serialize($links);
120+
}
121+
}
115122
}
116123
}
117124
}

src/actions/ViewRelatedAction.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88

99
use tuyakhov\jsonapi\ResourceInterface;
10+
use yii\base\Arrayable;
1011
use yii\data\ActiveDataProvider;
1112
use yii\db\ActiveQuery;
1213
use yii\rest\Action;

tests/SerializerTest.php

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public function testSerializeModelData()
4747
'attributes' => [
4848
'field1' => 'test',
4949
'field2' => 2,
50+
],
51+
'links' => [
52+
'self' => ['href' => 'http://example.com/resource/123']
5053
]
5154
]
5255
], $serializer->serialize($model));
@@ -59,6 +62,9 @@ public function testSerializeModelData()
5962
'type' => 'resource-models',
6063
'attributes' => [
6164
'field1' => 'test',
65+
],
66+
'links' => [
67+
'self' => ['href' => 'http://example.com/resource/123']
6268
]
6369
]
6470
], $serializer->serialize($model));
@@ -71,6 +77,9 @@ public function testSerializeModelData()
7177
'type' => 'resource-models',
7278
'attributes' => [
7379
'field1' => 'test',
80+
],
81+
'links' => [
82+
'self' => ['href' => 'http://example.com/resource/123']
7483
]
7584
]
7685
], $serializer->serialize($model));
@@ -87,7 +96,16 @@ public function testExpand()
8796
'field2' => 2,
8897
],
8998
'relationships' => [
90-
'extraField1' => ['data' => ['id' => '123', 'type' => 'resource-models']]
99+
'extraField1' => [
100+
'data' => ['id' => '123', 'type' => 'resource-models'],
101+
'links' => [
102+
'self' => ['href' => 'http://example.com/resource/123/relationships/extraField1'],
103+
'related' => ['href' => 'http://example.com/resource/123/extraField1'],
104+
]
105+
]
106+
],
107+
'links' => [
108+
'self' => ['href' => 'http://example.com/resource/123']
91109
]
92110
];
93111
$model = new ResourceModel();
@@ -109,6 +127,9 @@ public function testExpand()
109127
'field1' => 'test',
110128
'field2' => 2,
111129
],
130+
'links' => [
131+
'self' => ['href' => 'http://example.com/resource/123']
132+
]
112133
]
113134
]
114135
], $serializer->serialize($model));
@@ -124,6 +145,9 @@ public function testExpand()
124145
'field1' => 'test',
125146
'field2' => 2,
126147
],
148+
'links' => [
149+
'self' => ['href' => 'http://example.com/resource/123']
150+
]
127151
]
128152
]
129153
], $serializer->serialize($model));
@@ -140,14 +164,29 @@ public function dataProviderSerializeDataProvider()
140164
$bob->username = 'Bob';
141165
$bob->extraField1 = new ResourceModel();
142166
$expectedBob = ['id' => '123', 'type' => 'resource-models',
143-
'attributes' => ['username' => 'Bob'],
144-
'relationships' => ['extraField1' => ['data' => ['id' => '123', 'type' => 'resource-models']]]];
167+
'attributes' => ['username' => 'Bob'],
168+
'links' => ['self' => ['href' => 'http://example.com/resource/123']],
169+
'relationships' => ['extraField1' => [
170+
'data' => ['id' => '123', 'type' => 'resource-models'],
171+
'links' => [
172+
'related' => ['href' => 'http://example.com/resource/123/extraField1'],
173+
'self' => ['href' => 'http://example.com/resource/123/relationships/extraField1']
174+
]
175+
]]];
145176
$tom = new ResourceModel();
146177
$tom->username = 'Tom';
147178
$tom->extraField1 = new ResourceModel();
148-
$expectedTom = ['id' => '123', 'type' => 'resource-models',
149-
'attributes' => ['username' => 'Tom'],
150-
'relationships' => ['extraField1' => ['data' => ['id' => '123', 'type' => 'resource-models']]]];
179+
$expectedTom = [
180+
'id' => '123', 'type' => 'resource-models',
181+
'attributes' => ['username' => 'Tom'],
182+
'links' => ['self' => ['href' => 'http://example.com/resource/123']],
183+
'relationships' => ['extraField1' => [
184+
'data' => ['id' => '123', 'type' => 'resource-models'],
185+
'links' => [
186+
'related' => ['href' => 'http://example.com/resource/123/extraField1'],
187+
'self' => ['href' => 'http://example.com/resource/123/relationships/extraField1']
188+
]
189+
]]];
151190
return [
152191
[
153192
new ArrayDataProvider([

tests/data/ResourceModel.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
namespace tuyakhov\jsonapi\tests\data;
77

8+
use tuyakhov\jsonapi\LinksInterface;
89
use tuyakhov\jsonapi\ResourceInterface;
910
use tuyakhov\jsonapi\ResourceTrait;
1011
use yii\base\Model;
11-
use yii\db\ActiveQuery;
12+
use yii\helpers\Url;
13+
use yii\web\Link;
1214

13-
class ResourceModel extends Model implements ResourceInterface
15+
class ResourceModel extends Model implements ResourceInterface, LinksInterface
1416
{
1517
use ResourceTrait;
1618

@@ -49,4 +51,11 @@ public function setRelation($name, $value)
4951
{
5052
$this->_related[$name] = $value;
5153
}
54+
55+
public function getLinks()
56+
{
57+
return [
58+
Link::REL_SELF => Url::to('http://example.com/resource/' . $this->getId())
59+
];
60+
}
5261
}

0 commit comments

Comments
 (0)