Skip to content

Add JSON API basic support (continuation) #1175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 13, 2017

Conversation

meyerbaptiste
Copy link
Member

@meyerbaptiste meyerbaptiste commented Jun 13, 2017

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets api-platform/api-platform#82
License MIT
Doc PR N/A

Continuation (and conclusion?) of PRs #785 and #1036.

First, I rebase the branch against the master branch (:cold_sweat:).
Then (see the second commit):

  • I fixed some issues according to the JSON API schema definition,
  • I fixed Behat tests,
  • I added some PHPUnit tests,
  • I added the enabling of the PATCH operation only if JSON API support is enabled,
  • I removed dead code.

GTK:

  • This implementation is very, very basic,
  • IMO, normalizers could be refactored in the future.

Did I forget something?

Cc: @hectorh30, @Simperfit

// If order query parameter is not defined or is already an array, never mind
$orderParameter = $request->query->get($this->orderParameterName);
if (null === $orderParameter || is_array($orderParameter)
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strange cs?

@soyuka
Copy link
Member

soyuka commented Jun 13, 2017

First, I rebase the branch against the master branch (:cold_sweat:).

Wow! For this you deserve much ❤️! To bad that authors history was lost though. Really nice if this can finally come to an end :D. Thanks @meyerbaptiste!

@Simperfit
Copy link
Contributor

@soyuka Yep, anyway the author are in the files and we could @them when mergin :).

Thanks @meyerbaptiste for finishing this !

*
* @author Héctor Hurtarte <hectorh30@gmail.com>
*/
final class TransformSortingParametersListener
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like those parametersListener have a lot of code duplication. I don't know if it's okay like this or if we should base those on an abstract class. WDYT?

$returnDataArray = [];
if (isset($context['api_sub_level'])) {
foreach ($data as $index => $obj) {
$returnDataArray['data'][][$index] = $this->normalizer->normalize($obj, $format, $context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the $returnDataArray be like $returnDataArray = ['data' => []] on initialization for this to work? I don't get how ['data' => [[]]] is self-initializing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic of PHP: https://3v4l.org/GXaOT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow okay, I feel dumb right now, thanks @meyerbaptiste!

$lastPage = $data->getLastPage();
$itemsPerPage = $data->getItemsPerPage();

$paginated = 1. !== $lastPage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. ? Because of float comparison? Saw that below too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think. $lastPage is a float.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/api-platform/core/pull/1175/files#diff-5b154fa3efaf5776695baccd0a52daf4R66 initialized as integer? IMO pagination stuff should be integer-only because it doesn't make sense to have a float ^^ (maybe jsonapi specifies float though I haven't looked at it).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they wanted to keep a consistency with the PaginatorInterface interface which returns floats.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. We use float because of large numbers.


$typeShortName = '';

if ($className && $this->resourceClassResolver->isResourceClass($className)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because isResourceClass is quite heavy (#1068) I'd prefer if we could use a variable to store it above instead of calling it a least twice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored this part.

*
* @return bool|string
*/
private function getJsonApiCacheKey(string $format = null, array $context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method typing :bool?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? This method returns a string or false.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, my bad missed that.

@@ -183,6 +183,8 @@ private function getPathOperation(string $operationName, array $operation, strin
return $this->updateGetOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
case 'POST':
return $this->updatePostOperation($pathOperation, $mimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions);
case 'PATCH':
$pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting hack 😛 we could break after this IMO so that it gets to the return statement directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why adding a break here? I have to return in the PUT case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay then thought it returned as default.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@soyuka soyuka Jun 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 better like you did here indeed.


$normalizer = new ItemNormalizer($resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $contextBuilderProphecy->reveal());

$this->assertFalse($normalizer->supportsDenormalization('foo', ItemNormalizer::FORMAT));
$normalizer->denormalize(['foo'], Dummy::class, 'jsonld', ['jsonld_has_context' => true, 'jsonld_sub_level' => true, 'resource_class' => Dummy::class]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you drop this test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huum, I think it was because the test is about denormalization support and not denormalization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's weird indeed, test is testDontSupportDenormalization. Then we still do the denormalization with no assertions ?!

Still, denormalize isn't tested anymore in this class, maybe we could move it in a new test with an assertion then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you're right.

Then we still do the denormalization with no assertions

It's wrong, because shouldBeCalled() is an assertion 😛

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha yeah I meant no assertion on the denormalization call like we do on normalization below.


$this->listener->onKernelRequest($eventProphecy->reveal());

$expectedRequest = new Request(['page.size' => 5, 'page.number' => 3]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could also test negative page parameters values to avoid a recent issue we had.

Copy link
Member Author

@meyerbaptiste meyerbaptiste Jun 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. But at this level, positive or negative, it has no impact.

@Simperfit
Copy link
Contributor

@meyerbaptiste could you push it a little bit further by adding that 0.6% coverage missing ?

@meyerbaptiste meyerbaptiste force-pushed the add_jsonapi_support branch 6 times, most recently from 8e34350 to 257522c Compare June 14, 2017 15:14
$returnDataArray = [];
if (isset($context['api_sub_level'])) {
foreach ($data as $index => $obj) {
$returnDataArray['data'][][$index] = $this->normalizer->normalize($obj, $format, $context);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not confident with this piece of code. I don't think it works properly but at the same time, I don't know what is the correct result. I am not familiar with sub-resources...
Anyone can help me here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct according to what are the other normalizer's doing in case of a api_sub_level flag.

if (isset($context['api_sub_level'])) {

if (isset($context['api_sub_level'])) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more about the array structure. What type of values can be the index and the normalized object here? According to JSON API spec, data must be primary data or represent resource linkage, we can not put what we want in it! 😕

@meyerbaptiste meyerbaptiste force-pushed the add_jsonapi_support branch 8 times, most recently from 2bcdfcc to 481636c Compare June 16, 2017 13:57
@meyerbaptiste
Copy link
Member Author

I got +0.1% @Simperfit! I don't want to write tests for at least 1 month...

@Simperfit
Copy link
Contributor

it's good for me ;)

@hectorh30
Copy link

Awesome! Thanks so much for going on with this. I also went out of time to complete this, but I'm glad to see it moving forward.

@meyerbaptiste meyerbaptiste merged commit 4b3d1ab into api-platform:master Oct 13, 2017
@meyerbaptiste meyerbaptiste deleted the add_jsonapi_support branch October 13, 2017 15:06
@meyerbaptiste
Copy link
Member Author

Thanks @Simperfit & @hectorh30, 4 Oct 2016 -> 13 Oct 2017, 1 year of collaborative work!

@Simperfit
Copy link
Contributor

Thank you for finishing it !

@soyuka
Copy link
Member

soyuka commented Oct 13, 2017

It's MERGGEEEEDDDD ❤️ ❤️ ❤️
big thanks @Simperfit @meyerbaptiste @hectorh30 and every one who reviewed this!

@StephenOTT
Copy link

@mario-digitalstate

cr3a7ure added a commit to cr3a7ure/core that referenced this pull request Oct 27, 2017
* GraphQL Query support (api-platform#1358)

* Fix missing service when no Varnish URL is defined

* [PropertyFilter] Fix whitelist comparison (api-platform#1379)

* Remove wrong doc

* Swagger subcollection documentation issue (api-platform#1395)

* Make the requirements configurable

Closes api-platform#1401

* Provide a better exception message and type

Getting "Not found" on a route where you are getting an object can get
really confusing.

* Bump branch alias to 2.2.x-dev

2.1.x-dev is already taken by the 2.1 branch, and this should represent
the next minor version anyway.

* Fix tests

* Reuse PriorityTaggedServiceTrait from symfony

* Improve payload support and remove duplicate code in ConstraintViolationListNormalizer (api-platform#1416)

* Filter Annotation implementation

Parent class filters (needs test)
Support for nested properties
Tests
Fix some comments and remove id=>id in compiler pass

* Throw on abstract data providers / filters

* Remove an unused var

* Remove useless badges

* Enable the coverage

* Fix some quality issues

* Add job to test upstream libs deprecations

If api-platform uses upstream libs in a deprecated way, we should be aware
of it.

* Add job to test upstream libs deprecations

If api-platform uses upstream libs in a deprecated way, we should be aware
of it.

* Fix missing cache tag on empty collections

* Allow plain IDs with `allow_plain_identifiers`

* Fix indentation for GraphQL features.

* Add JSON API basic support (api-platform#785, api-platform#1036, api-platform#1175)

* Clean Behat tests

* Fix tags addition with an empty value

* Document swagger-specific description options

… and where to find them should there be newer ones.

* Fix PHPUnit tests

* Fix missing return statement

* Support & compatibility for PHP7.2

* Add feature to update swagger context for properties

* Generator compat improvements (api-platform#1429)

* Add support for resource names without namespace

* [SF 4.0] Make actions explicitly public

* Allow phpdocumentor/reflection-docblock 4

* fix hydra documentation normalizer with subresources

* Create a base collection normalizer

* Fix request auto-runner

* Update the changelog

* Update changelog
@jonday-simitive
Copy link

jonday-simitive commented Dec 1, 2017

Well done to everyone that helped out with this. Looks really great, been testing it out since it was merged in and great to see it has made it into a release. Is there any plans on getting include working http://jsonapi.org/format/#fetching-includes ... If not I'd be interested in contributing as it is one of my favorite parts of JSON API.

@soyuka
Copy link
Member

soyuka commented Dec 1, 2017

This looks really similar to what our Property and Group filters are doing. See https://github.com/api-platform/docs/blob/master/core/filters.md#serializer-filters

@jonday-simitive
Copy link

jonday-simitive commented Dec 1, 2017

@soyuka I could be wrong but it seems like the current implementation of JSON API will only provide me with the relationships property in the response if I add a related entity with a Group annotation for GET for example

{
  "links": {
    "self": "/api/cars/2"
  },
  "meta": {
    "totalItems": 1,
    "itemsPerPage": 25,
    "currentPage": 1
  },
  "data": [
    {
      "id": "/api/cars/2",
      "type": "Car",
      "attributes": {
        "modal": "Fiesta",
        "_id": 2,
      },
      "relationships": {
        "make": {
          "data": {
            "type": "Make",
            "id": "/api/make/77"
          }
        }
      }
    }
  ]
}

So a second API request need to be made. With the functionality that has yet to be implemented of the "include" query string to allow you to return:

{
  "links": {
    "self": "/api/cars/2?include=make"
  },
  "meta": {
    "totalItems": 1,
    "itemsPerPage": 25,
    "currentPage": 1
  },
  "data": [
    {
      "id": "/api/cars/2",
      "type": "Car",
      "attributes": {
        "model": "Fiesta",
        "_id": 2,
      },
      "relationships": {
        "make": {
          "data": {
            "type": "Make",
            "id": "/api/make/77"
          }
        }
      }
    }
  ],
  "included": [
    {
      "type": "make",
      "id": "88",
      "attributes": {
        "name": "Ford",
      }
    }
  ]
}

@soyuka
Copy link
Member

soyuka commented Dec 1, 2017

Indeed, it's just very similar and the implementation of include could follow the same logic as in those filters :).

@jonday-simitive
Copy link

@soyuka Ah yeah I get you, sorry been a long day ... thought you were saying I could use them already to get the included functionality. I'll take a look at those now though, thanks.

@cjunge-work
Copy link

Just a note on the includes. They make caching really difficult. A change in an included entity requires any caching to be purged for all other entities that include it. IMHO it's one of the flaws with JSON-API and similar formats that support includes.

A better option is to adopt HTTP/2 for faster connections with less latency and cache at the individual entity level (aggregate in DDD).

Included entities is primarily about saving requests, but HTTP/2 makes requests concurrent and faster/lighter than HTTP/1. Includes make the responses bigger and harder to maintain and less cacheable.

Making the JSON-API support feature-complete compared to the spec is a good thing, but I thought I'd bring some attention to the issues associated with using this feature. My opinion is based on using includes in a project, and having some big issues.

@bangbambang
Copy link

@cjunge-work I think it's a common issue with embedded relations (i.e using serialization group), irrespective of the format used. And IMO the best way to deal with it is to be mindful on what to embed for each responses to prevent overcomplicating cache management logic.

I personally hope this functionality would be available to provide consistency when used with content negotiation.

@deivid11
Copy link

deivid11 commented Jan 17, 2018

Hey thanks for the feature! How to enable this json api support?.
EDIT: Found it, adding format to the api platform configuraiton jsonapi: mime_types: ['application/vnd.api+json'].
Would be nice to add it to the config reference https://api-platform.com/docs/core/configuration/

hoangnd25 pushed a commit to hoangnd25/core that referenced this pull request Feb 23, 2018
@Ronnie-J
Copy link

@jonday-simitive did you start working on this? I am in a situation where I need a subresource to be included with data.

@jonday-simitive
Copy link

@Ronnie-J We ended up using multiple API requests or the SubResources Api Platform functionality. I've been trying to find the time to implement it but been unable to so far :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.