-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add documentation about doctrine query extensions #120
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
Conversation
|
see #107 |
|
+1 should've written |
core/extensions.md
Outdated
| @@ -0,0 +1,164 @@ | |||
| # Extensions | |||
|
|
|||
| API Platform Core provides a system to extends queries on items and collections readings. | |||
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.
extends [...] readings
core/extensions.md
Outdated
| # Extensions | ||
|
|
||
| API Platform Core provides a system to extends queries on items and collections readings. | ||
| You can create custom extension that fit your needs. |
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.
a custom [...]
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.
This sentence can be removed, it doesn't add something useful.
core/extensions.md
Outdated
|
|
||
| ## Custom Extension | ||
|
|
||
| If Doctrine ORM support is enabled, adding extension is as easy as registering a service in your `app/config/services.yml` file and create the extension you need. |
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.
adding an extension
create the class?
core/extensions.md
Outdated
|
|
||
| If Doctrine ORM support is enabled, adding extension is as easy as registering a service in your `app/config/services.yml` file and create the extension you need. | ||
|
|
||
| Custom extension can be written by implementing the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface` |
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.
A custom extension must implement ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface and/or ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface interfaces
core/extensions.md
Outdated
|
|
||
| Custom extension can be written by implementing the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface` | ||
| and / or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface` | ||
| interfaces, depending on your whether you asking for a collection of items or just an item. |
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.
depending if you are asking for
core/extensions.md
Outdated
| } | ||
|
|
||
| /** | ||
| * Add where condition |
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.
Adds the WHERE condition.
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.
Or maybe that this docblock can be removed from the doc as it contains no useful information (typehints are enough).
| } | ||
|
|
||
| $classMetadata = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass); | ||
| $user = $this->token->getToken()->getUser(); |
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 think you should test if $user is an instance of User because IIRC Symfony will return the string 'anon.' when the user isn't logged in.
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.
Alternatively you can add a firewall rule to check that the user is connected.
core/extensions.md
Outdated
| $mapping = $classMetadata->associationMappings[$association]; | ||
|
|
||
| if (User::class === $mapping['targetEntity']) { | ||
| $queryBuilder->andWhere('o.'.$association.' = '.$user->getId()); |
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 must use parameters. This is a potential security issue.
Btw, why do you need to access to the class metadata? Can't you hardcode o.user? As it's a code you control I think it's ok in term of design and it will be less complex.
core/extensions.md
Outdated
| - '@security.token_storage' | ||
| - '@security.authorization_checker' | ||
| tags: | ||
| - { name: api_platform.doctrine.orm.query_extension.collection, priority: 64 } |
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.
Why do we need to add a priority? If it is necessary, it should be explained. If it's not, it can be removed.
core/extensions.md
Outdated
| - { name: api_platform.doctrine.orm.query_extension.item, priority: 64 } | ||
| ``` | ||
| Having the item related tag and interface, allows you to customize the query when trying to get/read a specific Item. |
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 would say:
Thanks to the api_platform.doctrine.orm.query_extension.collection tag, API Platform will register this service as a collection extension. The api_platform.doctrine.orm.query_extension.item do the same thing for items.
core/extensions.md
Outdated
| $queryBuilder->andWhere('o.'.$association.' = '.$user->getId()); | ||
| return; | ||
| } | ||
| if (Offer::class === $resourceClass && !$this->authorizationChecker->isGranted('ROLE_ADMIN')) { |
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 would keep the guard clause. It reduce the code complexity and improve its readability.
core/extensions.md
Outdated
| if (Offer::class === $resourceClass && !$this->authorizationChecker->isGranted('ROLE_ADMIN')) { | ||
| $user = $this->token->getToken()->getUser(); | ||
| $rootAlias = $queryBuilder->getRootAliases()[0]; | ||
| $queryBuilder->andWhere(sprintf('%s.user = ', $rootAlias).$user->getId()); |
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.
It's still a security issue and a bad practice. You have to use parameters: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html#binding-parameters-to-your-query
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.
Using parameters will also allow the DBMS to keep the prepared query in cache.
|
I made the changes, i'm ready to squash. |
core/extensions.md
Outdated
| - { name: api_platform.doctrine.orm.query_extension.item } | ||
| ``` | ||
| Thanks to the api_platform.doctrine.orm.query_extension.collection tag, API Platform will register this service as a collection extension. |
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.
`api_platform.doctrine.orm.query_extension.collection `
core/extensions.md
Outdated
| ``` | ||
| Thanks to the api_platform.doctrine.orm.query_extension.collection tag, API Platform will register this service as a collection extension. | ||
| The api_platform.doctrine.orm.query_extension.item do the same thing for items. |
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.
`api_platform.doctrine.orm.query_extension.item`
core/extensions.md
Outdated
| Thanks to the api_platform.doctrine.orm.query_extension.collection tag, API Platform will register this service as a collection extension. | ||
| The api_platform.doctrine.orm.query_extension.item do the same thing for items. | ||
| Notice the priority level for the Collection tag. |
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.
api_platform.doctrine.orm.query_extension.collection tag
core/extensions.md
Outdated
| The api_platform.doctrine.orm.query_extension.item do the same thing for items. | ||
| Notice the priority level for the Collection tag. | ||
| There is a case, when an extension implements the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface` or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface` and supports to immediately return results, |
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.
"There is a case" can be removed
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.
[...] interface to return results by itself,
core/extensions.md
Outdated
| Notice the priority level for the Collection tag. | ||
| There is a case, when an extension implements the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface` or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface` and supports to immediately return results, | ||
| any lower priority extension will not be executed. | ||
| In our case, since the pagination is activated by default ([see how to disable the pagination](pagination.md#disabling-the-pagination)) and the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\PaginationExtension` is declared with a priority 8, we must declare a priority to at least 9 to ensure it's execution. |
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.
Because the pagination is enabled by default
a priority of 8
the priority of the app.doctrine.orm.query_extension.current_user service must be at least 9 to ensure its execution.
core/extensions.md
Outdated
| API Platform Core provides a system to extend queries on items and collections. | ||
|
|
||
| Extensions are specific to Doctrine, and therefor, the Doctrine ORM support must be enabled. | ||
| If you use custom providers, they should support extensions and be aware of active extensions OR implement their own extension systems. |
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 would say: If you use custom providers it's up to you to implement your own extension system or not.
core/extensions.md
Outdated
| If you use [custom data providers](data-providers.md), they must support extensions and be aware of active extensions to work | ||
| properly. | ||
|
|
||
| ## Filter upon the current user |
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 would change by Example
core/extensions.md
Outdated
| { | ||
| // ... | ||
| } | ||
|
|
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.
Extra blank line to remove
core/extensions.md
Outdated
|
|
||
| //... | ||
| } | ||
|
|
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.
extra blank line to remove
| private function addWhere(QueryBuilder $queryBuilder, string $resourceClass) | ||
| { | ||
| $user = $this->token->getToken()->getUser(); | ||
| if ($user instanceof User && Offer::class === $resourceClass && !$this->authorizationChecker->isGranted('ROLE_ADMIN')) { |
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 need to do something if $user is not an instance of User: adding something like where 1=2 to the query or (better) mention that the security component must be used to require a user to be authenticated to access this route.
core/extensions.md
Outdated
|
|
||
| ## Custom Extension | ||
|
|
||
| Adding an extension is as easy as registering a service in your `app/config/services.yml` file and create the class you need. |
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 think this whole paragraph can be removed.
core/extensions.md
Outdated
|
|
||
| Custom extension must implement the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface` | ||
| and / or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface` | ||
| interfaces, depending if you are asking for a collection of items or just an item. |
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.
, to be run when querying for a collection of items and when querying for an item respectively.
core/extensions.md
Outdated
| API Platform Core provides a system to extend queries on items and collections. | ||
|
|
||
| Extensions are specific to Doctrine, and therefore, the Doctrine ORM support must be enabled to use this feature. | ||
| If you use custom providers it's up to you to implement your own extension system or not. |
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.
Avoid this kind of formatting in Markdown. If it's supposed to be the same paragraph, then there should not be a line break, and should continue immediately after use this feature. on the previous line.
If you want a new paragraph, an extra line break is necessary (1 blank line in between).
If instead you want it to simply start on the next line, it's necessary to have 2 trailing spaces on the previous line. (But I don't think we want this.)
core/extensions.md
Outdated
|
|
||
| Adding an extension is as easy as registering a service in your `app/config/services.yml` file and create the class you need. | ||
|
|
||
| Custom extension must implement the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface` |
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.
Custom extensions
| ## Example | ||
|
|
||
| In the following example, we will see how to always get the offers owned by the current user. We will set up an exception, whenever the user has the `ROLE_ADMIN`. | ||
| Given these two entities: |
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.
same concern about formatting here... (and many other places below)
core/extensions.md
Outdated
|
|
||
| Notice the priority level for the `api_platform.doctrine.orm.query_extension.collection` tag. When an extension implements the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface` or the `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface` interface to return results by itself, any lower priority extension will not be executed. Because the pagination is enabled by default with a priority of 8, the priority of the `app.doctrine.orm.query_extension.current_user` service must be at least 9 to ensure its execution. | ||
|
|
||
| ### Note |
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 suggest: ### Blocking Anonymous Users
core/extensions.md
Outdated
|
|
||
| ### Note | ||
|
|
||
| This example adds a WHERE condition only when a fully authenticated user without ROLE_ADMIN tries to access to a resource. That mean it return without restriction any item or collection for every other user. You need to ensure that your user is authenticated to access the two endpoints. |
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.
a `WHERE` clause [...] without the `ROLE_ADMIN` role [...]. It means that anonymous users will be able to access to all data. To prevent this potential security issue, the API must ensure that the current user is authenticated.
core/extensions.md
Outdated
|
|
||
| This example adds a WHERE condition only when a fully authenticated user without ROLE_ADMIN tries to access to a resource. That mean it return without restriction any item or collection for every other user. You need to ensure that your user is authenticated to access the two endpoints. | ||
|
|
||
| A way of doing it, is with the access control : |
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.
To secure the access to endpoints, use the following access control rule:
(No space before the colon)
core/extensions.md
Outdated
| security: | ||
| # ... | ||
| firewalls: |
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 remove this whole block (the # ... before is enough).
4fe0268 to
f2efd9e
Compare
|
@GregoireHebert can you rebase please? |
toofff
left a comment
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.
After your rebase.
Change Previous chapter in file core/serialization-groups-and-relations.md by Previous chapter: [Extensions](extensions.md)
core/extensions.md
Outdated
| - { path: ^/users, roles: IS_AUTHENTICATED_FULLY } | ||
| ``` | ||
|
|
||
| Previous chapter: [Filters](filters.md) Next chapter: [Serialization Groups and Relations](serialization-groups-and-relations.md) No newline at end of file |
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.
Add break line between Previous chapter and Next chapter, please.
index.md
Outdated
| 7. [Serialization Groups and Relations](core/serialization-groups-and-relations.md) | ||
| 7. [Extensions](core/extensions.md) | ||
| 1. [Custom Extension](core/extensions.md#custom-extension) | ||
| 2. [Filter upon the current user](core/extensions.md#filter-upon-the-current-user) |
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.
Where is a tltle Filter upon the current user in the file core/extensions.md?
|
|
||
| If you use [custom data providers](data-providers.md), they must support extensions and be aware of active extensions to work properly. | ||
|
|
||
| ## Example |
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.
It should really be a level 2 title?
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 level would you use ?
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.
Exemple
blabla
Exemple
blabla maybe ?
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.
Yes, i prefer ### Example @Simperfit
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.
But if the title in the file /index.md is Filter upon the current user then it is necessary to put ## Filter upon the current user instead of ## Example.
And change anchor in the file /index.md.
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.
Index.md has a "Filter upon the current user" chapter, because it is what it is, it suits better than "Example" when that what a user wants to do, he finds it instantly. that's why the "Filter upon the current user" is linked with the example anchor (By the way, it used to be a "filter upon the current user" anchor, but @dunglas made me change it for "Example". For the anchor level, since it appear on the summary, I thought it had to be the same level than the previous anchor.
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.
ok
f2efd9e to
e15c723
Compare
|
seems good to me ! |
Simperfit
left a comment
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.
Thanks about that @GregoireHebert
|
|
||
| If you use [custom data providers](data-providers.md), they must support extensions and be aware of active extensions to work properly. | ||
|
|
||
| ## Example |
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.
Exemple
blabla
Exemple
blabla maybe ?
| use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||
| use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; | ||
|
|
||
| final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface |
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.
@api-platform/core-team IIUC the docs should contain best practices, is it one to implements both in one class ?
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.
It's ok to me for this use case. Creating two classes with duplicated code isn't ok however. So I would keep it as is.
e15c723 to
2d805d7
Compare
index.md
Outdated
| 1. [Creating Custom Doctrine ORM Filters](core/filters.md#creating-custom-doctrine-orm-filters) | ||
| 2. [Overriding Extraction of Properties from the Request](core/filters.md#overriding-extraction-of-properties-from-the-request) | ||
| 6. [Serialization Groups and Relations](core/serialization-groups-and-relations.md) | ||
| 6. [Extensions](core/extensions.md) |
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.
Can you move this section after Data Providers? It's an advanced topic, it should not be too early in the book.
|
TOC changed :) |
|
Thank you @GregoireHebert! |
No description provided.