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

Add debug command #197

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@
<tag name="console.command" />
</service>

<service id="webgriffe_sylius_akeneo.command.debug" class="Webgriffe\SyliusAkeneoPlugin\Command\DebugCommand">
<argument type="service" id="webgriffe_sylius_akeneo.api_client"/>
<argument type="service" id="sylius.repository.product_attribute"/>
<argument type="service" id="sylius.repository.product_option"/>
<argument type="service" id="service_container"/>
<argument type="service" id="property_accessor"/>
<argument type="service" id="sylius.factory.product"/>
<argument type="service" id="sylius.factory.product_variant"/>
<argument type="service" id="sylius.factory.channel_pricing"/>
<argument type="service" id="sylius.factory.product_translation"/>
<argument type="service" id="sylius.factory.product_variant_translation"/>
<tag name="console.command" />
</service>

<!-- General -->
<service id="webgriffe_sylius_akeneo.api_client.factory" class="Akeneo\Pim\ApiClient\AkeneoPimClientBuilder">
<argument>%webgriffe_sylius_akeneo.api_client.base_url%</argument>
Expand Down
300 changes: 300 additions & 0 deletions src/Command/DebugCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
<?php

declare(strict_types=1);

namespace Webgriffe\SyliusAkeneoPlugin\Command;

use Akeneo\Pim\ApiClient\AkeneoPimClientInterface;
use Sylius\Component\Core\Model\ChannelPricingInterface;
use Sylius\Component\Core\Model\ProductTranslationInterface;
use Sylius\Component\Product\Factory\ProductFactoryInterface;
use Sylius\Component\Product\Factory\ProductVariantFactoryInterface;
use Sylius\Component\Product\Model\ProductAttributeInterface;
use Sylius\Component\Product\Model\ProductOptionInterface;
use Sylius\Component\Product\Model\ProductVariantTranslationInterface;
use Sylius\Component\Product\Repository\ProductOptionRepositoryInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

/**
* @psalm-type AkeneoAttribute array{code: string, type: string, labels: array<string, ?string>}
*/
final class DebugCommand extends Command
{
protected static $defaultName = 'webgriffe:akeneo:debug';

/** @var array<string, ProductAttributeInterface> */
private array $syliusAttributes = [];

/** @var array<string, ProductOptionInterface> */
private array $syliusOptions = [];

/**
* @param RepositoryInterface<ProductAttributeInterface> $productAttributeRepository
* @param FactoryInterface<ChannelPricingInterface> $channelPricingFactory
* @param FactoryInterface<ProductTranslationInterface> $productTranslationFactory
* @param FactoryInterface<ProductVariantTranslationInterface> $productVariantTranslationFactory
*/
public function __construct(
private AkeneoPimClientInterface $akeneoPimClient,
private RepositoryInterface $productAttributeRepository,
private ProductOptionRepositoryInterface $productOptionRepository,
private ContainerInterface $container,
private PropertyAccessorInterface $propertyAccessor,
private ProductFactoryInterface $productFactory,
private ProductVariantFactoryInterface $productVariantFactory,
private FactoryInterface $channelPricingFactory,
private FactoryInterface $productTranslationFactory,
private FactoryInterface $productVariantTranslationFactory,
) {
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output)
{
foreach ($this->productAttributeRepository->findAll() as $productAttribute) {
$this->syliusAttributes[(string) $productAttribute->getCode()] = $productAttribute;
}
/** @var ProductOptionInterface $productOption */
foreach ($this->productOptionRepository->findAll() as $productOption) {
$this->syliusOptions[(string) $productOption->getCode()] = $productOption;
}
/** @var AkeneoAttribute[] $akeneoAttributes */
$akeneoAttributes = $this->akeneoPimClient->getAttributeApi()->all();
$rows = [];
/** @var array<array-key, array{type: string, options: array, priority: int}> $valueHandlers */
$valueHandlers = $this->container->getParameter('webgriffe_sylius_akeneo_plugin.value_handlers.product');

Check failure on line 73 in src/Command/DebugCommand.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.0, Symfony ^5.4, MySQL 8.0

UndefinedDocblockClass

src/Command/DebugCommand.php:73:26: UndefinedDocblockClass: Docblock-defined class, interface or enum named UnitEnum does not exist (see https://psalm.dev/200)

Check failure on line 73 in src/Command/DebugCommand.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.0, Symfony ^5.4, MySQL 8.0

UndefinedDocblockClass

src/Command/DebugCommand.php:73:26: UndefinedDocblockClass: Docblock-defined class, interface or enum named UnitEnum does not exist (see https://psalm.dev/200)
$isGenericAttributeValueHandlerEnabled = false;
$isProductOptionValueHandlerEnabled = false;
foreach ($valueHandlers as $valueHandler) {
if ($valueHandler['type'] === 'generic_attribute') {
$isGenericAttributeValueHandlerEnabled = true;

continue;
}
if ($valueHandler['type'] === 'product_option') {
$isProductOptionValueHandlerEnabled = true;

continue;
}
}
foreach ($akeneoAttributes as $akeneoAttribute) {
$akeneoAttributeCode = $akeneoAttribute['code'];
$syliusProductAttribute = $this->syliusAttributes[$akeneoAttributeCode] ?? null;
$syliusProductOption = $this->syliusOptions[$akeneoAttributeCode] ?? null;

$willBeImportedAsSyliusAttribute = $syliusProductAttribute instanceof ProductAttributeInterface && $isGenericAttributeValueHandlerEnabled;
$willBeImportedAsSyliusOption = $syliusProductOption instanceof ProductOptionInterface && $isProductOptionValueHandlerEnabled;
$properties = $this->resolveProperties($valueHandlers, $akeneoAttributeCode);
$compatibleValueHandlers = $this->resolveValueHandlers($valueHandlers, $akeneoAttributeCode);
$willBeImported = $willBeImportedAsSyliusAttribute || $willBeImportedAsSyliusOption || count($compatibleValueHandlers) > 0;

$rowspan = max(count($properties), count($compatibleValueHandlers));

$rows[] = [
new TableCell($willBeImported ? $akeneoAttributeCode : '<error>' . $akeneoAttributeCode . '</error>', ['rowspan' => $rowspan]),
new TableCell($willBeImportedAsSyliusAttribute ? 'Yes' : '', ['rowspan' => $rowspan]),
new TableCell($willBeImportedAsSyliusOption ? 'Yes' : '', ['rowspan' => $rowspan]),
new TableCell(array_key_exists(0, $properties) ? $properties[0] : ''),
new TableCell(array_key_exists(0, $compatibleValueHandlers) ? $compatibleValueHandlers[0] : ''),
new TableCell($willBeImported ? 'Yes' : '<error>No</error>', ['rowspan' => $rowspan]),
];
for ($i = 1; $i < $rowspan; ++$i) {
$rows[] = [
new TableCell(array_key_exists($i, $properties) ? $properties[$i] : ''),
new TableCell(array_key_exists($i, $compatibleValueHandlers) ? $compatibleValueHandlers[$i] : ''),
];
}
}

$table = new Table($output);
$table
->setHeaders([
'Akeneo attribute code',
'Will be imported as Sylius attribute?',
'Will be imported as Sylius option?',
'Will be imported on Sylius property?',
'Compatible value handlers',
'Will be imported?',
])
->setRows($rows)
;
$table->render();

return Command::SUCCESS;
}

/**
* @param array<array-key, array{type: string, options: array, priority: int}> $valueHandlers
*
* @return string[]
*/
private function resolveValueHandlers(array $valueHandlers, string $akeneoAttributeCode): array
{
$supportedValueHandlers = [];
foreach ($valueHandlers as $valueHandler) {
if ($valueHandler['type'] === 'generic_attribute') {
if (array_key_exists($akeneoAttributeCode, $this->syliusAttributes)) {
$supportedValueHandlers[] = 'generic_attribute';
}

continue;
}
if ($valueHandler['type'] === 'product_option') {
if (array_key_exists($akeneoAttributeCode, $this->syliusOptions)) {
$supportedValueHandlers[] = 'product_option';
}

continue;
}
if ($valueHandler['type'] === 'translatable_property') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'translatable_property';

continue;
}
if ($valueHandler['type'] === 'immutable_slug') {
if ($valueHandler['options']['$akeneoAttributeToSlugify'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'immutable_slug';

continue;
}
if ($valueHandler['type'] === 'image') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'image';

continue;
}
if ($valueHandler['type'] === 'channel_pricing') {
if ($valueHandler['options']['$akeneoAttribute'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'channel_pricing';

continue;
}
if ($valueHandler['type'] === 'file_attribute') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'file_attribute';

continue;
}
if ($valueHandler['type'] === 'metric_property') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'metric_property';
}
if ($valueHandler['type'] === 'generic_property') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
$supportedValueHandlers[] = 'generic_property';
}
}

return array_unique($supportedValueHandlers);
}

/**
* @param array<array-key, array{type: string, options: array, priority: int}> $valueHandlers
*
* @return string[]
*/
private function resolveProperties(array $valueHandlers, string $akeneoAttributeCode): array
{
$properties = [];
foreach ($valueHandlers as $valueHandler) {
if ($valueHandler['type'] === 'generic_property') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
/** @var string $propertyPath */
$propertyPath = $valueHandler['options']['$propertyPath'];
if ($this->propertyAccessor->isWritable($this->productFactory->createNew(), $propertyPath)) {
$properties[] = 'ProductInterface' . '::$' . $propertyPath;
}
if ($this->propertyAccessor->isWritable($this->productVariantFactory->createNew(), $propertyPath)) {
$properties[] = 'ProductVariantInterface' . '::$' . $propertyPath;
}

continue;
}
if ($valueHandler['type'] === 'channel_pricing') {
if ($valueHandler['options']['$akeneoAttribute'] !== $akeneoAttributeCode) {
continue;
}
/** @var string $propertyPath */
$propertyPath = $valueHandler['options']['$syliusPropertyPath'];
if ($this->propertyAccessor->isWritable($this->channelPricingFactory->createNew(), $propertyPath)) {
$properties[] = 'ChannelPricingInterface' . '::$' . $propertyPath;
}

continue;
}
if ($valueHandler['type'] === 'translatable_property') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
/** @var string $propertyPath */
$propertyPath = $valueHandler['options']['$translationPropertyPath'];
if ($this->propertyAccessor->isWritable($this->productTranslationFactory->createNew(), $propertyPath)) {
$properties[] = 'ProductTranslationInterface' . '::$' . $propertyPath;
}
if ($this->propertyAccessor->isWritable($this->productVariantTranslationFactory->createNew(), $propertyPath)) {
$properties[] = 'ProductVariantTranslationInterface' . '::$' . $propertyPath;
}

continue;
}
if ($valueHandler['type'] === 'immutable_slug') {
if ($valueHandler['options']['$akeneoAttributeToSlugify'] !== $akeneoAttributeCode) {
continue;
}
$properties[] = 'ProductTranslationInterface::$slug';

continue;
}
if ($valueHandler['type'] === 'image') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
$properties[] = 'ProductImageInterface::$file';

continue;
}
if ($valueHandler['type'] === 'metric_property') {
if ($valueHandler['options']['$akeneoAttributeCode'] !== $akeneoAttributeCode) {
continue;
}
/** @var string $propertyPath */
$propertyPath = $valueHandler['options']['$propertyPath'];
if ($this->propertyAccessor->isWritable($this->productFactory->createNew(), $propertyPath)) {
$properties[] = 'ProductInterface' . '::$' . $propertyPath;
}
if ($this->propertyAccessor->isWritable($this->productVariantFactory->createNew(), $propertyPath)) {
$properties[] = 'ProductVariantInterface' . '::$' . $propertyPath;
}

continue;
}
}

return $properties;
}
}
6 changes: 5 additions & 1 deletion src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,13 @@ public function load(array $configs, ContainerBuilder $container): void

$loader->load('services.xml');

/** @var array $productValueHandlers */
$productValueHandlers = $config['value_handlers']['product'] ?? [];
$container->addDefinitions(
$this->createValueHandlersDefinitionsAndPriorities($config['value_handlers']['product'] ?? []),
$this->createValueHandlersDefinitionsAndPriorities($productValueHandlers),
);

$container->setParameter('webgriffe_sylius_akeneo_plugin.value_handlers.product', $productValueHandlers);
}

public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface
Expand Down
7 changes: 5 additions & 2 deletions src/ValueHandler/GenericPropertyValueHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@

final class GenericPropertyValueHandler implements ValueHandlerInterface
{
public function __construct(private PropertyAccessorInterface $propertyAccessor, private string $akeneoAttributeCode, private string $propertyPath)
{
public function __construct(
private PropertyAccessorInterface $propertyAccessor,
private string $akeneoAttributeCode,
private string $propertyPath,
) {
}

/**
Expand Down
9 changes: 7 additions & 2 deletions src/ValueHandler/MetricPropertyValueHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@

final class MetricPropertyValueHandler implements ValueHandlerInterface
{
public function __construct(private PropertyAccessorInterface $propertyAccessor, private UnitMeasurementValueConverterInterface $unitMeasurementValueConverter, private string $akeneoAttributeCode, private string $propertyPath, private ?string $akeneoUnitMeasurementCode = null)
{
public function __construct(
private PropertyAccessorInterface $propertyAccessor,
private UnitMeasurementValueConverterInterface $unitMeasurementValueConverter,
private string $akeneoAttributeCode,
private string $propertyPath,
private ?string $akeneoUnitMeasurementCode = null,
) {
}

/**
Expand Down
Loading