diff --git a/core/lib/Thelia/Action/Tax.php b/core/lib/Thelia/Action/Tax.php index 4b21521851..09be076845 100644 --- a/core/lib/Thelia/Action/Tax.php +++ b/core/lib/Thelia/Action/Tax.php @@ -12,6 +12,8 @@ namespace Thelia\Action; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Thelia\Core\Event\Tax\TaxEvent; @@ -21,6 +23,14 @@ class Tax extends BaseAction implements EventSubscriberInterface { + private ServiceLocator $taxTypeLocator; + + public function __construct( + #[TaggedLocator('thelia.taxType')] ServiceLocator $taxTypeLocator + ) { + $this->taxTypeLocator = $taxTypeLocator; + } + public function create(TaxEvent $event, $eventName, EventDispatcherInterface $dispatcher): void { $tax = new TaxModel(); @@ -68,6 +78,14 @@ public function delete(TaxEvent $event): void } } + public function getTaxTypeService(TaxEvent $event): void + { + $tax = $event->getTax(); + if ($this->taxTypeLocator->has($tax->getType())) { + $event->setTaxTypeService($this->taxTypeLocator->get($tax->getType())); + } + } + /** * {@inheritDoc} */ @@ -77,6 +95,7 @@ public static function getSubscribedEvents() TheliaEvents::TAX_CREATE => ['create', 128], TheliaEvents::TAX_UPDATE => ['update', 128], TheliaEvents::TAX_DELETE => ['delete', 128], + TheliaEvents::TAX_GET_TYPE_SERVICE => ['getTaxTypeService', 128], ]; } } diff --git a/core/lib/Thelia/Core/Event/Tax/TaxEvent.php b/core/lib/Thelia/Core/Event/Tax/TaxEvent.php index c5dc695335..1dc85544c3 100644 --- a/core/lib/Thelia/Core/Event/Tax/TaxEvent.php +++ b/core/lib/Thelia/Core/Event/Tax/TaxEvent.php @@ -14,6 +14,7 @@ use Thelia\Core\Event\ActionEvent; use Thelia\Model\Tax; +use Thelia\TaxEngine\TaxTypeInterface; class TaxEvent extends ActionEvent { @@ -26,6 +27,8 @@ class TaxEvent extends ActionEvent protected $type; protected $requirements; + protected ?TaxTypeInterface $taxTypeService = null; + public function __construct(Tax $tax = null) { $this->tax = $tax; @@ -119,4 +122,16 @@ public function getRequirements() { return $this->requirements; } + + public function getTaxTypeService(): ?TaxTypeInterface + { + return $this->taxTypeService; + } + + public function setTaxTypeService(TaxTypeInterface $taxTypeService) + { + $this->taxTypeService = $taxTypeService; + + return $this; + } } diff --git a/core/lib/Thelia/Core/Event/TheliaEvents.php b/core/lib/Thelia/Core/Event/TheliaEvents.php index df010560de..d351b6738e 100644 --- a/core/lib/Thelia/Core/Event/TheliaEvents.php +++ b/core/lib/Thelia/Core/Event/TheliaEvents.php @@ -458,6 +458,7 @@ public static function getLoopExtendsEvent(string $eventName, string $loopName = public const TAX_CREATE = 'action.createTax'; public const TAX_UPDATE = 'action.updateTax'; public const TAX_DELETE = 'action.deleteTax'; + public const TAX_GET_TYPE_SERVICE = 'action.getTaxService'; // -- Profile management --------------------------------------------- diff --git a/core/lib/Thelia/Core/Thelia.php b/core/lib/Thelia/Core/Thelia.php index 2ae3839d9e..947a43991f 100644 --- a/core/lib/Thelia/Core/Thelia.php +++ b/core/lib/Thelia/Core/Thelia.php @@ -65,6 +65,7 @@ use Thelia\Model\Module; use Thelia\Model\ModuleQuery; use Thelia\Module\ModuleManagement; +use Thelia\TaxEngine\TaxTypeInterface; use TheliaSmarty\Template\SmartyParser; class Thelia extends Kernel @@ -459,6 +460,7 @@ protected function loadConfiguration(ContainerBuilder $container): void CouponInterface::class => 'thelia.coupon.addCoupon', ConditionInterface::class => 'thelia.coupon.addCondition', ControllerInterface::class => 'controller.service_arguments', + TaxTypeInterface::class => 'thelia.taxType', ]; foreach ($autoconfiguredInterfaces as $interfaceClass => $tag) { diff --git a/core/lib/Thelia/Form/TaxCreationForm.php b/core/lib/Thelia/Form/TaxCreationForm.php index 86b41231de..bb807351ee 100644 --- a/core/lib/Thelia/Form/TaxCreationForm.php +++ b/core/lib/Thelia/Form/TaxCreationForm.php @@ -12,6 +12,7 @@ namespace Thelia\Form; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Validator\Constraints; @@ -19,7 +20,6 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; use Thelia\Core\Translation\Translator; use Thelia\Model\Tax; -use Thelia\TaxEngine\TaxEngine; use Thelia\TaxEngine\TaxTypeRequirementDefinition; use Thelia\Type\TypeInterface; @@ -32,21 +32,24 @@ class TaxCreationForm extends BaseForm { use StandardDescriptionFieldsTrait; + private iterable $taxTypeIterator; + + public function __construct( + #[TaggedIterator('thelia.taxType')] iterable $taxTypeIterator + ) { + $this->taxTypeIterator = $taxTypeIterator; + } + protected static $typeList = []; protected function buildForm(): void { - $types = TaxEngine::getTaxTypeList(); - $typeList = []; $requirementList = []; - foreach ($types as $classname) { - $instance = new $classname(); - - $typeList[$instance->getTitle()] = Tax::escapeTypeName($classname); - - $requirementList[$classname] = $instance->getRequirementsDefinition(); + foreach ($this->taxTypeIterator as $taxType) { + $typeList[$taxType->getTitle()] = Tax::escapeTypeName($taxType::class); + $requirementList[$taxType::class] = $taxType->getRequirementsDefinition(); } $this->formBuilder diff --git a/core/lib/Thelia/Model/Tax.php b/core/lib/Thelia/Model/Tax.php index c8e9809afd..ea04014086 100644 --- a/core/lib/Thelia/Model/Tax.php +++ b/core/lib/Thelia/Model/Tax.php @@ -13,9 +13,12 @@ namespace Thelia\Model; use Propel\Runtime\Exception\PropelException; +use Propel\Runtime\Propel; +use Thelia\Core\Event\Tax\TaxEvent; +use Thelia\Core\Event\TheliaEvents; use Thelia\Exception\TaxEngineException; use Thelia\Model\Base\Tax as BaseTax; -use Thelia\TaxEngine\BaseTaxType; +use Thelia\Model\Map\TaxTableMap; class Tax extends BaseTax { @@ -45,22 +48,19 @@ public function getTaxRuleCountryPosition() public function getTypeInstance() { - $class = $this->getType(); + $eventDispatcher = Propel::getServiceContainer()->getWriteConnection(TaxTableMap::DATABASE_NAME)->getEventDispatcher(); + $taxEvent = new TaxEvent($this); + $eventDispatcher->dispatch($taxEvent, TheliaEvents::TAX_GET_TYPE_SERVICE); - /* test type */ - if (!class_exists($class)) { - throw new TaxEngineException('Recorded type `'.$class.'` does not exists', TaxEngineException::BAD_RECORDED_TYPE); - } - /** @var \Thelia\TaxEngine\BaseTaxType $instance */ - $instance = new $class(); + $typeService = $taxEvent->getTaxTypeService(); - if (!$instance instanceof BaseTaxType) { - throw new TaxEngineException('Recorded type `'.$class.'` does not extends BaseTaxType', TaxEngineException::BAD_RECORDED_TYPE); + if (!$typeService) { + throw new TaxEngineException('Recorded type `'.$this->getType().'` does not exists', TaxEngineException::BAD_RECORDED_TYPE); } - $instance->loadRequirements($this->getRequirements()); + $typeService->loadRequirements($this->getRequirements()); - return $instance; + return $typeService; } public function setRequirements($requirements) diff --git a/core/lib/Thelia/TaxEngine/BaseTaxType.php b/core/lib/Thelia/TaxEngine/BaseTaxType.php index 6082924533..781f3468b3 100644 --- a/core/lib/Thelia/TaxEngine/BaseTaxType.php +++ b/core/lib/Thelia/TaxEngine/BaseTaxType.php @@ -18,7 +18,7 @@ /** * @author Etienne Roudeix */ -abstract class BaseTaxType +abstract class BaseTaxType implements TaxTypeInterface { /** * A var <-> value array which contains TaxtType requirements (e.g. parameters). @@ -32,10 +32,8 @@ abstract class BaseTaxType * to use in tax calculation. * * For other tax types, this method shoud return 0. - * - * @return number */ - public function pricePercentRetriever() + public function pricePercentRetriever(): float { return 0; } @@ -44,10 +42,8 @@ public function pricePercentRetriever() * For constant amount tax type, return the absolute amount to use in tax calculation. * * For other tax types, this method shoud return 0. - * - * @return number */ - public function fixAmountRetriever(Product $product) + public function fixAmountRetriever(Product $product): float { return 0; } @@ -60,30 +56,23 @@ public function fixAmountRetriever(Product $product) * array( * 'percent' => new FloatType() * ); - * - * @return array of TaxTypeRequirementDefinition */ - public function getRequirementsDefinition() + public function getRequirementsDefinition(): array { return []; } - /** - * @return the name of this tax type - */ abstract public function getTitle(); - public function calculate(Product $product, $untaxedPrice) + public function calculate(Product $product, $untaxedPrice): float { return $untaxedPrice * $this->pricePercentRetriever() + $this->fixAmountRetriever($product); } /** * @throws TaxEngineException - * - * @return array return the requirements array */ - public function getRequirements() + public function getRequirements(): array { return $this->requirements; } @@ -111,7 +100,7 @@ public function loadRequirements($requirementsValues): void } } - public function setRequirement($key, $value) + public function setRequirement($key, $value): self { $this->requirements[$key] = $value; diff --git a/core/lib/Thelia/TaxEngine/TaxEngine.php b/core/lib/Thelia/TaxEngine/TaxEngine.php index cf1001f83d..6b145a7161 100644 --- a/core/lib/Thelia/TaxEngine/TaxEngine.php +++ b/core/lib/Thelia/TaxEngine/TaxEngine.php @@ -28,47 +28,16 @@ class TaxEngine { protected $taxCountry; protected $taxState; - protected $typeList; /** @var RequestStack */ protected $requestStack; - public function __construct(RequestStack $requestStack) - { + public function __construct( + RequestStack $requestStack, + ) { $this->requestStack = $requestStack; } - public static function getTaxTypeList() - { - $taxTypeDirectory = __DIR__.DS.'TaxType'; - $typeList = []; - try { - $directoryIterator = new \DirectoryIterator($taxTypeDirectory); - - foreach ($directoryIterator as $fileinfo) { - if ($fileinfo->isFile()) { - $extension = $fileinfo->getExtension(); - if (strtolower($extension) !== 'php') { - continue; - } - $className = $fileinfo->getBaseName('.php'); - - try { - $fullyQualifiedClassName = 'Thelia\\TaxEngine\\TaxType\\'.$className; - $instance = new $fullyQualifiedClassName(); - $typeList[] = \get_class($instance); - } catch (\Exception $ex) { - // Nothing special to do - } - } - } - } catch (\UnexpectedValueException $e) { - // Nothing special to do - } - - return $typeList; - } - /** * Find Tax Country Id * First look for a picked delivery address country diff --git a/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php index 1782ddf295..7f44a41a74 100644 --- a/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php @@ -34,7 +34,7 @@ public function setFeature($featureId) return $this; } - public function fixAmountRetriever(Product $product) + public function fixAmountRetriever(Product $product): float { $taxAmount = 0; $featureId = $this->getRequirement('feature'); @@ -64,7 +64,7 @@ public function fixAmountRetriever(Product $product) return $taxAmount; } - public function getRequirementsDefinition() + public function getRequirementsDefinition(): array { return [ new TaxTypeRequirementDefinition( diff --git a/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php index 65a0bd322a..61c94a910e 100644 --- a/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php @@ -13,6 +13,7 @@ namespace Thelia\TaxEngine\TaxType; use Thelia\Core\Translation\Translator; +use Thelia\Model\Product; use Thelia\TaxEngine\BaseTaxType; use Thelia\TaxEngine\TaxTypeRequirementDefinition; use Thelia\Type\FloatType; @@ -29,12 +30,12 @@ public function setAmount($amount) return $this; } - public function fixAmountRetriever(\Thelia\Model\Product $product) + public function fixAmountRetriever(Product $product): float { return $this->getRequirement('amount'); } - public function getRequirementsDefinition() + public function getRequirementsDefinition(): array { return [ new TaxTypeRequirementDefinition( diff --git a/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php index 95128ac77e..126dd2f794 100644 --- a/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php @@ -29,12 +29,12 @@ public function setPercentage($percent) return $this; } - public function pricePercentRetriever() + public function pricePercentRetriever(): float { return $this->getRequirement('percent') * 0.01; } - public function getRequirementsDefinition() + public function getRequirementsDefinition(): array { return [ new TaxTypeRequirementDefinition( diff --git a/core/lib/Thelia/TaxEngine/TaxTypeInterface.php b/core/lib/Thelia/TaxEngine/TaxTypeInterface.php new file mode 100644 index 0000000000..c724a63344 --- /dev/null +++ b/core/lib/Thelia/TaxEngine/TaxTypeInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Thelia\TaxEngine; + +use Thelia\Model\Product; + +/** + * @author Etienne Roudeix + */ +interface TaxTypeInterface +{ + /** + * For a price percent tax type, return the percentage (e.g. 20 for 20%) of the product price + * to use in tax calculation. + * + * For other tax types, this method shoud return 0. + */ + public function pricePercentRetriever(): float; + + /** + * For constant amount tax type, return the absolute amount to use in tax calculation. + * + * For other tax types, this method shoud return 0. + */ + public function fixAmountRetriever(Product $product): float; + + /** + * Returns the requirements definition of this tax type. This is an array of + * TaxTypeRequirementDefinition, which defines the name and the type of + * the requirements. Example :. + * + * array( + * 'percent' => new FloatType() + * ); + */ + public function getRequirementsDefinition(): array; + + public function getTitle(); + + public function calculate(Product $product, $untaxedPrice): float; + + public function getRequirements(): array; + + public function loadRequirements($requirementsValues): void; + + public function setRequirement($key, $value): self; + + public function getRequirement($key); +}