-
-
Notifications
You must be signed in to change notification settings - Fork 81
New resource-based API #119
base: master
Are you sure you want to change the base?
Conversation
Simpler, 2-3x faster, and doesn't violate the LSP like the old serializer-based API.
Apply fixes from StyleCI
|
||
namespace Tobscure\JsonApi; | ||
|
||
class Util |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay! This was going to be the next target of my hate. So glad it's dead ;)
Example usage: class PostResource extends AbstractResource
{
protected $type = 'posts';
protected $post;
public function __construct($post)
{
$this->post = $post;
}
public function getId()
{
return $this->post->id;
}
public function getAttributes(array $fields = null)
{
return ['body' => $this->post->body];
}
public function author()
{
return new Relationship(new UserResource($this->post->author));
}
}
class UserResource extends AbstractResource
{
protected $type = 'users';
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function getId()
{
return $this->user->id;
}
public function getAttributes(array $fields = null)
{
return ['name' => $this->user->name];
}
}
for ($i = 1; $i <= 100; $i++) {
$users[] = (object) ['id' => $i, 'name' => 'Toby'];
}
for ($i = 1; $i <= 50; $i++) {
$posts[] = (object) ['id' => $i, 'body' => 'hello', 'author' => $users[array_rand($users)]];
}
$resources = array_map(function ($post) {
return new PostResource($post);
}, $posts);
$document = new Document($resources);
$document->setInclude(['author']);
echo $document; |
src/ResourceInterface.php
Outdated
* @param array|null $fields | ||
* | ||
* @return array | ||
*/ | ||
public function getAttributes($model, array $fields = null); | ||
public function getAttributes(array $fields = null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about this
public function getSparseAttributes(array $fields); // and this guy can be implemented in the abstract class
public function getAttributes();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I think that probably creates more pain than it saves when you do want to implement it, because you then have two functions that are calculating attributes:
public function getAttributes() {
return [
'foo' => $this->calculateExpensiveAttribute()
];
}
public function getSparseAttributes(array $fields) {
$attributes = [];
if (in_array('foo', $fields)) {
$attributes['foo'] = $this->calculateExpensiveAttribute();
}
return $attributes;
}
vs. what we have now:
public function getAttributes(array $fields = null) {
$attributes = [];
if ($fields === null || in_array('foo', $fields)) {
$attributes['foo'] = $this->calculateExpensiveAttribute();
}
return $attributes;
}
For implementations that don't care about sparse fieldsets, I'm pretty sure you can leave out the $fields argument completely (as in the getAttributes()
method of my first example) since it has a default of null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I see your point. Agreed.
Good stuff. :) |
I just started using this library and wanted to drop an opinion here 😄 I like this change, although I like the name For instance, I'm guessing maybe this change is to be more in line with the wording in the JSON API spec? |
What about |
@franzliedke I like return new Relationship(new UserResource($this->post->author)); Since you have a relationship with another resource. It reads well! @tobscure Anything I can do to help move this along? I'm excited to start using this when it's ready :) |
Yeah I'm quite happy with @crhayes Feel free to start using it now, and let me know if you uncover any issues. I don't expect any further API changes. I just want to rewrite the test suite from scratch before releasing. |
Apply fixes from StyleCI
As long as we support PHP 5.5 + PHPUnit 4.8, we need to use getMock in our tests, which is deprecated in 5.0 and thus causing tests to fail.
…ncoded)" This reverts commit 4e80ea8.
Abstract class forces the object to belong to a certain hierarchy. If your domain object is already a part of another hierarchy (e.g. |
It also forces your clients to prefer inheritance over composition, you don't want to do that. https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose |
Right, understood. But then that leads me back to the method naming... I feel like the methods as they are will be pretty likely to conflict if implemented into an existing hierarchy. Like in your shopping cart example - a shopping cart could easily have "attributes" and "meta" which are not the same as JSON-API attributes/meta. I agree we shouldn't rename them to avoid conflict with any specific library, but what about to avoid conflict in general? |
You can't solve every potential corner case. I assume the best strategy is to stay focused on ResourceInterface as a part of JSON API domain. |
By the logic you've argued, should we also be using a RelationshipInterface? |
The original problem was LSP and it seems getting solved as far as I can tell. How to implement the architecture properly in general - I don't know, that's what I'm trying to answer by building my library. It's tough ;) |
I think technically, yes, it should be an interface. Because there are two separate concerns:
You could also argue the same for Error and Link objects. But pragmatically, it's much easier to just combine these responsibilities into the same class, so we can skip the step of mapping a (1) object to a (2) object during the serialization process. Even though it's technically not quite correct, I think I'm happy to leave it as is - otherwise I will end up rewriting your library in PHP 5, which I'm too lazy to do right now :P |
class Relationship | ||
use JsonSerializable; | ||
|
||
class Relationship implements JsonSerializable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how we support if relationship is empty? now we return {data: id: {null, type: type}} but it should be {data: null}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if you return Relationship::fromData(null)
?
I've reviewed these changes a couple of times and am in favor of this change. I have some reservations about the |
I think this is the key point regarding name conflicts, as with composition the implementing class will be "composing" the target class. |
@cjunge-work I see your point. I guess that makes sense, though having an interface might be beneficial here. At least people would have the option to implement their own specific error classes instead of using the builder. |
DRY-ed up tests
@tobscure any news on it? |
Added a benchmark
|
||
// Create a new JSON-API document with that collection as the data. | ||
$document = new Document($collection); | ||
$document = Document::fromData($resource); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm proposing to name method more verbose (because we will pass Resource
there):
$document = Document::fromResource($resource);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also pass in an array of resources. fromData
matches with the top-level data
key in the resulting document.
@tobyzerner @antonkomarev is this project any longer maintained? :( I've updated the master branch with the polymorphic PR of Anton, updated phpunit to 8.5, set min php to 7.2 and applied the PSR12 coding standard. https://github.com/burzum/json-api-php/tree/develop |
I'm using my own local implementation based on this one. It's enough for now. But surely it will be good to see this lib to move forward. |
@antonkomarev I'm trying to add typed return types and run into one issue, seems like a bug to me because according to the doc block it should only return array in Resource.php. public function toIdentifier(): array
{
if (! $this->data) {
return; // Changing this to [] breaks the test.
}
$array = [
'type' => $this->getType(),
'id' => $this->getId()
];
if (! empty($this->meta)) {
$array['meta'] = $this->meta;
}
return $array;
} I think it should then return an empty array or throw an exception? Is there a better place than this PR to discuss these issues? I would like to push the lib a little forward to up to date php. Also the doc block here said string but actually should be int and return 0 instead of empty string I think. Document.php: protected function getPage($key): int
{
$page = $this->getInput('page');
return isset($page[$key]) ? $page[$key] : 0;
} By the way my changes are based on the master branch, not this PRs branch. Is this PR already OK to use? If yes I'll have to merge it... |
Since |
@antonkomarev well, something does make it return null, this is at least what the tests reveal. What is holding this PR here back from being merged? |
Simpler, 2-3x faster, and doesn't violate the LSP like the old serializer-based API (#115).
Still to come: