diff --git a/features/product/managing_product_attributes/deleting_product_attribute.feature b/features/product/managing_product_attributes/deleting_product_attribute.feature index d1eebb2c0b..37f489f72d 100644 --- a/features/product/managing_product_attributes/deleting_product_attribute.feature +++ b/features/product/managing_product_attributes/deleting_product_attribute.feature @@ -9,13 +9,13 @@ Feature: Deleting a product attribute And the store has a product "44 Magnum" @ui @api - Scenario: Try deleting a attribute from the registry when product use him + Scenario: Try deleting a attribute from the registry when a product uses it Given this product has a text attribute "Gun caliber" with value "11 mm" When I delete this product attribute Then I should be notified that it is in use @ui @api - Scenario: Deleting a text product attribute when not by used + Scenario: Deleting a text product attribute when it's not used Given the store has a text product attribute "Gun caliber" When I delete this product attribute Then I should be notified that it has been successfully deleted diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4a38d545b6..9ffaa2c6d2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -770,6 +770,26 @@ parameters: count: 1 path: src/Sylius/Bundle/ApiBundle/DataPersister/MessengerDataPersister.php + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:persist\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php + + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:remove\\(\\) has no return type specified\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php + + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:remove\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php + + - + message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductAttributeDataPersister\\:\\:supports\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php + - message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\DataPersister\\\\ProductTaxonDataPersister\\:\\:persist\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 @@ -2250,6 +2270,11 @@ parameters: count: 1 path: src/Sylius/Bundle/CoreBundle/CatalogPromotion/Checker/CatalogPromotionEligibilityChecker.php + - + message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\CatalogPromotion\\\\Checker\\\\InForTaxonsScopeVariantChecker\\:\\:__construct\\(\\) has parameter \\$taxonRepository with generic interface Sylius\\\\Component\\\\Taxonomy\\\\Repository\\\\TaxonRepositoryInterface but does not specify its types\\: T$#" + count: 1 + path: src/Sylius/Bundle/CoreBundle/CatalogPromotion/Checker/InForTaxonsScopeVariantChecker.php + - message: "#^Method Sylius\\\\Bundle\\\\CoreBundle\\\\CatalogPromotion\\\\Checker\\\\ProductVariantForCatalogPromotionEligibility\\:\\:__construct\\(\\) has parameter \\$locator with generic class Symfony\\\\Component\\\\DependencyInjection\\\\ServiceLocator but does not specify its types\\: T$#" count: 1 diff --git a/src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php b/src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php index f807ee76c8..85fdd466a4 100644 --- a/src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php +++ b/src/Sylius/Bundle/ApiBundle/DataPersister/ProductAttributeDataPersister.php @@ -7,17 +7,13 @@ namespace Sylius\Bundle\ApiBundle\DataPersister; use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved; -use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface; use Sylius\Component\Product\Model\ProductAttributeInterface; -use Sylius\Component\Registry\ServiceRegistryInterface; -use Webmozart\Assert\Assert; /** @experimental */ final class ProductAttributeDataPersister implements ContextAwareDataPersisterInterface { public function __construct( private ContextAwareDataPersisterInterface $decoratedDataPersister, - private ServiceRegistryInterface $attributeTypeRegistry, ) { } @@ -29,13 +25,6 @@ final class ProductAttributeDataPersister implements ContextAwareDataPersisterIn /** @param ProductAttributeInterface $data */ public function persist($data, array $context = []) { - if (null === $data->getStorageType() && null !== $data->getType()) { - /** @var AttributeTypeInterface $attributeType */ - $attributeType = $this->attributeTypeRegistry->get($data->getType()); - - $data->setStorageType($attributeType->getStorageType()); - } - return $this->decoratedDataPersister->persist($data, $context); } diff --git a/src/Sylius/Bundle/ApiBundle/EventSubscriber/AttributeEventSubscriber.php b/src/Sylius/Bundle/ApiBundle/EventSubscriber/AttributeEventSubscriber.php new file mode 100644 index 0000000000..f52943b662 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/EventSubscriber/AttributeEventSubscriber.php @@ -0,0 +1,60 @@ + ['assignStorageType', EventPriorities::PRE_VALIDATE], + ]; + } + + public function assignStorageType(ViewEvent $event): void + { + $attribute = $event->getControllerResult(); + $method = $event->getRequest()->getMethod(); + + if ( + !$attribute instanceof AttributeInterface || + !in_array($method, [Request::METHOD_POST, Request::METHOD_PUT], true) + ) { + return; + } + + if (null === $attribute->getType() || '' === $attribute->getType()) { + return; + } + + if ( + null !== $attribute->getStorageType() || + !$this->attributeTypeRegistry->has($attribute->getType()) + ) { + return; + } + + /** @var AttributeTypeInterface $attributeType */ + $attributeType = $this->attributeTypeRegistry->get($attribute->getType()); + + $attribute->setStorageType($attributeType->getStorageType()); + + $event->setControllerResult($attribute); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml index aa0a329656..00ae7067dd 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml @@ -55,7 +55,6 @@ - diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/event_subscribers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/event_subscribers.xml index 197917f45c..6e6b43702b 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/event_subscribers.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/event_subscribers.xml @@ -54,5 +54,10 @@ + + + + + diff --git a/src/Sylius/Bundle/ApiBundle/spec/DataPersister/ProductAttributeDataPersisterSpec.php b/src/Sylius/Bundle/ApiBundle/spec/DataPersister/ProductAttributeDataPersisterSpec.php index 88f71d505a..b9761c9362 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/DataPersister/ProductAttributeDataPersisterSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/DataPersister/ProductAttributeDataPersisterSpec.php @@ -17,17 +17,13 @@ use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use PhpSpec\ObjectBehavior; use Sylius\Bundle\ApiBundle\Exception\ProductAttributeCannotBeRemoved; -use Sylius\Component\Attribute\AttributeType\AttributeTypeInterface; -use Sylius\Component\Attribute\AttributeType\TextAttributeType; -use Sylius\Component\Attribute\Model\AttributeValueInterface; use Sylius\Component\Product\Model\ProductAttributeInterface; -use Sylius\Component\Registry\ServiceRegistryInterface; final class ProductAttributeDataPersisterSpec extends ObjectBehavior { - function let(ContextAwareDataPersisterInterface $persister, ServiceRegistryInterface $typesRegistry): void + function let(ContextAwareDataPersisterInterface $persister): void { - $this->beConstructedWith($persister, $typesRegistry); + $this->beConstructedWith($persister); } function it_is_a_context_aware_persister(): void @@ -41,32 +37,10 @@ final class ProductAttributeDataPersisterSpec extends ObjectBehavior $this->supports($productAttribute)->shouldReturn(true); } - function it_uses_inner_persister_to_persist_product_attribute_when_storage_type_is_set( + function it_uses_inner_persister_to_persist_product_attribute( ContextAwareDataPersisterInterface $persister, ProductAttributeInterface $productAttribute, ): void { - $productAttribute->getType()->willReturn(TextAttributeType::TYPE); - $productAttribute->getStorageType()->willReturn(AttributeValueInterface::STORAGE_TEXT); - - $persister->persist($productAttribute, [])->shouldBeCalled(); - - $this->persist($productAttribute, []); - } - - function it_sets_storage_type_when_none_is_set_but_type_is( - ContextAwareDataPersisterInterface $persister, - ServiceRegistryInterface $typesRegistry, - AttributeTypeInterface $attributeType, - ProductAttributeInterface $productAttribute, - ): void { - $productAttribute->getType()->willReturn(TextAttributeType::TYPE); - $productAttribute->getStorageType()->willReturn(null); - - $attributeType->getStorageType()->willReturn(AttributeValueInterface::STORAGE_TEXT); - $typesRegistry->get(TextAttributeType::TYPE)->willReturn($attributeType); - - $productAttribute->setStorageType(AttributeValueInterface::STORAGE_TEXT)->shouldBeCalled(); - $persister->persist($productAttribute, [])->shouldBeCalled(); $this->persist($productAttribute, []); diff --git a/src/Sylius/Bundle/ApiBundle/spec/EventSubscriber/AttributeEventSubscriberSpec.php b/src/Sylius/Bundle/ApiBundle/spec/EventSubscriber/AttributeEventSubscriberSpec.php new file mode 100644 index 0000000000..efd21254b0 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/EventSubscriber/AttributeEventSubscriberSpec.php @@ -0,0 +1,138 @@ +beConstructedWith($registry); + } + + function it_implements_event_subscriber_interface(): void + { + $this->shouldImplement(EventSubscriberInterface::class); + } + + function it_does_nothing_when_controller_result_is_not_an_attribute( + ServiceRegistryInterface $registry, + HttpKernelInterface $kernel, + Request $request, + ): void { + $request->getMethod()->shouldBeCalled(); + $registry->has(Argument::any())->shouldNotBeCalled(); + + $this->assignStorageType(new ViewEvent( + $kernel->getWrappedObject(), + $request->getWrappedObject(), + HttpKernelInterface::MAIN_REQUEST, + new \stdClass(), + )); + } + + function it_does_nothing_when_attribute_has_no_type( + ServiceRegistryInterface $registry, + HttpKernelInterface $kernel, + Request $request, + AttributeInterface $attribute, + ): void { + $request->getMethod()->willReturn(Request::METHOD_POST); + $attribute->getType()->willReturn(null); + + $registry->has(Argument::any())->shouldNotBeCalled(); + + $this->assignStorageType(new ViewEvent( + $kernel->getWrappedObject(), + $request->getWrappedObject(), + HttpKernelInterface::MAIN_REQUEST, + $attribute->getWrappedObject(), + )); + } + + function it_does_nothing_when_attribute_has_a_storage_type( + ServiceRegistryInterface $registry, + HttpKernelInterface $kernel, + Request $request, + AttributeInterface $attribute, + ): void { + $request->getMethod()->willReturn(Request::METHOD_POST); + $attribute->getType()->willReturn('text'); + $attribute->getStorageType()->willReturn('text'); + + $registry->has(Argument::any())->shouldNotBeCalled(); + + $this->assignStorageType(new ViewEvent( + $kernel->getWrappedObject(), + $request->getWrappedObject(), + HttpKernelInterface::MAIN_REQUEST, + $attribute->getWrappedObject(), + )); + } + + function it_does_nothing_when_attribute_type_is_not_registered( + ServiceRegistryInterface $registry, + HttpKernelInterface $kernel, + Request $request, + AttributeInterface $attribute, + ): void { + $request->getMethod()->willReturn(Request::METHOD_POST); + $attribute->getType()->willReturn('foo'); + $attribute->getStorageType()->willReturn(null); + + $registry->has('foo')->willReturn(false); + + $this->assignStorageType(new ViewEvent( + $kernel->getWrappedObject(), + $request->getWrappedObject(), + HttpKernelInterface::MAIN_REQUEST, + $attribute->getWrappedObject(), + )); + } + + function it_sets_storage_type_based_on_set_attribute_type( + ServiceRegistryInterface $registry, + HttpKernelInterface $kernel, + Request $request, + AttributeInterface $attribute, + AttributeTypeInterface $attributeType, + ): void { + $request->getMethod()->willReturn(Request::METHOD_POST); + $attribute->getType()->willReturn('foo'); + $attribute->getStorageType()->willReturn(null); + + $registry->has('foo')->willReturn(true); + $registry->get('foo')->willReturn($attributeType); + + $attributeType->getStorageType()->willReturn('bar'); + + $attribute->setStorageType('bar')->shouldBeCalled(); + + $this->assignStorageType(new ViewEvent( + $kernel->getWrappedObject(), + $request->getWrappedObject(), + HttpKernelInterface::MAIN_REQUEST, + $attribute->getWrappedObject(), + )); + } +}