Skip to content
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

Fix to allow adding products with a customer option to the cart #134

Merged

Conversation

shochdoerfer
Copy link
Contributor

Fixes #131. I haven't yet added or fixed the tests as I wanted first to get the OK to go this route. Worked fine in my test environment.

Initially, the plan was to use the Pre Add Cart event, but somehow that event also fired too late, which meant the needed data was not available in the equals() method. So the only option I found was to implement a custom CartItemFactory and configure Sylius to use that instead of the default implementation. Sadly, the Sylius default implementation is marked with final which means, I had to c&p the default logic and customize it with the logic from the AddToCartListener. This could potentially lead to problems when Sylius decides to modify their default implementation.

@shochdoerfer
Copy link
Contributor Author

shochdoerfer commented Jun 20, 2023

Ideally, we could improve the equals() method a bit. I am thinking of introducing a checksum for the OrderItemOptionInterface, but I couldn't think of a reliable algorithm. That way we could simply compare the checksum of both objects without constantly iterrating over the configuration arrays.

@mamazu
Copy link
Collaborator

mamazu commented Jun 20, 2023

First of all thanks for your contribution. It makes sense to move this to the OrderItemFactory. There are just two things I would suggest changing:

Use the decoration

I don't know if you have heard of the concept of decorating a class. Here is the symfony documentation for it: https://symfony.com/doc/current/service_container/service_decoration.html

But in essence what you want is you want to add functionality to a class but still keep the old functionality like so:

class NewFactory {
    public function __construct(private FactoryInterface $oldFactory) {}

    public function something() {
        // Some code before
        $this->oldFactory->something();
        // Some code after
    }
} 

If you need help with that just ask again. But that could avoid code duplication.

Check where the createForProduct is used

I am not sure that this method is the best place for this as this could also be used by fixtures as well. Yes, it doesn't break but I think a method named createForProductWithCustomerOptions would probably be more fitting in naming and might even allow you to make the thing more request specific.

@shochdoerfer
Copy link
Contributor Author

@mamazu thx for your feedback.

I've read about service decoration but assumed since the Sylius class is final it might not work. Will give it a try and report back.

createForProduct is used by the Sylius configuration for these routes:

  • sylius_paypal_plugin_create_paypal_order_from_product
  • sylius_admin_product_variant_create
  • sylius_shop_ajax_cart_add_item
  • sylius_shop_partial_cart_add_item

In code createForProduct is used in the ProductVariantGenerator class and the respective spec files.

I'd be fine doing the additional checks in the createForProduct method, if you feel it makes more sense to have a custom method, I can tackle that.

@mamazu
Copy link
Collaborator

mamazu commented Jun 21, 2023

Yeah in the list of usages: sylius_admin_product_variant_create doesn't really fit in there.

That's why I thought that this might not be the right place to add it. I am not sure if there is a better way to add info onto cart items.

@shochdoerfer
Copy link
Contributor Author

Ok, then I'll add a new method in the factory and add the configuration to overwrite the factories for the sylius_shop_ajax_cart_add_item and sylius_shop_partial_cart_add_item routes. I think we can ignore sylius_paypal_plugin_create_paypal_order_from_product as this only takes the product Id as input, if I remember correctly.

@mamazu
Copy link
Collaborator

mamazu commented Jun 21, 2023

That sounds like a better approach to be honest. Sorry I am not much help with it since it's been a long time and we only use this bundle headless these days.

@shochdoerfer
Copy link
Contributor Author

No worries. At least we have a plan now. Need to check when I have more time to finalize this.

@shochdoerfer
Copy link
Contributor Author

Quick update: I still need to find the time to finish the PR. It's still on my todo list, but it will take a while until I am able to tackle this.

@shochdoerfer shochdoerfer force-pushed the feature/fix_add_product_to_cart branch from 09f95f9 to 1bf41e1 Compare December 28, 2023 09:05
@shochdoerfer
Copy link
Contributor Author

@mamazu I've updated/refactored the branch. Also, I've rebased the branch with the latest changes from master.

Decorating the default CartItemFactory was a bit tricky as I ran into a dependency cycle problem because I needed to decorate and overwrite the service at the same time (with the same id). That's why I must instanciate the default CartItemFactory in the custom CartItemFactory class I added to this project. Not ideal, but it works.

I've also updated the README with the route definitions to overwrite the Sylius ones to be able to call the createForProductWithCustomerOption factory method.

Copy link
Collaborator

@mamazu mamazu left a comment

Choose a reason for hiding this comment

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

Your merge request looks good. I am however a little concerned about your approach to the dependency injection. Could you explain what the issue is.
Because by default if you decorate a service then the old name will be redirected to the new service as well.

Comment on lines 30 to 35
private RequestStack $requestStack,
private EntityManagerInterface $entityManager,
private OrderItemOptionFactoryInterface $orderItemOptionFactory,
private OrderProcessorInterface $orderProcessor,
private CustomerOptionRepositoryInterface $customerOptionRepository,
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason we are not using constructor property promotion here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question ;) Not sure why I went this way in the original PR but we could totally switch to constructor property promotion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That would be great if you could do that.


<service
class="Brille24\SyliusCustomerOptionsPlugin\Factory\CartItemFactory"
id="sylius.custom_factory.order_item"
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have a naming schema. brille24.factory.order_item would probably fit better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMHO I need the Sylius ID here to overwrite the service definition. Other services rely on that ID, that's why I picked it. Or am I missing something?

Copy link

Choose a reason for hiding this comment

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

@shochdoerfer this is a decorator. You do not need to overwrite the service ID. The decorator gets its own id. And when the service it decorates is used, the decorator is applied automatically. Or symfony replaces the old one with the decorated one automatically? Anyway, looking at the docs and recalling my experience with decorators, I am certain you do not overwrite the service ID in the config. You just need to configure what service you intend to decorate.
Have you tried using the id @mamazu suggested? I think that should work just fine.

https://symfony.com/doc/6.4/service_container/service_decoration.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@seizan8 that makes sense. Can't recall why it did not work for me or it looked like it is not working.

I've fixed the issue locally already. Currently checking how to best fix the tests as things changed quite a bit.

@shochdoerfer
Copy link
Contributor Author

Let me give you some more context why I did the things I did. The original service definition from Sylius looks like this:

<service id="sylius.custom_factory.order_item" class="Sylius\Component\Core\Factory\CartItemFactory" decorates="sylius.factory.order_item" decoration-priority="256" public="false">
	<argument type="service" id="sylius.custom_factory.order_item.inner" />
	<argument type="service" id="sylius.product_variant_resolver.default" />
</service>

I tried to decorate it like this:

<service
		class="Brille24\SyliusCustomerOptionsPlugin\Factory\CartItemFactory"
		id="sylius.custom_factory.order_item"
		decorates="sylius.custom_factory.order_item"
		decoration-priority="255"
		public="false"
>
	<argument type="service" id="sylius.custom_factory.order_item"/>
	<argument type="service" id="sylius.product_variant_resolver.default"/>
	<argument type="service" id="request_stack"/>
	<argument type="service" id="brille24.factory.order_item_option"/>
	<argument type="service" id="brille24.repository.customer_option"/>
</service>

That leads to the following error when Symfony tries to compile the container definition: An alias cannot reference itself, got a circular reference on "sylius.custom_factory.order_item". It felt the only option I have is to go the route I took but maybe I am missing something.

@mamazu
Copy link
Collaborator

mamazu commented Jan 5, 2024

Try this:

<service
		class="Brille24\SyliusCustomerOptionsPlugin\Factory\CartItemFactory"
                id="brille24.factory.order_item"
		decorates="sylius.custom_factory.order_item"
		decoration-priority="255"
		public="false"
>
	<argument type="service" id="brille24.factory.order_item.inner"/>
	<argument type="service" id="sylius.product_variant_resolver.default"/>
	<argument type="service" id="request_stack"/>
	<argument type="service" id="brille24.factory.order_item_option"/>
	<argument type="service" id="brille24.repository.customer_option"/>
</service>

This will create a new service called brille24.factory.order_item and it will replace the sylius.custom_factory.order_item as well. To get access to the old service (the Sylius service) you can use brille24.factory.order_item.inner.

@shochdoerfer
Copy link
Contributor Author

@mamazu I tried that but somehow the brille24.factory.order_item.inner reference gives me a Sylius\Component\Resource\Factory\FactoryInterface instance and not \Sylius\Component\Core\Factory\CartItemFactory. Not sure why. The sylius.custom_factory.order_item service seems to be marked with public=false - maybe that is why.

Anyway, I've pushed a few more commits incl. a test for the CartItemFactory class. Let me know what's more needed to get this PR merged.

@mamazu
Copy link
Collaborator

mamazu commented Jan 5, 2024

Yeah, this might be because Sylius also decorates this. But yeah, I'm happy to merge this either way now. The public=false is the default. This means that you can't access the service with $container->get(...) when the container is compiled.

@shochdoerfer
Copy link
Contributor Author

Awesome! Thx for the good collaboration!

@mamazu mamazu merged commit cd4282f into Brille24:master Jan 5, 2024
0 of 6 checks passed
@t-n-y
Copy link

t-n-y commented Jan 5, 2024

Thx a lot for the fix.
However, is there a reason why doctrine/dbal still conflict with version ^3.0 ?

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

Successfully merging this pull request may close these issues.

After adding the bundle, I am not able to add products with a customer option to the cart
4 participants