Skip to content

[RFR] Allow to specify formats per resources/operations #1798

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 1 commit into from
Jun 9, 2018

Conversation

antograssiot
Copy link
Contributor

@antograssiot antograssiot commented Mar 29, 2018

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #1778
License MIT
Doc PR

As suggesting in the issue, this PR will allow to use the following:

/**
 * @ApiResource(attributes={
 *     "formats"={"jsonld", "json", "html", "csv"={"text/csv"}}
 * }, itemOperations={
 *     "put", "delete",
 *     "get"={"formats"={"jsonld", "json", "html", "pdf"={"application/pdf"}}}
 * })
 */
class Foo

todo:

  • Create a FormatsProvider service
  • Add more PHPUnit tests
  • Add integration tests
  • Update the swagerUi available formats
  • Add formats fallback attributes in the @ApiResource annotation

ping @vincentchalamon, @Toflar

@antograssiot antograssiot changed the title Allow to specify formats per resources/operations WIP Allow to specify formats per resources/operations Mar 29, 2018
@antograssiot
Copy link
Contributor Author

antograssiot commented Mar 29, 2018

Playing a bit more with this and when adding test I think I need to introduce a new service like a FormatProvider that will be called by every service that actually receives %api_platform.formats%...

@antograssiot antograssiot force-pushed the format-by-resource branch 4 times, most recently from c9fb1f1 to 9fc3c53 Compare March 30, 2018 12:04
@antograssiot antograssiot changed the title WIP Allow to specify formats per resources/operations [RFR] Allow to specify formats per resources/operations Mar 30, 2018
@dunglas
Copy link
Member

dunglas commented Mar 30, 2018

I'm for keeping only "pdf"={"application/pdf"}, it's expliciter

@antograssiot
Copy link
Contributor Author

PR updated to only support the simplest declaration form.

Copy link
Member

@dunglas dunglas left a comment

Choose a reason for hiding this comment

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

To me there is a flaw in the logic. The service shouldn't depend of the request, it should use the attributes directly to find the accepted formats for every resource class, then the Swagger documentation and the listener should use this new service to respectively display the available formats or throw an exception when a non-supported format is requested.

private $formats;
private $resourceMetadataFactory;

public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $formats)
Copy link
Member

Choose a reason for hiding this comment

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

I suggest to renamed formats in configuredFormats, it will be mor explicit

*/
public function fromRequest(Request $request): array
{
if ($this->formats) {
Copy link
Member

Choose a reason for hiding this comment

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

It looks buggy: if a different format can be configured per resource, then you must test for $this->formats[$resourceClass] or the bad formats will be returned (for instance the ones of the previously requested resource class).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not storing key/values just the format for one operation right now

@antograssiot
Copy link
Contributor Author

Ok I'll think about your comment @dunglas. I'm the impression that you expect this service to register all acceptables format per resource, but as the format could be defined at the operation level, that seems a bit weird to me.
I could still remove the request and only use attributes, that's what I've been starting with but when I saw the number of places where attributes were not already presents/extracted, I decided to it in a single point in the service, ok to change back if you prefer. I'll work on that next week

@Toflar
Copy link
Contributor

Toflar commented Apr 3, 2018

Sorry for coming late to the party and thanks for pinging me @antograssiot. I'm +1 on the fact that it should go into its own service and become part of the resource metadata rather than the request as @dunglas stated. Even if you allow to specify the formats on operation level, it is still resource meta data that can have different sources, not only annotations.

@antograssiot
Copy link
Contributor Author

@dunglas it has been updated and doesn't rely on the SF Request but only on attributes.

@antograssiot
Copy link
Contributor Author

@Toflar it already leverages the getOperationAttribute() method from the resource metadata so already doesn't rely on annotation only but would work with Yaml or XML like every other attributes. Did I missed something ?

Copy link
Contributor

@Toflar Toflar left a comment

Choose a reason for hiding this comment

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

@Toflar it already leverages the getOperationAttribute() method from the resource metadata so already doesn't rely on annotation only but would work with Yaml or XML like every other attributes. Did I missed something ?

I was just mentioning again 😄 PR looks already a lot better now, excellent work! I left a few comments, mainly about improving DX when upgrading to v3 later on but also on the general concept about how the configuration should behave. I hope it helps, keep up the work!

public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $formats)
{
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->configuredFormats = $formats;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you also rename $formats to $configuredFormats to be consistent here?

/**
* Finds formats from a Resource class.
*/
public function getFormatsFromAttributes(array $attributes): array
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if I like this concept here. I mean it's a matter of how we want it to work. Right now the specified formats on the resource would override the generally configured formats. But if I think about it, the generally configured formats are general. So what I expect this annotation to do is to configure additional formats for this specific resource. So imho it should merge the configured formats with the ones defined on the resource. I understand that it can be a use case to say everything should be available in json except for this one resource so maybe how it behaves should be a configuration option too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's indeed one of the concerns I have too. it could be painful if you add a general format, you'll then need to update all annotation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah for me the general ones are the ones that should be available for all endpoints. So merging seems more appropriate to me.

Copy link
Member

@meyerbaptiste meyerbaptiste Apr 5, 2018

Choose a reason for hiding this comment

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

I like the idea to override the default formats, it's pretty useful (at the operation level at least) I think. IMHO, If we merge the configured formats, we also have to find a way to be able to completely override the default formats.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, thinking about additionalFormats and formats. The first just adds and the other overrides and you're done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok let's try to get this merged with formats only and I'll add additionalFormats in a following PR if @api-platform/core-team agrees. That will be pretty easy anyway

{
$this->negotiator = $negotiator;
$this->formats = $formats;
$this->formatsProvider = $formatsProvider;
Copy link
Contributor

Choose a reason for hiding this comment

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

You could remove the typehint and do something like this:

public function __construct(Negotiator $negotiator, $formatProvider)
{
    // BC
    if (\is_array($formatProvider)) {
        // deprecated notice but set anyway
        return;
    }
    if (!$formatProvider instanceof FormatsProviderInterface) {
        // throw new \InvalidArgumentException......
    }
}

@@ -51,6 +55,10 @@ public function onKernelRequest(GetResponseEvent $event)
return;
}

if (null !== $this->formatsProvider) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would need a comment to be removed once BC is removed for Version 3.


public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, array $formats)
public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, array $formats, FormatsProviderInterface $formatsProvider = null)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here with the constructor. I just want to omit we add another 5 arguments to the constructor because it makes migration from 2 to 3 harder.

@@ -129,6 +130,13 @@
*/
private $forceEager;

/**
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
Copy link
Contributor

Choose a reason for hiding this comment

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

In my PR (#1685) it was mentioned that the core team does not want to add more root annotation configuration values but instead put it into attributes. Just saying that this may need to be modified but I let @dunglas decide on this. I don't remember the exact reason for this anymore because I actually like when things are specific 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this just follows #1788, nothing more

@vincentchalamon vincentchalamon dismissed their stale review April 5, 2018 08:44

PR has been updated, new review to come

{
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->configuredFormats = $formats;
$this->formats = [];
Copy link
Member

Choose a reason for hiding this comment

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

public $formats = [];

if (!is_numeric($format)) {
$resourceFormats[$format] = (array) $value;
continue;
} elseif (array_key_exists($value, $this->configuredFormats)) {
Copy link
Member

Choose a reason for hiding this comment

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

if instead of elseif.

continue;
}

throw new InvalidArgumentException(sprintf("You either need to add the format '%s' to your project configuration or declare a mime type for it in your annotation.", $value));
Copy link
Member

Choose a reason for hiding this comment

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

Can you add the @throws annotation in the PHPDoc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


$resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);

if (!$formats = (array) $resourceMetadata->getOperationAttribute($attributes, 'formats', [], true)) {
Copy link
Member

Choose a reason for hiding this comment

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

About (array), is there a way to get something different from an array?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I guess by using "formats"="json" instead of "formats"={"json"}

Copy link
Contributor

Choose a reason for hiding this comment

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

But as the key is plural, the user could easily understand it expects an array. Otherwise if the value is not an array, maybe throw an exception? WDYT @meyerbaptiste?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can if you all prefer, it was almost costless

Copy link
Contributor Author

Choose a reason for hiding this comment

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

on a second thought, I buy the 'plural' argument, I've removed the typehint, added an exception and test.

return $this->configuredFormats;
}

$resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
Copy link
Member

Choose a reason for hiding this comment

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

What if resource_class is not set?

@antograssiot antograssiot changed the title [RFR] Allow to specify formats per resources/operations [WIP] Allow to specify formats per resources/operations Apr 6, 2018
@antograssiot antograssiot changed the title [WIP] Allow to specify formats per resources/operations [RFR] Allow to specify formats per resources/operations Apr 6, 2018
@antograssiot antograssiot force-pushed the format-by-resource branch 2 times, most recently from 1a75d55 to d19daa2 Compare April 16, 2018 09:20
@antograssiot
Copy link
Contributor Author

I'm not sure why circle-ci would try to test the code on PHP5.6 🤔 ...
@teohhanhui is it because of #1888 and 2.2 not being merged in master yet ?

@soyuka
Copy link
Member

soyuka commented Apr 26, 2018

try to force push

@antograssiot antograssiot force-pushed the format-by-resource branch 4 times, most recently from 199c17d to 2201faa Compare May 3, 2018 13:48
@antograssiot antograssiot force-pushed the format-by-resource branch from 2201faa to 615746f Compare May 5, 2018 19:32
@antograssiot antograssiot force-pushed the format-by-resource branch 2 times, most recently from 01e3b11 to 60706c9 Compare June 8, 2018 18:59
@antograssiot
Copy link
Contributor Author

I've rebased and updated with the last fixer rules. Anything else to be done on this one ?

@@ -58,7 +58,7 @@
<argument>%api_platform.title%</argument>
<argument>%api_platform.description%</argument>
<argument>%api_platform.version%</argument>
<argument>%api_platform.formats%</argument>
<argument type="service" id="api_platform.formats_provider"/>
Copy link
Member

Choose a reason for hiding this comment

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

missing space before />

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated 👍

Add unit tests

Update service injection with deprecation error and exception

Fix missing use of FormatsProvider into DocumentAction

Only support the shortest mime types declaration

Update service injection in documentation to trigger deprecation warning

Avoid another way to badly declare format overrding.

update phpdoc
@dunglas dunglas merged commit bd2eadb into api-platform:master Jun 9, 2018
@dunglas
Copy link
Member

dunglas commented Jun 9, 2018

Thanks!

@antograssiot antograssiot deleted the format-by-resource branch June 11, 2018 14:40
antograssiot added a commit to antograssiot/docs-1 that referenced this pull request Jun 16, 2018
antograssiot added a commit to antograssiot/docs-1 that referenced this pull request Jun 16, 2018
antograssiot added a commit to antograssiot/docs-1 that referenced this pull request Jun 16, 2018
dunglas pushed a commit to api-platform/docs that referenced this pull request Jun 18, 2018
* Add documentation on enabling format for specific operation

api-platform/core#1798

* Update content-negotiation.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants