-
-
Notifications
You must be signed in to change notification settings - Fork 906
How to add new entry in a ManyToMany? #1628
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
Comments
Writability on subresource is a wanted/needed/interesting feature indeed, it should not be too hard to implement now. Feel free to work on it if you have the time! |
@vincentchalamon that's great, am in the middle of similar problem and am trying to create custom api entry but with no success , can you please post an example of how you created custom action for PUT /events/{id}/register |
Hi @hassanjuniedi, my case changed a little bit as I need a more complex object to register a user to an event. I've created a In your case, I think you confuse the front routing and the api routing: you don't need to restrict the comment creation url under If you really need to send a POST request to products_add_comment:
path: /products/{id}/comments
controller: api_platform.action.placeholder
methods: [POST] Then, add an itemOperation on your Product entity: /**
* @ApiResource(attributes={...}, itemOperations={
* ...
* "add_comment"={"route_name"="products_add_comment"}
* })
*/
class Product Finally, you can manage everything in an EventSubcriber listening to services:
App\EventSubscriber\ProductsAddCommentEventSubscriber:
autoconfigure: true
autowire: true public function ProductsAddCommentEventSubscriber implements EventSubscriber
{
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
// ...Register events (be careful with priorities!)
public function deserializeCommentOnKernelRequest($event)
{
$request = $event->getRequest();
if ('products_add_comment' !== $request->attributes->get('_route')) {
return;
}
// Deserialize Comment from $request->getContent();
// Validate Comment, then throw an exception (status code 400) if invalid
$request->attributes->set('data', $comment);
}
public function saveCommentOnKernelView($event)
{
$request = $event->getRequest();
if ('products_add_comment' !== $request->attributes->get('_route')) {
return;
}
// Persist/flush Comment from $event->getControllerResult();
}
} In order to use API Platform EventSubscribers to serialize your Comment object & prepare Response, you may need to add some parameters in your route, like I let you do the rest of the code 😉Let me know if you have any question about this example. |
@vincentchalamon thanks for your quick reply and helpful explanation,but i think i need something little different . /comments is working fine but in may case i use many-to-many relationship between product and comment to use join table to link product with comments
i just need a way to pass comment @id and product @id so i can add comment to product and persist it to database (in join table). i hope that i delivered my issue clearly, thanks in advance . |
OK so maybe you should use a meta-model like: class Comment
{
private $objectId;
private $objectClass;
private $author;
private $createdAt;
private $updatedAt;
private $comment;
public function setObject($object)
{
$this->setObjectId($object->getId());
$this->setObjectClass(get_class($object));
}
} Then, you could send a POST request to {
"comment": "Lorem ipsum dolor sit amet"
} In this example, public function ProductsAddCommentEventSubscriber implements EventSubscriber
{
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
// ...Register events (be careful with priorities!)
public function deserializeCommentOnKernelRequest($event)
{
$request = $event->getRequest();
if ('products_add_comment' !== $request->attributes->get('_route')) {
return;
}
// Deserialize Comment from $request->getContent();
// If you set correctly the priorities, you should have a `data` request attribute which correspond to the Product (or related object)
$comment->setObject($request->attributes->get('data'));
$request->attributes->set('comment', $comment);
}
} class ProductAddComment
{
/**
* @Route(
* name="product_add_comment",
* path="/products/{id}/add-comment",
* defaults={
* "_api_resource_class"=Comment::Class,
* "_api_item_operation_name"="add_comment",
* "_api_receive"="false"
* }
* )
* @Method("PUT")
*/
public function __invoke(Product $data, Comment $comment): Comment
{
return $comment;
}
} Note: I didn't have the time & opportunity to test this code, so I hope it'll work. Otherwise, it illustrates the approach :-) |
Thank you a lot , i will try this approach and let you know . |
I tried to get the request object using event listener and i succeed with that approach , but then i figure out a better approach as shown in the code below.
|
👍 but your code could be optimized ;-)
//AppBundle/Action/PutComments.php
final class PutComments
{
/**
* @Route(
* name="product_put_comments",
* path="/products/{id}/comments.{_format}",
* defaults={
* "_format"="jsonld",
* "_api_resource_class"=Product::Class,
* "_api_item_operation_name"="put_comments",
* }
* )
* @Method("PUT")
*/
public function __invoke(Request $request, $format, SerializerInterface $serializer, ValidatorInterface $validator, ManagerRegistry $registry, Product $product)
{
$content = $request->getContent();
if (empty($content)) {
// Here you should throw an exception if the content is empty
}
$comment = $serializer->deserialize($content, Comment::class, $format);
$errors = $validator->validate($comment);
if (0 < count($errors)) {
$output = array();
foreach ($errors as $error) {
$output[$error->getPropertyPath()][] = $error->getMessage();
}
return new JsonResponse($output, 400);
}
$product->addComment($comment);
$em = $registry->getManagerForClass(Comment::class);
$em->persist($comment);
$em->flush();
return $product;
}
} |
I love the idea, but let's not further misuse |
I'm currently working on a PR to implement POST & DELETE requests on an ApiSubresource. But, as @dunglas said, it's out of the REST concept. What do you think @soyuka @teohhanhui @api-platform/core-team: should I continue working on it, or would we consider to not implement such borderline feature in API Platform? |
Ping @api-platform/core-team @dunglas @soyuka @alanpoulain |
IMHO: if there is a real need, then it should be done, even if it's not REST. But we should document this feature as to be avoided if possible. |
I already experienced this need 2 or 3 times in differents projects, and I also talked about it with other API Platform developers who had the same need. Finally, everybody create a custom controller... But I think it could easily be managed by something generic in API Platform. |
If it's not too much work to add this in a "generic way" I'm 👍. Also, this feature is a high demanded one. |
@soyuka What do you think about the REST concept in this feature? |
It's borderline when it comes to REST but I think that we can still provide this feature. |
The semantics seem fine to me if it's just POST and DELETE. @soyuka What's your concern on the REST-fulness? |
If I understand correctly, the IRI must stay the same (e.g. |
@teohhanhui The requests will be:
I'm not sure about the syntax of the second scenario: "Add an existing RelatedDummy to a Dummy" |
My only REST related concern is that we may loose the context of the root resource. Anyway, that's related to subresources in general and as we already have read support adding write support seems to be the correct next step. |
What do you mean? Currently, in my developments, I'm adding the |
when requesting |
@vincentchalamon @alanpoulain Thanks again for bring me in. To pick up and continue from the PR I raised in #2598 and yours in #2428. Many-to-many (e.g., User-to-Event) is an interesting use case. Is there any reason we shouldn't / couldn't do PUT? (I have read through the thread but couldn't find the rationale behind it) So, here is my thought on the use cases discussed here.
As far as I know, subresource is just another resource in REST (which operation(s) is done on relation with other resource(s) instead of on its own) even though there is no written standard of it. So, I do believe subresource is REST-ful by nature. I could be wrong though.
If need helps to implement, I'm more than happy to look at them. (I can't tell how much time I can spend on on a regular basis, but I can promise I'm persisted) A project I'm working on has been working around some subresource use cases (and edge cases) and I don't like building up tech debts day by day. So, what do you guys think? I would love to know and discuss on them. |
Hi @torreytsui, thanks for your feedback! I'll try to answer your questions as I can.
Since subresources are available in API Platform, people use them in a more complex and deep way than expected. Adding POST & DELETE operations on subresource will extend the subresources capabilities for specific reasons (cf. previous comments), but may also increase the complexity of the subresources. We should be careful about this complexity when adding new features on it, and lock them to their only topic. That's also why I would lock the POST & DELETE operation to collection subresources only, to prevent any bad use. Adding PUT request on a subresource could be an interesting feature in some specific cases, but may be mis-used most of the time. Users must call the object PUT operation instead of updating it through another relation.
The difference between an item & a collection operation is the presence of an id attribute in the uri in item operations, that's why POST is a collection operation. Adding a subresource id attribute in this POST operation will break this rule. But it's already possible in API Platform to reference an object through its identifier in the body: embedded references. For instance:
Using
For ManyToMany operations (
Adding
There is a maxDepth in the ApiSubresource for the same reason ;-)
And I would be very happy to work with you on this feature :-) I took a look at your PR, you implemented an interesting approach of this feature. It would be awesome if we could merge both PRs and handle together the differents tasks for it (feel free to complete this list as a common reference for our work):
I excluded the refacto of the ApiSubresource cause it must be done in another issue, where I'll include you of course :-) I'd be very happy to talk with you about it. Don't hesitate to ping me directly on Slack too. |
Allowing POST/PUT/DELETE on ManyToMany subresource is going to be an awesome feature! I just encountered this need in my project. So far implementing it using an array of IRIs. |
The correct semantics of We've just had Json Merge Patch support in 2.5 / But the subresources feature needs a major overhaul. See #2706 |
FYI, the There are 2 supported PATCH formats, for now:
|
Hello , is this feature implemented or not yet ? Thanks |
Any updates on this?? :-) If you guys need help I might have time to do a PR, but would need some guidance. |
Hi @gonzalovilaseca, Thanks for proposing your help and time :-) This issue has be moved to this RFC: #2706. We're currently trying to organize a hackaton with the core-team to fix this issue as soon as possible. We'll let you know if we need some help, or when the feature will be released. |
I have a User entity linked with a bidirectional ManyToMany association to an Event entity: an event have users registered, and it's possible to get a user events through an ApiSubresource.
When a user wants to register to an event, I should update the event to add the user:
This could also be done by adding the event to the user:
In the first example, I'll expose the users id, which could be a security issue. In the second example, I'll have to find all the user events id which is not optimized.
To optimize this process, I've created a custom entry-point
PUT /events/{id}/register
to register the current user to the event.It would be awesome to allow to send a write request to my ApiSubresource to add an event without reminding all previous events, for instance:
And to remove an event from this ApiSubresource:
DELETE /users/12345/events/67890
What do you think @api-platform/core-team ?
The text was updated successfully, but these errors were encountered: