From a10a04a689fcf3a450a4dc6aea1ab4b2f31fcec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Wed, 18 Sep 2019 12:06:59 +0200 Subject: [PATCH 1/7] Migrated to a Symfony 4 app with Symfony Flex --- .gitignore | 0 bin/console | 69 ++ composer.json | 55 +- composer.lock | 939 +++++++++++++++++- config/bootstrap.php | 23 + .../Domain/Configuration/DTO/Attribute.php | 27 + .../Configuration/DTO/AttributeGroup.php | 20 + src/App/Domain/Configuration/DTO/Family.php | 20 + src/App/Domain/Configuration/DTO/Label.php | 33 + src/App/Domain/Configuration/DTO/Locale.php | 14 + .../Domain/Configuration/DTO/Localised.php | 17 + .../Configuration/DTO/MetricAttribute.php | 23 + src/App/Domain/Configuration/DTO/Store.php | 23 + src/App/Domain/Configuration/DTO/Website.php | 23 + .../Command/ExtractAttributeGroups.php | 45 + .../Command/ExtractAttributeOptions.php | 46 + .../Fixture/Command/ExtractAttributes.php | 41 + .../Fixture/Command/ExtractFamilies.php | 59 ++ .../Fixture/Command/ExtractFamilyVariants.php | 53 + .../Domain/Fixture/Command/ExtractLocales.php | 21 + .../Fixture/Command/ExtractSimpleProducts.php | 48 + src/App/Domain/Fixture/IterableToCsv.php | 33 + src/App/Domain/Fixture/SqlToCsv.php | 31 + .../Configuration/Configuration.php | 153 +++ .../Configuration/YamlFileLoader.php | 33 + .../Console/Command/GoCommand.php | 178 ++++ .../Console/Command/InitializeCommand.php | 146 +++ .../Console/Command/ProductCommand.php | 114 +++ .../Console/Command/TestCommand.php | 70 ++ .../Iteration/AbstractCallbackIterator.php | 47 + .../Iteration/CallbackIterator.php | 11 + .../Infrastructure/Iteration/MapIterator.php | 62 ++ .../Normalizer/FallbackNormalizer.php | 29 + .../Normalizer/ListNormalizer.php | 59 ++ .../Yaml/AttributeGroupNormalizer.php | 55 + .../Normalizer/Yaml/AttributeNormalizer.php | 64 ++ .../Normalizer/Yaml/FamilyNormalizer.php | 81 ++ .../Normalizer/Yaml/LabelNormalizer.php | 32 + .../Yaml/MetricAttributeNormalizer.php | 19 + .../AllAttributeGroupsFromFamily.php | 51 + .../Projection/AllAttributes.php | 175 ++++ .../AllAttributesFromAttributeGroup.php | 193 ++++ .../Infrastructure/Projection/AllFamilies.php | 41 + .../Projection/AllStoresFromWebsite.php | 53 + .../Infrastructure/Projection/AllWebsites.php | 38 + .../Projection/OneLocaleFromStore.php | 49 + src/App/Infrastructure/Repository/Locale.php | 21 + .../Repository/RepositoryInterface.php | 8 + src/App/Infrastructure/Repository/Store.php | 27 + src/App/Infrastructure/Repository/Website.php | 21 + .../extract-attribute-options.sql.twig | 40 + .../templates/extract-attributes.sql.twig | 210 ++++ .../finalize-product-children.sql.twig | 0 .../finalize-product-parents.sql.twig | 0 .../templates}/finalize-products.sql.twig | 0 .../Resources/templates/initialize.sql.twig | 32 + .../templates/product/00-sku.sql.twig | 13 + .../product/01-attribute-options.sql.twig | 44 + .../02-prefill-axis-attributes.sql.twig | 23 + ...act-datetime-scopable-localizable.sql.twig | 0 .../attribute/extract-image-scopable.sql.twig | 0 ...simpleselect-scopable-localizable.sql.twig | 0 .../extract-simpleselect-scopable.sql.twig | 0 .../attribute/extract-simpleselect.sql.twig | 0 ...tract-status-scopable-localizable.sql.twig | 0 ...extract-text-scopable-localizable.sql.twig | 0 .../product/attribute/extract-text.sql.twig | 0 ...ract-varchar-scopable-localizable.sql.twig | 0 ...t-visibility-scopable-localizable.sql.twig | 0 symfony.lock | 72 ++ templates/initialize.sql.twig | 194 ---- 71 files changed, 3901 insertions(+), 220 deletions(-) create mode 100644 .gitignore create mode 100755 bin/console create mode 100644 config/bootstrap.php create mode 100644 src/App/Domain/Configuration/DTO/Attribute.php create mode 100644 src/App/Domain/Configuration/DTO/AttributeGroup.php create mode 100644 src/App/Domain/Configuration/DTO/Family.php create mode 100644 src/App/Domain/Configuration/DTO/Label.php create mode 100644 src/App/Domain/Configuration/DTO/Locale.php create mode 100644 src/App/Domain/Configuration/DTO/Localised.php create mode 100644 src/App/Domain/Configuration/DTO/MetricAttribute.php create mode 100644 src/App/Domain/Configuration/DTO/Store.php create mode 100644 src/App/Domain/Configuration/DTO/Website.php create mode 100644 src/App/Domain/Fixture/Command/ExtractAttributeGroups.php create mode 100644 src/App/Domain/Fixture/Command/ExtractAttributeOptions.php create mode 100644 src/App/Domain/Fixture/Command/ExtractAttributes.php create mode 100644 src/App/Domain/Fixture/Command/ExtractFamilies.php create mode 100644 src/App/Domain/Fixture/Command/ExtractFamilyVariants.php create mode 100644 src/App/Domain/Fixture/Command/ExtractLocales.php create mode 100644 src/App/Domain/Fixture/Command/ExtractSimpleProducts.php create mode 100644 src/App/Domain/Fixture/IterableToCsv.php create mode 100644 src/App/Domain/Fixture/SqlToCsv.php create mode 100644 src/App/Infrastructure/Configuration/Configuration.php create mode 100644 src/App/Infrastructure/Configuration/YamlFileLoader.php create mode 100644 src/App/Infrastructure/Console/Command/GoCommand.php create mode 100644 src/App/Infrastructure/Console/Command/InitializeCommand.php create mode 100644 src/App/Infrastructure/Console/Command/ProductCommand.php create mode 100644 src/App/Infrastructure/Console/Command/TestCommand.php create mode 100644 src/App/Infrastructure/Iteration/AbstractCallbackIterator.php create mode 100644 src/App/Infrastructure/Iteration/CallbackIterator.php create mode 100644 src/App/Infrastructure/Iteration/MapIterator.php create mode 100644 src/App/Infrastructure/Normalizer/FallbackNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/ListNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Yaml/AttributeGroupNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Yaml/AttributeNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Yaml/FamilyNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Yaml/LabelNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Yaml/MetricAttributeNormalizer.php create mode 100644 src/App/Infrastructure/Projection/AllAttributeGroupsFromFamily.php create mode 100644 src/App/Infrastructure/Projection/AllAttributes.php create mode 100644 src/App/Infrastructure/Projection/AllAttributesFromAttributeGroup.php create mode 100644 src/App/Infrastructure/Projection/AllFamilies.php create mode 100644 src/App/Infrastructure/Projection/AllStoresFromWebsite.php create mode 100644 src/App/Infrastructure/Projection/AllWebsites.php create mode 100644 src/App/Infrastructure/Projection/OneLocaleFromStore.php create mode 100644 src/App/Infrastructure/Repository/Locale.php create mode 100644 src/App/Infrastructure/Repository/RepositoryInterface.php create mode 100644 src/App/Infrastructure/Repository/Store.php create mode 100644 src/App/Infrastructure/Repository/Website.php create mode 100644 src/App/Resources/templates/extract-attribute-options.sql.twig create mode 100644 src/App/Resources/templates/extract-attributes.sql.twig rename {templates => src/App/Resources/templates}/finalize-product-children.sql.twig (100%) rename {templates => src/App/Resources/templates}/finalize-product-parents.sql.twig (100%) rename {templates => src/App/Resources/templates}/finalize-products.sql.twig (100%) create mode 100644 src/App/Resources/templates/initialize.sql.twig create mode 100644 src/App/Resources/templates/product/00-sku.sql.twig create mode 100644 src/App/Resources/templates/product/01-attribute-options.sql.twig create mode 100644 src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig rename {templates => src/App/Resources/templates}/product/attribute/extract-datetime-scopable-localizable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-image-scopable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-simpleselect-scopable-localizable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-simpleselect-scopable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-simpleselect.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-status-scopable-localizable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-text-scopable-localizable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-text.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-varchar-scopable-localizable.sql.twig (100%) rename {templates => src/App/Resources/templates}/product/attribute/extract-visibility-scopable-localizable.sql.twig (100%) create mode 100644 symfony.lock delete mode 100644 templates/initialize.sql.twig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..4e75a1d --- /dev/null +++ b/bin/console @@ -0,0 +1,69 @@ +#!/usr/bin/env php +getParameterOption(['--env', '-e'], null, true)) { + putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); +} + +if ($input->hasParameterOption('--no-debug', true)) { + putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); +} + +require dirname(__DIR__) . '/config/bootstrap.php'; + +if ($_SERVER['APP_DEBUG']) { + umask(0000); + + if (class_exists(Debug::class)) { + Debug::enable(); + } +} + +$output = new ConsoleOutput(); + +$factory = new class($output) { + private $logger; + + public function __construct(OutputInterface $output) + { + $this->logger = new ConsoleLogger($output); + } + + public function __invoke(string $className) + { + return function () use ($className) { + return new $className(null, $this->logger); + }; + } +}; + +$application = new Application('Akeneo Fixtures From Magento'); +$application->setCommandLoader(new FactoryCommandLoader([ + 'init' => $factory(\App\Infrastructure\Console\Command\InitializeCommand::class), + 'go' => $factory(\App\Infrastructure\Console\Command\GoCommand::class), + 'test' => $factory(\App\Infrastructure\Console\Command\TestCommand::class), +// 'categories' => function(){}, + 'products' => $factory(\App\Infrastructure\Console\Command\ProductCommand::class), +])); +$application->run($input, $output); diff --git a/composer.json b/composer.json index 4203311..d94cde7 100644 --- a/composer.json +++ b/composer.json @@ -3,12 +3,65 @@ "type": "project", "require": { "php": "^7.2", + "ext-PDO": "^7.2", + "ext-ctype": "*", + "ext-iconv": "*", + "psr/log": "^1.1", + "symfony/cache": "4.3.*", + "symfony/config": "4.3.*", + "symfony/console": "4.3.*", + "symfony/debug": "4.3.*", + "symfony/dotenv": "4.3.*", + "symfony/flex": "^1.3.1", + "symfony/serializer": "4.3.*", + "symfony/yaml": "4.3.*", "twig/twig": "^2.10" }, + "require-dev": { + }, + "config": { + "preferred-install": { + "*": "dist" + }, + "sort-packages": true + }, "minimum-stability": "stable", "autoload": { "psr-4": { - "Kiboko\\Bridge\\Akeneo\\Magento\\": "src/" + "App\\": "src/App/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/App" + } + }, + "replace": { + "paragonie/random_compat": "2.*", + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php71": "*", + "symfony/polyfill-php70": "*", + "symfony/polyfill-php56": "*" + }, + "scripts": { + "auto-scripts": { + + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "4.3.*" } } } diff --git a/composer.lock b/composer.lock index 935437b..d7fb2db 100644 --- a/composer.lock +++ b/composer.lock @@ -4,40 +4,568 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b34b0e5e432d8eb831289b9077e6c65d", + "content-hash": "af4fc2bcff0835e8df379e13372004c1", "packages": [ { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "symfony/cache", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "d263af3cec33afa862310e58545fdc10d779806f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/d263af3cec33afa862310e58545fdc10d779806f", + "reference": "d263af3cec33afa862310e58545fdc10d779806f", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1", + "symfony/service-contracts": "^1.1", + "symfony/var-exporter": "^4.2" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/var-dumper": "<3.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.1", + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2019-06-28T13:16:30+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v1.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", + "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-13T11:15:36+00:00" + }, + { + "name": "symfony/config", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "a17a2aea43950ce83a0603ed301bac362eb86870" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/a17a2aea43950ce83a0603ed301bac362eb86870", + "reference": "a17a2aea43950ce83a0603ed301bac362eb86870", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/messenger": "~4.1", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2019-07-18T10:34:59+00:00" + }, + { + "name": "symfony/console", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9", + "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-07-24T17:13:59+00:00" + }, + { + "name": "symfony/debug", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "527887c3858a2462b0137662c74837288b998ee3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/527887c3858a2462b0137662c74837288b998ee3", + "reference": "527887c3858a2462b0137662c74837288b998ee3", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "~3.4|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2019-07-23T11:21:36+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "c9ea2a1c60e7db08c1d1379cd4448fd14bda11eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/c9ea2a1c60e7db08c1d1379cd4448fd14bda11eb", + "reference": "c9ea2a1c60e7db08c1d1379cd4448fd14bda11eb", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/process": "~3.4|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2019-06-26T06:50:02+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.3.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "url": "https://github.com/symfony/filesystem.git", + "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b9896d034463ad6fd2bf17e2bf9418caecd6313d", + "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Component\\Filesystem\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -46,23 +574,66 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "time": "2019-06-23T08:51:25+00:00" + }, + { + "name": "symfony/flex", + "version": "v1.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "4467ab35c82edebac58fe58c22cea166a805eb1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/4467ab35c82edebac58fe58c22cea166a805eb1f", + "reference": "4467ab35c82edebac58fe58c22cea166a805eb1f", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": "^7.0" + }, + "require-dev": { + "composer/composer": "^1.0.2", + "symfony/dotenv": "^3.4|^4.0", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8", + "symfony/process": "^2.7|^3.0|^4.0" + }, + "type": "composer-plugin", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + }, + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "time": "2019-08-06T08:03:45+00:00" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "time": "2019-07-19T08:59:18+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -123,6 +694,321 @@ ], "time": "2019-08-06T08:03:45+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/serializer", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "702900654e0ceed9ca7a9eccffb1d6ec69d7c8b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/702900654e0ceed9ca7a9eccffb1d6ec69d7c8b6", + "reference": "702900654e0ceed9ca7a9eccffb1d6ec69d7c8b6", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/type-resolver": "<0.2.1", + "symfony/dependency-injection": "<3.4", + "symfony/property-access": "<3.4", + "symfony/property-info": "<3.4", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/cache": "~3.4|~4.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/property-access": "~3.4|~4.0", + "symfony/property-info": "^3.4.13|~4.0", + "symfony/validator": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", + "doctrine/cache": "For using the default cached annotation reader and metadata cache.", + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "For using the XML mapping loader.", + "symfony/http-foundation": "For using a MIME type guesser within the DataUriNormalizer.", + "symfony/property-access": "For using the ObjectNormalizer.", + "symfony/property-info": "To deserialize relations.", + "symfony/yaml": "For using the default YAML mapping loader." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Serializer Component", + "homepage": "https://symfony.com", + "time": "2019-08-26T08:55:16+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d", + "reference": "f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-13T11:15:36+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "9dee83031dcf6dcb53bb7ec1c51de085329bf5cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/9dee83031dcf6dcb53bb7ec1c51de085329bf5cb", + "reference": "9dee83031dcf6dcb53bb7ec1c51de085329bf5cb", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2019-06-22T08:39:44+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/34d29c2acd1ad65688f58452fd48a46bd996d5a6", + "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-07-24T14:47:54+00:00" + }, { "name": "twig/twig", "version": "v2.11.3", @@ -198,7 +1084,10 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.2" + "php": "^7.2", + "ext-pdo": "^7.2", + "ext-ctype": "*", + "ext-iconv": "*" }, "platform-dev": [] } diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 0000000..62e5f76 --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,23 @@ +=1.2) +if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { + foreach ($env as $k => $v) { + $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); + } +} elseif (!class_exists(Dotenv::class)) { + throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); +} else { + // load all the .env files + (new Dotenv(false))->loadEnv(dirname(__DIR__) . '/.env'); +} + +$_SERVER += $_ENV; +$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; +$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; +$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/src/App/Domain/Configuration/DTO/Attribute.php b/src/App/Domain/Configuration/DTO/Attribute.php new file mode 100644 index 0000000..2f3e70e --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Attribute.php @@ -0,0 +1,27 @@ +code = $code; + $this->label = $label; + $this->strategy = $strategy; + $this->type = $type; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/AttributeGroup.php b/src/App/Domain/Configuration/DTO/AttributeGroup.php new file mode 100644 index 0000000..a66ff36 --- /dev/null +++ b/src/App/Domain/Configuration/DTO/AttributeGroup.php @@ -0,0 +1,20 @@ +code = $code; + $this->label = $label; + $this->attributes = $attributes; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/Family.php b/src/App/Domain/Configuration/DTO/Family.php new file mode 100644 index 0000000..a3dab29 --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Family.php @@ -0,0 +1,20 @@ +code = $code; + $this->label = $label; + $this->attributeGroups = $attributeGroups; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/Label.php b/src/App/Domain/Configuration/DTO/Label.php new file mode 100644 index 0000000..0f13b21 --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Label.php @@ -0,0 +1,33 @@ +fallback = $fallback; + $this->localised = $localised; + } + + public function localised(Locale $locale): string + { + foreach ($this->localised as $localised) { + if ($localised->locale->code === $locale->code) { + return $localised->label; + } + } + + return $this->fallback; + } + + public function __toString() + { + return $this->fallback ?? self::class; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/Locale.php b/src/App/Domain/Configuration/DTO/Locale.php new file mode 100644 index 0000000..74e8d3a --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Locale.php @@ -0,0 +1,14 @@ +code = $code; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/Localised.php b/src/App/Domain/Configuration/DTO/Localised.php new file mode 100644 index 0000000..c1770c0 --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Localised.php @@ -0,0 +1,17 @@ +locale = $locale; + $this->label = $label; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/MetricAttribute.php b/src/App/Domain/Configuration/DTO/MetricAttribute.php new file mode 100644 index 0000000..78312e4 --- /dev/null +++ b/src/App/Domain/Configuration/DTO/MetricAttribute.php @@ -0,0 +1,23 @@ +metricFamily = $metricFamily; + $this->defaultMetric = $defaultMetric; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/Store.php b/src/App/Domain/Configuration/DTO/Store.php new file mode 100644 index 0000000..8e7939f --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Store.php @@ -0,0 +1,23 @@ +code = $code; + $this->name = $name; + $this->storeId = $storeId; + $this->locale = $locale; + } +} \ No newline at end of file diff --git a/src/App/Domain/Configuration/DTO/Website.php b/src/App/Domain/Configuration/DTO/Website.php new file mode 100644 index 0000000..8e28a96 --- /dev/null +++ b/src/App/Domain/Configuration/DTO/Website.php @@ -0,0 +1,23 @@ +code = $code; + $this->name = $name; + $this->defaultStore = array_shift($stores); + $this->stores = $stores; + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractAttributeGroups.php b/src/App/Domain/Fixture/Command/ExtractAttributeGroups.php new file mode 100644 index 0000000..b29de7d --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractAttributeGroups.php @@ -0,0 +1,45 @@ + $group) { + yield array_merge( + [ + 'code' => $groupCode, + 'sort_order' => ++$index, + ], + ...array_map(function($locale, $label) { + return [ + 'label-' . $locale => $label, + ]; + }, array_keys($group['label']), $group['label']) + ); + } + })($groups), + $output + ); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php b/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php new file mode 100644 index 0000000..34c96a0 --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php @@ -0,0 +1,46 @@ +pdo = $pdo; + $this->twig = $twig; + } + + public function __invoke( + \SplFileObject $output, + array $attributes, + array $locales, + array $mapping + ): void { + try { + $view = $this->twig->load('extract-attribute-options.sql.twig'); + } catch (LoaderError|RuntimeError|SyntaxError $e) { + throw new \RuntimeException(null, null, $e); + } + + (new SqlToCsv($this->pdo)) + ( + $view->render([ + 'attributes' => $attributes, + 'locales' => $locales, + 'mapping' => $mapping, + ]), + $output + ); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractAttributes.php b/src/App/Domain/Fixture/Command/ExtractAttributes.php new file mode 100644 index 0000000..8494288 --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractAttributes.php @@ -0,0 +1,41 @@ +pdo = $pdo; + $this->twig = $twig; + } + + public function __invoke(\SplFileObject $output, array $attributes, array $locales): void + { + try { + $view = $this->twig->load('extract-attributes.sql.twig'); + } catch (LoaderError|RuntimeError|SyntaxError $e) { + throw new \RuntimeException(null, null, $e); + } + + (new SqlToCsv($this->pdo)) + ( + $view->render([ + 'attributes' => $attributes, + 'locales' => $locales, + ]), + $output + ); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractFamilies.php b/src/App/Domain/Fixture/Command/ExtractFamilies.php new file mode 100644 index 0000000..418b6bf --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractFamilies.php @@ -0,0 +1,59 @@ + $family) { + yield array_merge( + [ + 'code' => $familyCode, + 'attributes' => implode(',', $family['attributes']), + 'attribute_as_label' => $family['label'], + 'attribute_as_image' => $family['image'], + ], + ...array_map(function($requirements) { + return [ + 'requirements-' . $requirements['scope'] => implode(',', $requirements['attributes']) + ]; + }, $family['requirements']), + ...array_map(function($locale) use($familyCode) { + return [ + 'label-' . $locale => sprintf('%s (%s)', $familyCode, $locale), + ]; + }, $locales) + ); + } + })($families, $locales), + $output + ); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php b/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php new file mode 100644 index 0000000..523c689 --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php @@ -0,0 +1,53 @@ + $family) { + foreach ($family['variations'] as $variationCode => $variation) { + yield array_merge( + [ + 'code' => $variationCode, + 'family' => $familyCode, + 'variant-axes_1' => isset($variation['level_1']) ? implode(',', $variation['level_1']['axis']) : null, + 'variant-axes_2' => isset($variation['level_2']) ? implode(',', $variation['level_2']['axis']) : null, + 'variant-attributes_1' => isset($variation['level_1']) ? implode(',', $variation['level_1']['attributes']) : null, + 'variant-attributes_2' => isset($variation['level_2']) ? implode(',', $variation['level_2']['attributes']) : null, + ], + ...array_map(function ($locale) use ($familyCode) { + return [ + 'label-' . $locale => sprintf('%s (%s)', $familyCode, $locale), + ]; + }, $locales) + ); + } + } + })($families, $locales), + $output + ); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractLocales.php b/src/App/Domain/Fixture/Command/ExtractLocales.php new file mode 100644 index 0000000..b246b4a --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractLocales.php @@ -0,0 +1,21 @@ +fwrite(Yaml::dump([ + 'locales' => iterator_to_array((function(array $locales) { + foreach ($locales as $code => $config) { + yield $code => [ + 'currency' => $config['currency'] + ]; + } + })($locales)) + ], 4, 2)); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php b/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php new file mode 100644 index 0000000..a45be2f --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php @@ -0,0 +1,48 @@ +pdo = $pdo; + $this->twig = $twig; + } + + public function __invoke(\SplFileObject $output, array $attributes, array $locales): void + { + try { + $view = $this->twig->load('extract-products.sql.twig'); + + $query = $view->render([ + 'attributes' => $attributes, + 'locales' => $locales, + ]); + + $view = $this->twig->load('extract-products.sql.twig'); + + (new SqlToCsv($this->pdo)) + ( + $view->render([ + 'attributes' => $attributes, + 'locales' => $locales, + ]), + $output + ); + } catch (LoaderError|RuntimeError|SyntaxError $e) { + throw new \RuntimeException('An error occurred during the product data extraction.', null, $e); + } + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/IterableToCsv.php b/src/App/Domain/Fixture/IterableToCsv.php new file mode 100644 index 0000000..a42b309 --- /dev/null +++ b/src/App/Domain/Fixture/IterableToCsv.php @@ -0,0 +1,33 @@ +columns = $columns; + } + + public function __invoke(iterable $data, \SplFileObject $output): self + { + $output->fputcsv($this->columns, ';'); + foreach ($data as $item) { + $output->fputcsv($this->orderColumns($this->columns, $item), ';'); + } + + return $this; + } + + private function orderColumns(array $headers, array $line) + { + $result = []; + foreach ($headers as $cell) { + $result[$cell] = $line[$cell] ?? null; + } + return $result; + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/SqlToCsv.php b/src/App/Domain/Fixture/SqlToCsv.php new file mode 100644 index 0000000..4163346 --- /dev/null +++ b/src/App/Domain/Fixture/SqlToCsv.php @@ -0,0 +1,31 @@ +connection = $connection; + } + + public function __invoke(string $sql, \SplFileObject $output): self + { + $statement = $this->connection->query($sql); + + $columnCount = $statement->columnCount(); + $columns = []; + for ($i = 0; $i < $columnCount; ++$i) { + $columns[] = $statement->getColumnMeta($i)['name']; + } + $output->fputcsv($columns, ';'); + foreach ($statement as $item) { + $output->fputcsv($item, ';'); + } + + return $this; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Configuration/Configuration.php b/src/App/Infrastructure/Configuration/Configuration.php new file mode 100644 index 0000000..f7271a2 --- /dev/null +++ b/src/App/Infrastructure/Configuration/Configuration.php @@ -0,0 +1,153 @@ +getRootNode() + ->children() + ->arrayNode('attributes')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->arrayNode('label') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->enumNode('type') + ->isRequired() + ->values([ + 'identifier', + 'string', + 'text', + 'rich-text', + 'simple-select', + 'multi-select', + 'metric', + 'image', + 'status', + 'visibility', + 'datetime', + ]) + ->end() + ->scalarNode('strategy')->isRequired()->end() + ->scalarNode('source')->end() + ->scalarNode('group')->defaultValue('default')->end() + ->arrayNode('metric') + ->children() + ->scalarNode('family')->isRequired()->end() + ->scalarNode('unit')->isRequired()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('groups')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->arrayNode('label') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('families')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->scalarNode('label')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('image')->isRequired()->cannotBeEmpty()->end() + ->arrayNode('attributes') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->arrayNode('variations') + ->arrayPrototype() + ->children() + ->scalarNode('code')->isRequired()->end() + ->arrayNode('level_1') + ->children() + ->arrayNode('axis') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->arrayNode('attributes') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->arrayNode('level_2') + ->children() + ->arrayNode('axis') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->arrayNode('attributes') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('requirements') + ->arrayPrototype() + ->children() + ->scalarNode('scope')->isRequired()->end() + ->arrayNode('attributes') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('locales')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->scalarNode('currency')->end() + ->end() + ->end() + ->end() + ->arrayNode('scopes')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->arrayNode('locales')->cannotBeEmpty() + ->arrayPrototype() + ->children() + ->scalarNode('code')->isRequired()->end() + ->scalarNode('store')->isRequired()->end() + ->end() + ->end() + ->end() + ->scalarNode('store')->end() + ->end() + ->end() + ->end() + ->arrayNode('codes_mapping') + ->arrayPrototype() + ->children() + ->scalarNode('from')->end() + ->scalarNode('to')->end() + ->end() + ->end() + ->end() + ->end() + ; + + return $treeBuilder; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Configuration/YamlFileLoader.php b/src/App/Infrastructure/Configuration/YamlFileLoader.php new file mode 100644 index 0000000..0b0e053 --- /dev/null +++ b/src/App/Infrastructure/Configuration/YamlFileLoader.php @@ -0,0 +1,33 @@ +processConfiguration( + new Configuration(), + Yaml::parse(file_get_contents($resource)) + ); + } + + public function supports($resource, $type = null) + { + if (!\is_string($resource)) { + return false; + } + + if (null === $type && \in_array(pathinfo($resource, PATHINFO_EXTENSION), ['yaml', 'yml'], true)) { + return true; + } + + return \in_array($type, ['yaml', 'yml'], true); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Console/Command/GoCommand.php b/src/App/Infrastructure/Console/Command/GoCommand.php new file mode 100644 index 0000000..807fd4f --- /dev/null +++ b/src/App/Infrastructure/Console/Command/GoCommand.php @@ -0,0 +1,178 @@ +logger = $logger ?? new NullLogger(); + } + + protected function configure() + { + $this->setDescription('Generate the fixtures files depending on your catalog.yaml configuration and your Magento data.'); + + $this->addOption( + 'dsn', + 'd', + InputOption::VALUE_OPTIONAL, + 'Specify a Data Source Name for PDO, defaults to APP_DSN environment variable.' + ); + + $this->addOption( + 'username', + 'u', + InputOption::VALUE_OPTIONAL, + 'Specify the username for PDO, defaults to APP_USERNAME environment variable.' + ); + + $this->addOption( + 'password', + 'p', + InputOption::VALUE_OPTIONAL, + 'Specify the password for PDO, defaults to APP_PASSWORD environment variable.' + ); + + $this->addOption( + 'config', + 'c', + InputOption::VALUE_OPTIONAL, + 'Specify the path to the catalog config file.' + ); + + $this->addArgument( + 'output', + InputArgument::OPTIONAL, + 'Specify the output file, defaults to CWD.' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $locator = new FileLocator([ + getcwd(), + ]); + + $loader = new DelegatingLoader(new LoaderResolver([ + new GlobFileLoader($locator), + new YamlFileLoader($locator) + ])); + + $style = new SymfonyStyle($input, $output); + + try { + if (!empty($file = $input->getOption('config'))) { + $config = $loader->load($file, 'glob'); + } else { + $config = $loader->load('{.,}catalog.y{a,}ml', 'glob'); + } + } catch (LoaderLoadException $e) { + $style->error($e->getMessage()); + return -1; + } + + $pdo = new \PDO( + $input->getOption('dsn') ?? $_ENV['APP_DSN'] ?? 'mysql:host=localhost;dbname=magento', + $input->getOption('username') ?? $_ENV['APP_USERNAME'] ?? 'root', + $input->getOption('password') ?? $_ENV['APP_PASSWORD'] ?? null, + [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + ] + ); + + $twig = new Environment( + new FilesystemLoader([ + __DIR__ . '/../../../Resources/templates' + ]), + [ + 'autoescape' => false, + ] + ); + + (new Fixture\Command\ExtractLocales())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/locales.yml', 'w'), + $config['locales'] + ); + + $style->writeln('locales ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractAttributes($pdo, $twig))( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attributes.csv', 'w'), + $config['attributes'], + array_keys($config['locales']) + ); + + $style->writeln('attributes ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractAttributeOptions($pdo, $twig))( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attribute_options.csv', 'w'), + array_keys($config['attributes']), + array_keys($config['locales']), + $config['codes_mapping'] + ); + + $style->writeln('attribute options ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractAttributeGroups())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attribute_groups.csv', 'w'), + $config['groups'], + array_keys($config['locales']) + ); + + $style->writeln('attribute groups ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractFamilies())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/families.csv', 'w'), + $config['families'], + $config['scopes'], + array_keys($config['locales']) + ); + + $style->writeln('families ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractFamilyVariants())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/family_variants.csv', 'w'), + $config['families'], + array_keys($config['locales']) + ); + + $style->writeln('family variants ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractSimpleProducts($pdo, $twig))( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/products.csv', 'w'), + $config['families'], + array_keys($config['locales']) + ); + + $style->writeln('products ok', SymfonyStyle::OUTPUT_PLAIN); + $style->writeln('product models nok', SymfonyStyle::OUTPUT_PLAIN); + + return 0; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Console/Command/InitializeCommand.php b/src/App/Infrastructure/Console/Command/InitializeCommand.php new file mode 100644 index 0000000..f9a6de0 --- /dev/null +++ b/src/App/Infrastructure/Console/Command/InitializeCommand.php @@ -0,0 +1,146 @@ +logger = $logger ?? new NullLogger(); + } + + protected function configure() + { + $this->setDescription('Creates an initial configuration file from Magento configuration.'); + + $this->addOption( + 'dsn', + 'd', + InputOption::VALUE_OPTIONAL, + 'Specify a Data Source Name for PDO, defaults to APP_DSN environment variable.' + ); + + $this->addOption( + 'username', + 'u', + InputOption::VALUE_OPTIONAL, + 'Specify the username for PDO, defaults to APP_USERNAME environment variable.' + ); + + $this->addOption( + 'password', + 'p', + InputOption::VALUE_OPTIONAL, + 'Specify the password for PDO, defaults to APP_PASSWORD environment variable.' + ); + + $this->addArgument( + 'output', + InputArgument::OPTIONAL, + 'Specify the output file, defaults to STDOUT.', + 'php://stdout' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $pdo = new \PDO( + $input->getOption('dsn') ?? $_ENV['APP_DSN'] ?? 'mysql:host=localhost;dbname=magento', + $input->getOption('username') ?? $_ENV['APP_USERNAME'] ?? 'root', + $input->getOption('password') ?? $_ENV['APP_PASSWORD'] ?? null, + [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + ] + ); + + $websites = new AllWebsites($pdo); + $families = new AllFamilies($pdo); + $attributes = new AllAttributes($pdo); + + $labelNormalizer = new LabelNormalizer(); + $attributeNormalizer = new AttributeNormalizer($labelNormalizer, $labelNormalizer); + $attributesNormalizer = new ListNormalizer($attributeNormalizer, $attributeNormalizer); + + $config = [ + 'catalog' => [ + 'locales' => array_values(array_unique(array_merge( + [], + ...new MapIterator(function(Website $website) { + return array_map(function(Store $store) { + return $store->locale->code; + }, $website->stores); + }, new \CallbackFilterIterator(new \IteratorIterator($websites), function (Website $website) { + return count($website->stores) > 0; + })) + ))), + 'scopes' => iterator_to_array(new MapIterator(function(Website $website) { + return [ + 'code' => $website->code, + 'locales' => array_map(function(Store $store) { + return [ + 'code' => $store->locale->code, + 'store' => $store->storeId, + ]; + }, $website->stores), + ]; + }, new \CallbackFilterIterator(new \IteratorIterator($websites), function (Website $website) { + return count($website->stores) > 0; + }))), + 'families' => iterator_to_array(new MapIterator(function(Family $family) { + return [ + 'code' => $family->code, + 'attributes' => array_merge([], ...array_values(array_map(function(AttributeGroup $group) { + return array_values(array_map(function(Attribute $attribute) { + return $attribute->code; + }, $group->attributes)); + }, $family->attributeGroups))), + 'variations' => [], + ]; + }, $families)), + 'attributes' => $attributesNormalizer->normalize( + new AllAttributes($pdo), + 'yaml' + ), + ] + ]; + + $processor = new Processor(); + + $processor->processConfiguration( + new Configuration(), + $config + ); + + $output->write(Yaml::dump($config, 4, 2)); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Console/Command/ProductCommand.php b/src/App/Infrastructure/Console/Command/ProductCommand.php new file mode 100644 index 0000000..303d83e --- /dev/null +++ b/src/App/Infrastructure/Console/Command/ProductCommand.php @@ -0,0 +1,114 @@ +setDescription('Export families list.'); + + $this->addOption( + 'config', + 'c', + InputOption::VALUE_OPTIONAL, + 'Specify the path to the catalog config file.' + ); + + $this->addArgument( + 'output', + InputArgument::OPTIONAL, + 'Specify the output file, defaults to STDOUT.', + 'php://stdout' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $locator = new FileLocator([ + getcwd(), + ]); + + $loader = new DelegatingLoader(new LoaderResolver([ + new GlobFileLoader($locator), + new YamlFileLoader($locator) + ])); + + $style = new SymfonyStyle($input, $output); + + try { + if (!empty($file = $input->getOption('config'))) { + $config = $loader->load($file, 'glob'); + } else { + $config = $loader->load('{.,}catalog.y{a,}ml', 'glob'); + } + } catch (LoaderLoadException $e) { + $style->error($e->getMessage()); + return -1; + } + + $output = new \SplFileObject($input->getArgument('output') ?? 'php://stdout', 'w'); + + $columns = array_merge( + [ + 'code', + ], + array_map(function($locale) { + return 'label-' . $locale; + }, $config['locales']), + [ + 'attributes', + 'attribute_as_image', + 'attribute_as_label', + ], + array_map(function($scope) { + return 'requirements-' . $scope; + }, array_keys($config['scopes'])) + ); + + (new IterableToCsv($columns)) + ( + (function(array $config) { + foreach ($config['families'] as $code => $family) { + yield array_merge( + [ + 'code' => $code, + 'attributes' => implode(',', $family['attributes']), + 'attribute_as_label' => $family['label'], + 'attribute_as_image' => $family['image'], + ], + ...array_map(function($requirements) { + return [ + 'requirements-' . $requirements['scope'] => implode(',', $requirements['attributes']) + ]; + }, $family['requirements']), + ...array_map(function($locale) use($code) { + return [ + 'label-' . $locale => sprintf('%s (%s)', $code, $locale), + ]; + }, $config['locales']) + ); + } + })($config), + $output + ); + + return 0; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Console/Command/TestCommand.php b/src/App/Infrastructure/Console/Command/TestCommand.php new file mode 100644 index 0000000..41c685b --- /dev/null +++ b/src/App/Infrastructure/Console/Command/TestCommand.php @@ -0,0 +1,70 @@ +logger = $logger ?? new NullLogger(); + } + + protected function configure() + { + $this->setDescription('Tests the syntax of the configuration file.'); + + $this->addArgument( + 'configuration', + InputArgument::OPTIONAL, + 'Specify the configuration file path, defaults to catalog.yml.', + getcwd() . '/catalog.yml' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $locator = new FileLocator([ + getcwd(), + ]); + + $loader = new DelegatingLoader(new LoaderResolver([ + new GlobFileLoader($locator), + new YamlFileLoader($locator) + ])); + + $style = new SymfonyStyle($input, $output); + + try { + if (!empty($file = $input->getOption('config'))) { + $config = $loader->load($file, 'glob'); + } else { + $config = $loader->load('{.,}catalog.y{a,}ml', 'glob'); + } + } catch (LoaderLoadException $e) { + $style->error($e->getMessage()); + return -1; + } + + $style->success('File syntax is correct!'); + return 0; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Iteration/AbstractCallbackIterator.php b/src/App/Infrastructure/Iteration/AbstractCallbackIterator.php new file mode 100644 index 0000000..d399ef6 --- /dev/null +++ b/src/App/Infrastructure/Iteration/AbstractCallbackIterator.php @@ -0,0 +1,47 @@ +inner = $inner; + $this->callback = $callback; + } + + public function next() + { + $this->inner->next(); + } + + public function key() + { + return $this->inner->key(); + } + + public function valid() + { + return $this->inner->valid(); + } + + public function rewind() + { + $this->inner->rewind(); + } + + public function getInnerIterator() + { + return $this->inner; + } + + public function count() + { + return count($this->inner); + } +} diff --git a/src/App/Infrastructure/Iteration/CallbackIterator.php b/src/App/Infrastructure/Iteration/CallbackIterator.php new file mode 100644 index 0000000..f9215d5 --- /dev/null +++ b/src/App/Infrastructure/Iteration/CallbackIterator.php @@ -0,0 +1,11 @@ +callback)($this->inner->current()); + } +} diff --git a/src/App/Infrastructure/Iteration/MapIterator.php b/src/App/Infrastructure/Iteration/MapIterator.php new file mode 100644 index 0000000..a0087df --- /dev/null +++ b/src/App/Infrastructure/Iteration/MapIterator.php @@ -0,0 +1,62 @@ +innerIterators = []; + $this->index = 0; + + parent::__construct(new \MultipleIterator( + \MultipleIterator::MIT_NEED_ANY | \MultipleIterator::MIT_KEYS_NUMERIC + ), $callback); + + $this->attachIterator(...$traversables); + } + + public function attachIterator(\Traversable ...$traversables): void + { + foreach ($traversables as $traversable) { + if ($traversable instanceof \Iterator) { + $this->inner->attachIterator($traversable); + } else { + $this->inner->attachIterator(new \IteratorIterator($traversable)); + } + + $this->innerIterators[] = $traversable; + } + } + + public function rewind() + { + $this->index = 0; + parent::rewind(); + } + + public function next() + { + $this->index++; + parent::next(); + } + + public function key() + { + parent::key(); + return $this->index; + } + + public function current() + { + return ($this->callback)(...$this->inner->current()); + } +} diff --git a/src/App/Infrastructure/Normalizer/FallbackNormalizer.php b/src/App/Infrastructure/Normalizer/FallbackNormalizer.php new file mode 100644 index 0000000..b8be9ce --- /dev/null +++ b/src/App/Infrastructure/Normalizer/FallbackNormalizer.php @@ -0,0 +1,29 @@ +normalizer = $normalizer; + $this->denormalizer = $denormalizer; + } + + public function normalize($object, $format = null, array $context = []) + { + return iterator_to_array($this->normalizeChild($object, substr($format, 0, -2), $context)); + } + + public function normalizeChild(iterable $object, $format, array $context): \Iterator + { + $childFormat = substr($format, 0, -2); + foreach ($object as $item) { + yield $this->normalizer->normalize($item, $childFormat, $context); + } + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + return iterator_to_array($this->denormalizeChild($data, substr($type, 0, -2), $format, $context)); + } + + public function denormalizeChild(iterable $object, $type, $format, array $context): \Iterator + { + $childType = substr($type, 0, -2); + foreach ($object as $item) { + yield $this->denormalizer->denormalize($item, $childType, $format, $context); + } + } + + public function supportsNormalization($data, $format = null) + { + return is_iterable($data); + } + + public function supportsDenormalization($data, $type, $format = null) + { + return substr($type, -2) === '[]' + && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2)); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Yaml/AttributeGroupNormalizer.php b/src/App/Infrastructure/Normalizer/Yaml/AttributeGroupNormalizer.php new file mode 100644 index 0000000..c5b1e34 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Yaml/AttributeGroupNormalizer.php @@ -0,0 +1,55 @@ +normalizer = new ListNormalizer($normalizer, $denormalizer); + } + + /** + * @param AttributeGroup|mixed $object + */ + public function normalize($object, $format = null, array $context = []) + { + return [ + 'code' => $object->code, + 'code' => $this->normalizer->normalize($object->attributeGroups, $format, $context), + ]; + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + // TODO: Implement denormalize() method. + } + + public function supportsNormalization($data, $format = null) + { + return $data instanceof AttributeGroup + && $format === 'yaml'; + } + + public function supportsDenormalization($data, $type, $format = null) + { + // TODO: Implement supportsDenormalization() method. + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Yaml/AttributeNormalizer.php b/src/App/Infrastructure/Normalizer/Yaml/AttributeNormalizer.php new file mode 100644 index 0000000..e0ceac0 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Yaml/AttributeNormalizer.php @@ -0,0 +1,64 @@ +labelNormalizer = $labelNormalizer; + $this->labelDenormalizer = $labelDenormalizer; + } + + /** + * @param Attribute $object + */ + public function normalize($object, $format = null, array $context = []) + { + return [ + 'code' => $object->code, + 'label' => $this->labelNormalizer->normalize($object->label, $format, $context), + 'strategy' => $object->strategy, + 'type' => $object->type, + ]; + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + return new Attribute( + $data['code'], + $this->labelDenormalizer->denormalize( + $data['label'] ?? ucfirst(str_replace('_', ' ', $data['code'])), + Label::class, + $format, + $context + ), + $data['strategy'], + $data['type'] + ); + } + + public function supportsNormalization($data, $format = null) + { + return $data instanceof Attribute + && $format === 'yaml'; + } + + public function supportsDenormalization($data, $type, $format = null) + { + return is_a($type, Attribute::class) + && $format === 'yaml'; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Yaml/FamilyNormalizer.php b/src/App/Infrastructure/Normalizer/Yaml/FamilyNormalizer.php new file mode 100644 index 0000000..949eb05 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Yaml/FamilyNormalizer.php @@ -0,0 +1,81 @@ +labelNormalizer = $labelNormalizer; + $this->labelDenormalizer = $labelDenormalizer; + $this->attributeGroupNormalizer = new ListNormalizer($attributeGroupNormalizer, $attributeGroupDenormalizer); + } + + /** + * @param Family|mixed $object + */ + public function normalize($object, $format = null, array $context = []) + { + return [ + 'code' => $object->code, + 'label' => $object->label, + 'attributes' => $object->attributeGroups, + 'variations' => [] + ]; + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + return new Family( + $data['code'], + $this->labelDenormalizer->denormalize( + $data['label'] ?? ucfirst(str_replace('_', ' ', $data['code'])), + Label::class, + $format, + $context + ), + ...$this->attributeGroupNormalizer->denormalize( + $data['label'] ?? ucfirst(str_replace('_', ' ', $data['code'])), + Label::class, + $format, + $context + ) + ); + } + + public function supportsNormalization($data, $format = null) + { + return $data instanceof Family + && $format === 'yaml'; + } + + public function supportsDenormalization($data, $type, $format = null) + { + return is_a($type, Family::class) + && $format === 'yaml'; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Yaml/LabelNormalizer.php b/src/App/Infrastructure/Normalizer/Yaml/LabelNormalizer.php new file mode 100644 index 0000000..a694a0b --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Yaml/LabelNormalizer.php @@ -0,0 +1,32 @@ +connection = $connection; + $this->familyId = $familyId; + } + + public function getIterator() + { + $statement = $this->connection->prepare(<<execute([ + 'familyId' => $this->familyId, + ]); + + foreach ($statement as $row) { + yield new AttributeGroup( + $row['code'], + new Label($row['label']), + ...new AllAttributesFromAttributeGroup($this->connection, $this->familyId, $row['id']) + ); + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Projection/AllAttributes.php b/src/App/Infrastructure/Projection/AllAttributes.php new file mode 100644 index 0000000..3b642e0 --- /dev/null +++ b/src/App/Infrastructure/Projection/AllAttributes.php @@ -0,0 +1,175 @@ +connection = $connection; + } + + public function getIterator() + { + $statement = $this->connection->prepare(<<execute(); + + foreach ($statement as $row) { + if ($row['akeneo_type'] === 'pim_catalog_metric') { + yield new MetricAttribute( + $row['code'], + new Label($row['label']), + 'ad-hoc', + $row['metric_family'], + $row['default_metric_unit'] + ); + } else { + yield new Attribute( + $row['code'], + new Label($row['label']), + 'ad-hoc', + $row['akeneo_type'] ?? 'pim_catalog_text' + ); + } + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Projection/AllAttributesFromAttributeGroup.php b/src/App/Infrastructure/Projection/AllAttributesFromAttributeGroup.php new file mode 100644 index 0000000..a4e40be --- /dev/null +++ b/src/App/Infrastructure/Projection/AllAttributesFromAttributeGroup.php @@ -0,0 +1,193 @@ +connection = $connection; + $this->familyId = $familyId; + $this->attributeGroupId = $attributeGroupId; + } + + public function getIterator() + { + $statement = $this->connection->prepare(<<execute([ + 'familyId' => $this->familyId, + 'groupId' => $this->attributeGroupId, + ]); + + foreach ($statement as $row) { + if ($row['akeneo_type'] === 'pim_catalog_metric') { + yield new MetricAttribute( + $row['code'], + new Label($row['label']), + 'ad-hoc', + $row['metric_family'], + $row['default_metric_unit'] + ); + } else { + yield new Attribute( + $row['code'], + new Label($row['label']), + 'ad-hoc', + $row['akeneo_type'] ?? 'pim_catalog_text' + ); + } + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Projection/AllFamilies.php b/src/App/Infrastructure/Projection/AllFamilies.php new file mode 100644 index 0000000..351e656 --- /dev/null +++ b/src/App/Infrastructure/Projection/AllFamilies.php @@ -0,0 +1,41 @@ +connection = $connection; + } + + public function getIterator() + { + $statement = $this->connection->query(<<connection, $row['id']) + ); + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Projection/AllStoresFromWebsite.php b/src/App/Infrastructure/Projection/AllStoresFromWebsite.php new file mode 100644 index 0000000..2ce3b4a --- /dev/null +++ b/src/App/Infrastructure/Projection/AllStoresFromWebsite.php @@ -0,0 +1,53 @@ +connection = $connection; + $this->websiteId = $websiteId; + } + + public function getIterator() + { + $statement = $this->connection->prepare(<<execute([ + 'websiteId' => $this->websiteId, + ]); + + foreach ($statement as $row) { + $locale = (new OneLocaleFromStore($this->connection, $this->websiteId, $row['id']))->get(); + + yield new Store( + $row['code'], + new Label($row['label'], new Localised($locale, $row['label'])), + $row['id'], + $locale + ); + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Projection/AllWebsites.php b/src/App/Infrastructure/Projection/AllWebsites.php new file mode 100644 index 0000000..88fbed6 --- /dev/null +++ b/src/App/Infrastructure/Projection/AllWebsites.php @@ -0,0 +1,38 @@ +connection = $connection; + } + + public function getIterator() + { + $statement = $this->connection->query(<<connection, $row['id'])) + ); + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Projection/OneLocaleFromStore.php b/src/App/Infrastructure/Projection/OneLocaleFromStore.php new file mode 100644 index 0000000..23d2d81 --- /dev/null +++ b/src/App/Infrastructure/Projection/OneLocaleFromStore.php @@ -0,0 +1,49 @@ +connection = $connection; + $this->websiteId = $websiteId; + $this->storeId = $storeId; + } + + public function get(): Locale + { + $statement = $this->connection->prepare(<<execute([ + 'websiteId' => $this->websiteId, + 'storeId' => $this->storeId, + ]); + + return new Locale($statement->fetchColumn()); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Repository/Locale.php b/src/App/Infrastructure/Repository/Locale.php new file mode 100644 index 0000000..ca56b70 --- /dev/null +++ b/src/App/Infrastructure/Repository/Locale.php @@ -0,0 +1,21 @@ +connection = $connection; + } + + public function fromStore(int $websiteId, int $storeId): iterable + { + return new OneLocaleFromStore($this->connection, $websiteId, $storeId); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Repository/RepositoryInterface.php b/src/App/Infrastructure/Repository/RepositoryInterface.php new file mode 100644 index 0000000..cb89d79 --- /dev/null +++ b/src/App/Infrastructure/Repository/RepositoryInterface.php @@ -0,0 +1,8 @@ +connection = $connection; + } + + public function all(): iterable + { + return new AllStores($this->connection); + } + + public function fromWebsite(int $websiteId): iterable + { + return new AllStoresFromWebsite($this->connection, $websiteId); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Repository/Website.php b/src/App/Infrastructure/Repository/Website.php new file mode 100644 index 0000000..e497aff --- /dev/null +++ b/src/App/Infrastructure/Repository/Website.php @@ -0,0 +1,21 @@ +connection = $connection; + } + + public function all(): iterable + { + return new AllWebsites($this->connection); + } +} \ No newline at end of file diff --git a/src/App/Resources/templates/extract-attribute-options.sql.twig b/src/App/Resources/templates/extract-attribute-options.sql.twig new file mode 100644 index 0000000..7455cb7 --- /dev/null +++ b/src/App/Resources/templates/extract-attribute-options.sql.twig @@ -0,0 +1,40 @@ +SELECT + {% set codeMapping -%} + LOWER(opt_value_default.value) + {%- endset -%} + {%- for replace in mapping -%} + {%- set codeMapping -%} + REPLACE({{ codeMapping }}, '{{ replace.from }}', '{{ replace.to }}') + {%- endset -%} + {%- endfor %} + SUBSTRING(CONCAT(codes.code, '_', {{ codeMapping }}), 1, 100) AS code, + attribute.attribute_code AS attribute, + {% for locale in locales %} + COALESCE(opt_value_{{ locale }}.value, opt_value_default.value) AS "label-{{ locale }}", + {% endfor -%} + opt.sort_order AS sort_order + +FROM ( +{%- set renderedAttributes = [] -%} + +{%- for code in attributes -%} + {% set renderedAttribute %} + SELECT '{{ code }}' AS code + {% endset %} + + {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} +{%- endfor -%} + {{ renderedAttributes|join(' UNION ') }} +) AS codes +INNER JOIN eav_attribute AS attribute + ON codes.code = attribute.attribute_code +INNER JOIN eav_attribute_option AS opt + ON opt.attribute_id=attribute.attribute_id +INNER JOIN eav_attribute_option_value AS opt_value_default + ON opt_value_default.option_id=opt.option_id + AND opt_value_default.store_id=0 +{% for locale in locales %} +LEFT JOIN eav_attribute_option_value AS opt_value_{{ locale }} + ON opt_value_{{ locale }}.option_id=opt.option_id + AND opt_value_{{ locale }}.store_id=0 +{% endfor -%} \ No newline at end of file diff --git a/src/App/Resources/templates/extract-attributes.sql.twig b/src/App/Resources/templates/extract-attributes.sql.twig new file mode 100644 index 0000000..39d7d82 --- /dev/null +++ b/src/App/Resources/templates/extract-attributes.sql.twig @@ -0,0 +1,210 @@ +SELECT + COALESCE(codes.type, type_mapping.akeneo_type) AS "type", + codes.code AS "code", + {% for locale in locales %} + attributes.frontend_label AS "label-{{ locale }}", + {% endfor -%} + attributes.groups, +-- GROUP_CONCAT(', ', ) AS "group", + IF(attributes.is_unique, '1', '0') AS "unique", + COALESCE( + codes.useable_as_grid_filter, + CASE COALESCE(codes.type, type_mapping.akeneo_type) + WHEN 'pim_catalog_identifier' THEN '1' + WHEN 'pim_catalog_simpleselect' THEN '1' + WHEN 'pim_catalog_simpleselect' THEN '1' + WHEN 'pim_catalog_textarea' THEN '1' + WHEN 'pim_catalog_text' THEN '1' + ELSE '0' + END + ) AS "useable_as_grid_filter", + NULL AS "allowed_extensions", + NULL AS "metric_family", + NULL AS "default_metric_unit", + NULL AS "reference_data_name", + COALESCE( + codes.localizable, + CASE COALESCE(codes.type, type_mapping.akeneo_type) + WHEN 'pim_catalog_identifier' THEN '0' + WHEN 'pim_catalog_simpleselect' THEN '0' + WHEN 'pim_catalog_simpleselect' THEN '0' + WHEN 'pim_catalog_textarea' THEN '0' + WHEN 'pim_catalog_text' THEN '0' + WHEN 'pim_catalog_image' THEN '0' + ELSE '1' END + ) AS "localizable", + COALESCE( + codes.scopable, + CASE COALESCE(codes.type, type_mapping.akeneo_type) + WHEN 'pim_catalog_identifier' THEN '0' + ELSE '1' END + ) AS "scopable", + CASE codes.code + WHEN 'description' THEN '1' + ELSE '' END + AS "wysiwyg_enabled" + +FROM ( +{%- set renderedAttributes = [] -%} + +{%- for code, attribute in attributes -%} + {% set renderedAttribute %} + SELECT '{{ attribute.code|default(code) }}' AS code, + {% if attribute.type is same as('string') -%} + 'pim_catalog_varchar' + {%- elseif attribute.type is same as('identifier') -%} + 'pim_catalog_identifier' + {%- elseif attribute.type is same as('text') or attribute.type is same as('rich-text') -%} + 'pim_catalog_textarea' + {%- elseif attribute.type is same as('simple-select') or attribute.type is same as('status') or attribute.type is same as('visibility') -%} + 'pim_catalog_simpleselect' + {%- elseif attribute.type is same as('multi-select') -%} + 'pim_catalog_multiselect' + {%- elseif attribute.type is same as('image') -%} + 'pim_catalog_image' + {%- elseif attribute.type is same as('metric') -%} + 'pim_catalog_metric' + {%- elseif attribute.type is same as('datetime') -%} + 'pim_catalog_datetime' + {%- else -%} NULL + {%- endif %} AS type, + {{ attribute.localizable|default(false) ? '1' : '0' }} AS localizable, + {{ attribute.scopable|default(false) ? '1' : '0' }} AS scopable, + {{ attribute.usableAsGridFilter|default(false) ? '1' : '0' }} AS useable_as_grid_filter, + {{ attribute.type is same as('rich-text') ? '1' : '0' }} AS wysiwyg_enabled + {% endset %} + + {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} +{%- endfor -%} + {{ renderedAttributes|join(' UNION ') }} +) AS codes +LEFT JOIN ( + SELECT attribute.*, GROUP_CONCAT(', ', attribute_group.attribute_group_name) AS groups + FROM eav_attribute AS attribute + INNER JOIN eav_entity_attribute AS entity_attribute + ON attribute.attribute_id=entity_attribute.attribute_id + INNER JOIN eav_entity_type AS entity + ON entity.entity_type_id = entity_attribute.entity_type_id + INNER JOIN eav_attribute_group AS attribute_group + ON attribute_group.attribute_group_id = entity_attribute.attribute_group_id + WHERE entity.entity_type_code='catalog_product' + GROUP BY attribute.attribute_id +) AS attributes ON attributes.attribute_code=codes.code +LEFT JOIN ( + SELECT + 'pim_catalog_identifier' AS akeneo_type, + NULL AS attribute_model, + 'catalog/product_attribute_backend_sku' AS backend_model, + 'static' AS backend_type, + NULL AS frontend_model, + 'text' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_textarea' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'text' AS backend_type, + NULL AS frontend_model, + 'textarea' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_textarea' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'varchar' AS backend_type, + NULL AS frontend_model, + 'textarea' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_text' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'varchar' AS backend_type, + NULL AS frontend_model, + 'text' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_date' AS akeneo_type, + NULL AS attribute_model, + 'catalog/product_attribute_backend_startdate' AS backend_model, + 'datetime' AS backend_type, + 'eav/entity_attribute_frontend_datetime' AS frontend_model, + 'date' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_date' AS akeneo_type, + NULL AS attribute_model, + 'eav/entity_attribute_backend_datetime' AS backend_model, + 'datetime' AS backend_type, + 'eav/entity_attribute_frontend_datetime' AS frontend_model, + 'date' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_simpleselect' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'int' AS backend_type, + NULL AS frontend_model, + 'select' AS frontend_input, + 'catalog/product_status' AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_simpleselect' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'int' AS backend_type, + NULL AS frontend_model, + 'select' AS frontend_input, + 'catalog/product_visibility' AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_simpleselect' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'int' AS backend_type, + NULL AS frontend_model, + 'select' AS frontend_input, + 'eav/entity_attribute_source_table' AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_image' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'varchar' AS backend_type, + 'catalog/product_attribute_frontend_image' AS frontend_model, + 'media_image' AS frontend_input, + NULL AS source_model, + NULL AS metric_family, + NULL AS default_metric_unit + UNION SELECT + 'pim_catalog_metric' AS akeneo_type, + NULL AS attribute_model, + NULL AS backend_model, + 'decimal' AS backend_type, + NULL AS frontend_model, + 'weight' AS frontend_input, + NULL AS source_model, + 'Weight' AS metric_family, + 'KILOGRAM' AS default_metric_unit +) AS type_mapping + ON (type_mapping.attribute_model=attributes.attribute_model OR (type_mapping.attribute_model IS NULL AND attributes.attribute_model IS NULL)) + AND (type_mapping.backend_model=attributes.backend_model OR (type_mapping.backend_model IS NULL AND attributes.backend_model IS NULL)) + AND (type_mapping.backend_type=attributes.backend_type OR (type_mapping.backend_type IS NULL AND attributes.backend_type IS NULL)) + AND (type_mapping.frontend_model=attributes.frontend_model OR (type_mapping.frontend_model IS NULL AND attributes.frontend_model IS NULL)) + AND (type_mapping.frontend_input=attributes.frontend_input OR (type_mapping.frontend_input IS NULL AND attributes.frontend_input IS NULL)) + AND (type_mapping.source_model=attributes.source_model OR (type_mapping.source_model IS NULL AND attributes.source_model IS NULL)) diff --git a/templates/finalize-product-children.sql.twig b/src/App/Resources/templates/finalize-product-children.sql.twig similarity index 100% rename from templates/finalize-product-children.sql.twig rename to src/App/Resources/templates/finalize-product-children.sql.twig diff --git a/templates/finalize-product-parents.sql.twig b/src/App/Resources/templates/finalize-product-parents.sql.twig similarity index 100% rename from templates/finalize-product-parents.sql.twig rename to src/App/Resources/templates/finalize-product-parents.sql.twig diff --git a/templates/finalize-products.sql.twig b/src/App/Resources/templates/finalize-products.sql.twig similarity index 100% rename from templates/finalize-products.sql.twig rename to src/App/Resources/templates/finalize-products.sql.twig diff --git a/src/App/Resources/templates/initialize.sql.twig b/src/App/Resources/templates/initialize.sql.twig new file mode 100644 index 0000000..37579e6 --- /dev/null +++ b/src/App/Resources/templates/initialize.sql.twig @@ -0,0 +1,32 @@ + + +{#CREATE TEMPORARY TABLE tmp_hierarchy (#} +{# parent VARCHAR(128) NOT NULL,#} +{# child VARCHAR(128) NOT NULL,#} +{# variant VARCHAR(128) NOT NULL,#} +{# color VARCHAR(128) NULL,#} +{# capacity VARCHAR(128) NULL,#} +{# PRIMARY KEY (parent, child, variant),#} +{# INDEX (parent),#} +{# INDEX (child),#} +{# INDEX (variant)#} +{#)#} +{#SELECT#} +{# product.sku AS parent,#} +{# CONCAT(product.sku{%- for axis in axises -%}, ':', {{ as_field_alias(axis) }}.short_code{%- endfor -%}) AS child,#} +{#{%- for axis in axises -%}#} +{# {{ as_field_alias(axis) }}.code AS `{{ attribute.codeInDestination }}`,#} +{#{% endfor -%}#} +{# child.sku AS variant#} +{#FROM catalog_product_entity AS product#} +{#INNER JOIN catalog_product_super_link AS link#} +{# ON link.parent_id=product.entity_id#} +{#INNER JOIN catalog_product_entity AS child#} +{# ON child.entity_id=link.product_id#} +{#{% for axis in axises -%}#} +{#INNER JOIN {{ as_attribute_table(axis) }} AS {{ as_field_alias(axis) }}#} +{# ON {{ as_field_alias(axis) }}.entity_id = child.entity_id#} +{#{% endfor -%}#} +{#WHERE product.sku IS NOT NULL#} +{# AND child.sku IS NOT NULL#} +{# AND product.type_id IN ('configurable');#} \ No newline at end of file diff --git a/src/App/Resources/templates/product/00-sku.sql.twig b/src/App/Resources/templates/product/00-sku.sql.twig new file mode 100644 index 0000000..23b33bb --- /dev/null +++ b/src/App/Resources/templates/product/00-sku.sql.twig @@ -0,0 +1,13 @@ +CREATE TEMPORARY TABLE tmp_sku ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) +SELECT + product.sku, + product.entity_id, + product.type_id +FROM catalog_product_entity AS product +WHERE product.sku IS NOT NULL; \ No newline at end of file diff --git a/src/App/Resources/templates/product/01-attribute-options.sql.twig b/src/App/Resources/templates/product/01-attribute-options.sql.twig new file mode 100644 index 0000000..05c84b1 --- /dev/null +++ b/src/App/Resources/templates/product/01-attribute-options.sql.twig @@ -0,0 +1,44 @@ +CREATE TEMPORARY TABLE tmp_options ( + option_id INTEGER NOT NULL, + code VARCHAR(256) NOT NULL, + short_code VARCHAR(256) NOT NULL, + attribute VARCHAR(128) NOT NULL, + sort_order INTEGER NOT NULL, + PRIMARY KEY (option_id), + INDEX (code, attribute), + INDEX (attribute) +) +SELECT + opt_value_default.option_id, + {% set codeMapping -%} + LOWER(opt_value_default.value) + {%- endset -%} + {%- for replace in mapping -%} + {%- set codeMapping -%} + REPLACE({{ codeMapping }}, '{{ replace.from }}', '{{ replace.to }}') + {%- endset -%} + {%- endfor %} + SUBSTRING(CONCAT(codes.code, '_', {{ codeMapping }}), 1, 100) AS code, + SUBSTRING({{ codeMapping }}, 1, 99 - LENGTH(codes.code)) AS short_code, + attribute.attribute_code AS attribute, + opt.sort_order AS sort_order + +FROM ( +{%- set renderedAttributes = [] -%} + +{%- for code in attributes -%} + {% set renderedAttribute %} + SELECT '{{ code }}' AS code + {% endset %} + + {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} +{%- endfor -%} + {{ renderedAttributes|join(' UNION ') }} +) AS codes +INNER JOIN eav_attribute AS attribute + ON codes.code = attribute.attribute_code +INNER JOIN eav_attribute_option AS opt + ON opt.attribute_id=attribute.attribute_id +INNER JOIN eav_attribute_option_value AS opt_value_default + ON opt_value_default.option_id=opt.option_id + AND opt_value_default.store_id=0 \ No newline at end of file diff --git a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig new file mode 100644 index 0000000..a19e7f3 --- /dev/null +++ b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig @@ -0,0 +1,23 @@ +{% for axis in axises %} +CREATE TEMPORARY TABLE {{ as_field_table(axis) }} ( + entity_id INTEGER NOT NULL, + code VARCHAR(256) NOT NULL, + short_code VARCHAR(256) NOT NULL, + PRIMARY KEY (entity_id, code), + INDEX (code) +) +SELECT + {{ as_default_alias() }}.entity_id, + {{ as_default_alias() }}_option.short_code AS short_code, + {{ as_default_alias() }}_option.code AS code +FROM eav_attribute AS attribute +INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.attribute_id=attribute.attribute_id +INNER JOIN tmp_options AS {{ as_default_alias() }}_option + ON {{ as_default_alias() }}_option.attribute = attribute.attribute_code + AND {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id +WHERE attribute.attribute_code = '{{ attribute.codeInSource }}' + AND attribute.entity_type_id = 4 + AND attribute.attribute_id={{ as_default_alias() }}.attribute_id + AND {{ as_default_alias() }}.store_id = 0; +{% endfor %} \ No newline at end of file diff --git a/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig similarity index 100% rename from templates/product/attribute/extract-datetime-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig diff --git a/templates/product/attribute/extract-image-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-image-scopable.sql.twig similarity index 100% rename from templates/product/attribute/extract-image-scopable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-image-scopable.sql.twig diff --git a/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig similarity index 100% rename from templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig diff --git a/templates/product/attribute/extract-simpleselect-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig similarity index 100% rename from templates/product/attribute/extract-simpleselect-scopable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig diff --git a/templates/product/attribute/extract-simpleselect.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig similarity index 100% rename from templates/product/attribute/extract-simpleselect.sql.twig rename to src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig diff --git a/templates/product/attribute/extract-status-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig similarity index 100% rename from templates/product/attribute/extract-status-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig diff --git a/templates/product/attribute/extract-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig similarity index 100% rename from templates/product/attribute/extract-text-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig diff --git a/templates/product/attribute/extract-text.sql.twig b/src/App/Resources/templates/product/attribute/extract-text.sql.twig similarity index 100% rename from templates/product/attribute/extract-text.sql.twig rename to src/App/Resources/templates/product/attribute/extract-text.sql.twig diff --git a/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig similarity index 100% rename from templates/product/attribute/extract-varchar-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig diff --git a/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig similarity index 100% rename from templates/product/attribute/extract-visibility-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig diff --git a/symfony.lock b/symfony.lock new file mode 100644 index 0000000..ea1353b --- /dev/null +++ b/symfony.lock @@ -0,0 +1,72 @@ +{ + "psr/cache": { + "version": "1.0.1" + }, + "psr/container": { + "version": "1.0.0" + }, + "psr/log": { + "version": "1.1.0" + }, + "symfony/cache": { + "version": "v4.3.3" + }, + "symfony/cache-contracts": { + "version": "v1.1.5" + }, + "symfony/config": { + "version": "v4.3.3" + }, + "symfony/console": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.3", + "ref": "482d233eb8de91ebd042992077bbd5838858890c" + }, + "files": [ + "bin/console", + "config/bootstrap.php" + ] + }, + "symfony/debug": { + "version": "v4.3.3" + }, + "symfony/dotenv": { + "version": "v4.3.3" + }, + "symfony/filesystem": { + "version": "v4.3.3" + }, + "symfony/flex": { + "version": "1.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "1.0", + "ref": "dc3fc2e0334a4137c47cfd5a3ececc601fa61a0b" + }, + "files": [ + ".env" + ] + }, + "symfony/polyfill-mbstring": { + "version": "v1.12.0" + }, + "symfony/polyfill-php73": { + "version": "v1.12.0" + }, + "symfony/serializer": { + "version": "v4.3.4" + }, + "symfony/service-contracts": { + "version": "v1.1.5" + }, + "symfony/var-exporter": { + "version": "v4.3.3" + }, + "symfony/yaml": { + "version": "v4.3.3" + } +} diff --git a/templates/initialize.sql.twig b/templates/initialize.sql.twig deleted file mode 100644 index 8b4f44f..0000000 --- a/templates/initialize.sql.twig +++ /dev/null @@ -1,194 +0,0 @@ -CREATE TEMPORARY TABLE tmp_sku ( - sku VARCHAR(128) NOT NULL, - entity_id INTEGER NOT NULL, - type_id VARCHAR(32) NOT NULL, - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - product.sku, - product.entity_id, - product.type_id -FROM catalog_product_entity AS product -WHERE product.sku IS NOT NULL; - -CREATE TEMPORARY TABLE tmp_options ( - option_id INTEGER NOT NULL, - code VARCHAR(256) NOT NULL, - short_code VARCHAR(256) NOT NULL, - attribute VARCHAR(128) NOT NULL, - sort_order INTEGER NOT NULL, - PRIMARY KEY (option_id), - INDEX (code, attribute), - INDEX (attribute) -) -SELECT - opt_value_default.option_id, - SUBSTRING(CONCAT( - codes.code, - '_', - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - LOWER(opt_value_default.value), - '"', - 'inches' - ), - 'â', - 'a' - ), - 'è', - 'e' - ), - 'é', - 'e' - ), - '/', - '_' - ), - '.', - '_' - ), - ' ', - '_' - ), - ',', - '_' - ), - ')', - '' - ), - '(', - '' - ), - '+', - '_plus' - ), - '_x_', - 'x' - ) - ), 1, 100) AS code, - SUBSTRING(REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - LOWER(opt_value_default.value), - '"', - 'inches' - ), - 'â', - 'a' - ), - 'è', - 'e' - ), - 'é', - 'e' - ), - '/', - '_' - ), - '.', - '_' - ), - ' ', - '_' - ), - ',', - '_' - ), - ')', - '' - ), - '(', - '' - ), - '+', - '_plus' - ), - '_x_', - 'x' - ), 1, 99 - LENGTH(codes.code)) AS short_code, - attribute.attribute_code AS attribute, - opt.sort_order AS sort_order - -FROM ({{ attributes|map(attribute => "SELECT '#{ attribute.codeInSource }'")|join(' UNION ')|raw }}) AS codes -INNER JOIN eav_attribute AS attribute - ON codes.code = attribute.attribute_code -INNER JOIN eav_attribute_option AS opt - ON opt.attribute_id=attribute.attribute_id -INNER JOIN eav_attribute_option_value AS opt_value_default - ON opt_value_default.option_id=opt.option_id - AND opt_value_default.store_id=0; - -{% for axis in axises %} -CREATE TEMPORARY TABLE {{ as_field_table(axis) }} ( - entity_id INTEGER NOT NULL, - code VARCHAR(256) NOT NULL, - short_code VARCHAR(256) NOT NULL, - PRIMARY KEY (entity_id, code), - INDEX (code) -) -SELECT - {{ as_default_alias() }}.entity_id, - {{ as_default_alias() }}_option.short_code AS short_code, - {{ as_default_alias() }}_option.code AS code -FROM eav_attribute AS attribute -INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} - ON {{ as_default_alias() }}.attribute_id=attribute.attribute_id -INNER JOIN tmp_options AS {{ as_default_alias() }}_option - ON {{ as_default_alias() }}_option.attribute = attribute.attribute_code - AND {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id -WHERE attribute.attribute_code = '{{ attribute.codeInSource }}' - AND attribute.entity_type_id = 4 - AND attribute.attribute_id={{ as_default_alias() }}.attribute_id - AND {{ as_default_alias() }}.store_id = 0; -{% endfor %} - -{#CREATE TEMPORARY TABLE tmp_hierarchy (#} -{# parent VARCHAR(128) NOT NULL,#} -{# child VARCHAR(128) NOT NULL,#} -{# variant VARCHAR(128) NOT NULL,#} -{# color VARCHAR(128) NULL,#} -{# capacity VARCHAR(128) NULL,#} -{# PRIMARY KEY (parent, child, variant),#} -{# INDEX (parent),#} -{# INDEX (child),#} -{# INDEX (variant)#} -{#)#} -{#SELECT#} -{# product.sku AS parent,#} -{# CONCAT(product.sku{%- for axis in axises -%}, ':', {{ as_field_alias(axis) }}.short_code{%- endfor -%}) AS child,#} -{#{%- for axis in axises -%}#} -{# {{ as_field_alias(axis) }}.code AS `{{ attribute.codeInDestination }}`,#} -{#{% endfor -%}#} -{# child.sku AS variant#} -{#FROM catalog_product_entity AS product#} -{#INNER JOIN catalog_product_super_link AS link#} -{# ON link.parent_id=product.entity_id#} -{#INNER JOIN catalog_product_entity AS child#} -{# ON child.entity_id=link.product_id#} -{#{% for axis in axises -%}#} -{#INNER JOIN {{ as_attribute_table(axis) }} AS {{ as_field_alias(axis) }}#} -{# ON {{ as_field_alias(axis) }}.entity_id = child.entity_id#} -{#{% endfor -%}#} -{#WHERE product.sku IS NOT NULL#} -{# AND child.sku IS NOT NULL#} -{# AND product.type_id IN ('configurable');#} \ No newline at end of file From 8ccc040c28048c795e493bb406d266b95f121c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Wed, 16 Oct 2019 15:47:59 +0200 Subject: [PATCH 2/7] Refactored the whole application to make it more extensible in the future --- .docker/php@7.2/Dockerfile | 88 ---- .docker/php@7.2/cli-xdebug/Dockerfile | 65 +++ .docker/php@7.2/cli/Dockerfile | 65 +++ .docker/php@7.2/config/memory.ini | 1 - .gitignore | 1 + README.md | 25 +- bin/console | 2 +- catalog.yml | 398 ++++++++++++++++++ composer.json | 1 + composer.lock | 51 ++- .../Fixture/Command/ExtractSimpleProducts.php | 80 +++- src/App/Domain/Magento/Attribute.php | 9 + .../Domain/Magento}/Attribute/AdHoc.php | 8 +- src/App/Domain/Magento/Attribute/Aliased.php | 29 ++ .../Domain/Magento}/Attribute/ExNihilo.php | 8 +- .../Domain/Magento}/AttributeRenderer.php | 2 +- .../Magento}/AttributeRenderer/Datetime.php | 10 +- .../Magento}/AttributeRenderer/Image.php | 10 +- .../AttributeRenderer/SimpleSelect.php | 10 +- .../Magento}/AttributeRenderer/Status.php | 10 +- .../Magento}/AttributeRenderer/Text.php | 10 +- .../Magento}/AttributeRenderer/Varchar.php | 10 +- .../Magento}/AttributeRenderer/Visibility.php | 10 +- .../Domain/Magento}/AttributeResolver.php | 2 +- .../Domain/Magento}/CodeGenerator.php | 3 +- .../Magento}/CodeGenerator/Globalised.php | 15 +- .../Magento}/CodeGenerator/Localized.php | 17 +- .../Domain/Magento}/CodeGenerator/Scoped.php | 18 +- .../CodeGenerator/ScopedAndLocalized.php | 19 +- src/{ => App/Domain/Magento}/Field.php | 2 +- src/App/Domain/Magento/FieldResolver.php | 9 + .../Magento}/FieldResolver/Globalised.php | 14 +- .../Magento}/FieldResolver/Localized.php | 16 +- .../Domain/Magento}/FieldResolver/Scoped.php | 14 +- .../FieldResolver/ScopedAndLocalized.php | 14 +- .../Magento/FieldResolver/VariantAxis.php | 9 + src/{ => App/Domain/Magento}/Locale.php | 2 +- .../Domain/Magento}/Locale/Locale.php | 6 +- .../Domain/Magento}/Locale/LocaleMapping.php | 6 +- src/{ => App/Domain/Magento}/MagentoStore.php | 2 +- src/{ => App/Domain/Magento}/Renderer.php | 2 +- src/{ => App/Domain/Magento}/Scope.php | 2 +- src/{ => App/Domain/Magento}/Scope/Scope.php | 8 +- .../Domain/Magento}/Scope/ScopeMapping.php | 6 +- .../Domain/Magento}/TwigExtension.php | 12 +- src/{ => App/Domain/Magento}/VariantAxis.php | 2 +- .../AttributeNotFoundException.php | 7 + .../AttributeRendererFactory.php | 137 ++++++ src/App/Infrastructure/Command/CommandBus.php | 29 ++ .../Command/CommandInterface.php | 8 + .../Infrastructure/Command/TwigCommand.php | 53 +++ .../Configuration/Configuration.php | 13 +- .../Console/Command/GoCommand.php | 64 ++- src/App/Infrastructure/FieldsFactory.php | 136 ++++++ .../Normalizer/ListDenormalizer.php | 35 ++ .../Normalizer/ListNormalizer.php | 35 +- .../Magento/Attribute/AdHocNormalizer.php | 22 + .../Magento/Attribute/AliasedNormalizer.php | 23 + .../Magento/Attribute/ExNihiloNormalizer.php | 22 + .../Magento/AttributeDenormalizer.php | 54 +++ .../Magento/AttributeDenormalizerFactory.php | 20 + .../Normalizer/Magento/LocaleDenormalizer.php | 24 ++ .../Magento/LocaleDenormalizerFactory.php | 16 + .../Normalizer/Magento/ScopeDenormalizer.php | 39 ++ .../Magento/ScopeDenormalizerFactory.php | 20 + .../Normalizer/NoSuitableDenormalizer.php | 8 + .../Infrastructure/VariantAxisesFactory.php | 66 +++ .../templates/product/00-sku.sql.twig | 2 +- .../product/01-attribute-options.sql.twig | 4 +- .../02-prefill-axis-attributes.sql.twig | 6 +- ...act-datetime-scopable-localizable.sql.twig | 11 +- ...tract-image-scopable-localizable.sql.twig} | 9 +- .../product/attribute/extract-image.sql.twig | 23 + ...simpleselect-scopable-localizable.sql.twig | 33 +- .../extract-simpleselect-scopable.sql.twig | 33 +- .../attribute/extract-simpleselect.sql.twig | 13 +- ...zable.sql.twig => extract-status.sql.twig} | 27 +- ...extract-text-scopable-localizable.sql.twig | 9 +- .../product/attribute/extract-text.sql.twig | 6 +- ...ract-varchar-scopable-localizable.sql.twig | 8 +- ...e.sql.twig => extract-visibility.sql.twig} | 27 +- src/Attribute.php | 9 - src/Attribute/Aliased.php | 29 -- src/FieldResolver.php | 9 - src/FieldResolver/VariantAxis.php | 9 - symfony.lock | 3 + 86 files changed, 1776 insertions(+), 428 deletions(-) delete mode 100644 .docker/php@7.2/Dockerfile create mode 100644 .docker/php@7.2/cli-xdebug/Dockerfile create mode 100644 .docker/php@7.2/cli/Dockerfile delete mode 100644 .docker/php@7.2/config/memory.ini create mode 100644 catalog.yml create mode 100644 src/App/Domain/Magento/Attribute.php rename src/{ => App/Domain/Magento}/Attribute/AdHoc.php (58%) create mode 100644 src/App/Domain/Magento/Attribute/Aliased.php rename src/{ => App/Domain/Magento}/Attribute/ExNihilo.php (58%) rename src/{ => App/Domain/Magento}/AttributeRenderer.php (89%) rename src/{ => App/Domain/Magento}/AttributeRenderer/Datetime.php (84%) rename src/{ => App/Domain/Magento}/AttributeRenderer/Image.php (84%) rename src/{ => App/Domain/Magento}/AttributeRenderer/SimpleSelect.php (82%) rename src/{ => App/Domain/Magento}/AttributeRenderer/Status.php (84%) rename src/{ => App/Domain/Magento}/AttributeRenderer/Text.php (84%) rename src/{ => App/Domain/Magento}/AttributeRenderer/Varchar.php (84%) rename src/{ => App/Domain/Magento}/AttributeRenderer/Visibility.php (84%) rename src/{ => App/Domain/Magento}/AttributeResolver.php (79%) rename src/{ => App/Domain/Magento}/CodeGenerator.php (72%) rename src/{ => App/Domain/Magento}/CodeGenerator/Globalised.php (63%) rename src/{ => App/Domain/Magento}/CodeGenerator/Localized.php (72%) rename src/{ => App/Domain/Magento}/CodeGenerator/Scoped.php (72%) rename src/{ => App/Domain/Magento}/CodeGenerator/ScopedAndLocalized.php (76%) rename src/{ => App/Domain/Magento}/Field.php (91%) create mode 100644 src/App/Domain/Magento/FieldResolver.php rename src/{ => App/Domain/Magento}/FieldResolver/Globalised.php (59%) rename src/{ => App/Domain/Magento}/FieldResolver/Localized.php (69%) rename src/{ => App/Domain/Magento}/FieldResolver/Scoped.php (72%) rename src/{ => App/Domain/Magento}/FieldResolver/ScopedAndLocalized.php (75%) create mode 100644 src/App/Domain/Magento/FieldResolver/VariantAxis.php rename src/{ => App/Domain/Magento}/Locale.php (72%) rename src/{ => App/Domain/Magento}/Locale/Locale.php (74%) rename src/{ => App/Domain/Magento}/Locale/LocaleMapping.php (75%) rename src/{ => App/Domain/Magento}/MagentoStore.php (78%) rename src/{ => App/Domain/Magento}/Renderer.php (98%) rename src/{ => App/Domain/Magento}/Scope.php (82%) rename src/{ => App/Domain/Magento}/Scope/Scope.php (76%) rename src/{ => App/Domain/Magento}/Scope/ScopeMapping.php (78%) rename src/{ => App/Domain/Magento}/TwigExtension.php (84%) rename src/{ => App/Domain/Magento}/VariantAxis.php (57%) create mode 100644 src/App/Infrastructure/AttributeNotFoundException.php create mode 100644 src/App/Infrastructure/AttributeRendererFactory.php create mode 100644 src/App/Infrastructure/Command/CommandBus.php create mode 100644 src/App/Infrastructure/Command/CommandInterface.php create mode 100644 src/App/Infrastructure/Command/TwigCommand.php create mode 100644 src/App/Infrastructure/FieldsFactory.php create mode 100644 src/App/Infrastructure/Normalizer/ListDenormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/Attribute/AliasedNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/Attribute/ExNihiloNormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/AttributeDenormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/AttributeDenormalizerFactory.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/LocaleDenormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/LocaleDenormalizerFactory.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/ScopeDenormalizer.php create mode 100644 src/App/Infrastructure/Normalizer/Magento/ScopeDenormalizerFactory.php create mode 100644 src/App/Infrastructure/Normalizer/NoSuitableDenormalizer.php create mode 100644 src/App/Infrastructure/VariantAxisesFactory.php rename src/App/Resources/templates/product/attribute/{extract-image-scopable.sql.twig => extract-image-scopable-localizable.sql.twig} (82%) create mode 100644 src/App/Resources/templates/product/attribute/extract-image.sql.twig rename src/App/Resources/templates/product/attribute/{extract-status-scopable-localizable.sql.twig => extract-status.sql.twig} (72%) rename src/App/Resources/templates/product/attribute/{extract-visibility-scopable-localizable.sql.twig => extract-visibility.sql.twig} (74%) delete mode 100644 src/Attribute.php delete mode 100644 src/Attribute/Aliased.php delete mode 100644 src/FieldResolver.php delete mode 100644 src/FieldResolver/VariantAxis.php diff --git a/.docker/php@7.2/Dockerfile b/.docker/php@7.2/Dockerfile deleted file mode 100644 index 6841e58..0000000 --- a/.docker/php@7.2/Dockerfile +++ /dev/null @@ -1,88 +0,0 @@ -FROM php:7.2-cli-alpine - -LABEL maintainer="Grégory Planchat " - -ARG APP_UID=1000 -ARG APP_GID=1000 -ARG APP_USERNAME=docker -ARG APP_GROUPNAME=docker - -RUN set -ex\ - && apk update \ - && apk upgrade \ - && echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ - && apk add \ - shadow@testing \ - ca-certificates \ - wget \ - autoconf \ - bash \ - binutils \ - expat \ - file \ - g++ \ - gcc \ - m4 \ - make \ - git \ - nodejs \ - npm \ - && update-ca-certificates - -RUN docker-php-ext-install opcache - -RUN apk add --update icu-dev icu \ - && docker-php-ext-configure intl \ - && docker-php-ext-install intl \ - && apk del icu-dev - -RUN apk del \ - autoconf \ - bash \ - binutils \ - expat \ - file \ - g++ \ - gcc \ - gdbm \ - gmp \ - isl \ - libatomic \ - libbz2 \ - libc-dev \ - libffi \ - libgcc \ - libgomp \ - libldap \ - libltdl \ - libmagic \ - libstdc++ \ - libtool \ - m4 \ - make \ - mpc1 \ - mpfr3 \ - musl-dev \ - perl \ - pkgconf \ - pkgconfig \ - python \ - re2c \ - readline \ - sqlite-libs \ - && rm -rf /tmp/* /var/cache/apk/* - -RUN addgroup -g ${APP_GID} ${APP_USERNAME} \ - && adduser -u ${APP_UID} -h /opt/${APP_USERNAME} -H -G ${APP_GROUPNAME} -s /sbin/nologin -D ${APP_USERNAME} - -COPY config/memory.ini /usr/local/etc/php/conf.d/memory.ini - -RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ - && php -r "if (hash_file('SHA384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ - && php composer-setup.php --install-dir /usr/local/bin --filename composer\ - && php -r "unlink('composer-setup.php');" - -RUN mkdir -p /opt/docker/.npm \ - && chown docker:docker /opt/docker/.npm - -WORKDIR /app diff --git a/.docker/php@7.2/cli-xdebug/Dockerfile b/.docker/php@7.2/cli-xdebug/Dockerfile new file mode 100644 index 0000000..3d3defa --- /dev/null +++ b/.docker/php@7.2/cli-xdebug/Dockerfile @@ -0,0 +1,65 @@ +FROM kiboko/php:7.2-cli-xdebug + +LABEL maintainer="Grégory Planchat " + +RUN set -ex\ + && apk add \ + wget \ + autoconf \ + bash \ + binutils \ + expat \ + file \ + g++ \ + gcc \ + m4 \ + make \ + git \ + nodejs \ + npm \ + && update-ca-certificates + +RUN apk add --update mysql-dev \ + && docker-php-ext-configure pdo_mysql \ + && docker-php-ext-install pdo_mysql \ + && docker-php-ext-configure mysqli \ + && docker-php-ext-install mysqli \ + && apk del mysql-dev + +RUN apk del \ + autoconf \ + bash \ + binutils \ + expat \ + file \ + g++ \ + gcc \ + gdbm \ + gmp \ + isl \ + libatomic \ + libbz2 \ + libc-dev \ + libffi \ + libgcc \ + libgomp \ + libldap \ + libltdl \ + libmagic \ + libstdc++ \ + libtool \ + m4 \ + make \ + mpc1 \ + mpfr3 \ + musl-dev \ + perl \ + pkgconf \ + pkgconfig \ + python \ + re2c \ + readline \ + sqlite-libs \ + && rm -rf /tmp/* /var/cache/apk/* + +WORKDIR /var/www/html diff --git a/.docker/php@7.2/cli/Dockerfile b/.docker/php@7.2/cli/Dockerfile new file mode 100644 index 0000000..535f8d5 --- /dev/null +++ b/.docker/php@7.2/cli/Dockerfile @@ -0,0 +1,65 @@ +FROM kiboko/php:7.2-cli-blackfire + +LABEL maintainer="Grégory Planchat " + +RUN set -ex\ + && apk add \ + wget \ + autoconf \ + bash \ + binutils \ + expat \ + file \ + g++ \ + gcc \ + m4 \ + make \ + git \ + nodejs \ + npm \ + && update-ca-certificates + +RUN apk add --update mysql-dev \ + && docker-php-ext-configure pdo_mysql \ + && docker-php-ext-install pdo_mysql \ + && docker-php-ext-configure mysqli \ + && docker-php-ext-install mysqli \ + && apk del mysql-dev + +RUN apk del \ + autoconf \ + bash \ + binutils \ + expat \ + file \ + g++ \ + gcc \ + gdbm \ + gmp \ + isl \ + libatomic \ + libbz2 \ + libc-dev \ + libffi \ + libgcc \ + libgomp \ + libldap \ + libltdl \ + libmagic \ + libstdc++ \ + libtool \ + m4 \ + make \ + mpc1 \ + mpfr3 \ + musl-dev \ + perl \ + pkgconf \ + pkgconfig \ + python \ + re2c \ + readline \ + sqlite-libs \ + && rm -rf /tmp/* /var/cache/apk/* + +WORKDIR /var/www/html diff --git a/.docker/php@7.2/config/memory.ini b/.docker/php@7.2/config/memory.ini deleted file mode 100644 index b0fe7fe..0000000 --- a/.docker/php@7.2/config/memory.ini +++ /dev/null @@ -1 +0,0 @@ -memory_limit=-1 diff --git a/.gitignore b/.gitignore index e69de29..f10862a 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/.env diff --git a/README.md b/README.md index 90152ca..dfce6d3 100644 --- a/README.md +++ b/README.md @@ -160,4 +160,27 @@ $renderer = new Renderer( ); $renderer(fopen('products.sql', 'w'), $twig); -``` \ No newline at end of file +``` + +Supported attribute types +--- + +| Magento Type | Akeneo Type | Not Localizable, not Scopable | Localizable, not Scopable | Not Localizable, Scopable | Localizable, Scopable | +| --- | --- | --- | --- | --- | --- | +| Gallery | Asset Collection | ❌ | ❌ | ❌ | ❌ | +| Datetime | Date | ✅ | ❌ | ❌ | ✅ | +| File | File | ❌ | ❌ | ❌ | ❌ | +| SKU | Identifier | ✅ | ❌ | ❌ | ❌ | +| Image | Image | ✅ | ❌ | ❌ | ✅ | +| Decimal | Metric | ❌ | ❌ | ❌ | ❌ | +| Multiselect | Multi select | ❌ | ❌ | ❌ | ❌ | +| Select | Simple select | ✅ | ❌ | ✅ | ✅ | +| Number | Number | ❌ | ❌ | ❌ | ❌ | +| Price | Price | ❌ | ❌ | ❌ | ❌ | +| Status | Simple select | ✅ | ❌ | ❌ | ❌ | +| - | Reference data multi select | ❌ | ❌ | ❌ | ❌ | +| - | Reference data simple select | ❌ | ❌ | ❌ | ❌ | +| Text | Text area | ✅ | ❌ | ❌ | ✅ | +| Varchar | Text | ✅ | ❌ | ❌ | ✅ | +| Visibility | Simple select | ✅ | ❌ | ❌ | ❌ | +| YesNo | Yes No | ❌ | ❌ | ❌ | ❌ | diff --git a/bin/console b/bin/console index 4e75a1d..b3c2877 100755 --- a/bin/console +++ b/bin/console @@ -58,7 +58,7 @@ $factory = new class($output) { } }; -$application = new Application('Akeneo Fixtures From Magento'); +$application = new Application('Bisous', '1.0.0'); $application->setCommandLoader(new FactoryCommandLoader([ 'init' => $factory(\App\Infrastructure\Console\Command\InitializeCommand::class), 'go' => $factory(\App\Infrastructure\Console\Command\GoCommand::class), diff --git a/catalog.yml b/catalog.yml new file mode 100644 index 0000000..83dd515 --- /dev/null +++ b/catalog.yml @@ -0,0 +1,398 @@ +catalog: + attributes: + - code: sku + type: identifier + strategy: ad-hoc + group: general + - code: name + type: string + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: variation_name + type: string + strategy: ex-nihilo + group: general + scoped: true + localised: true + - code: description + type: rich-text + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: variation_description + type: rich-text + strategy: aliased + source: description + group: general + scoped: true + localised: true + - code: short_description + type: rich-text + strategy: ex-nihilo + group: general + scoped: true + localised: true + - code: meta_title + type: string + strategy: ad-hoc + source: meta_title + group: general + scoped: true + localised: true + - code: meta_description + type: text + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: status + type: status + strategy: ad-hoc + group: general + - code: visibility + type: visibility + strategy: ad-hoc + group: general + - code: capacity + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: grade + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: color + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: news_from_date + type: datetime + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: news_to_date + type: datetime + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: manufacturer + type: datetime + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: pastille + type: simple-select + strategy: ex-nihilo + group: general + scoped: true + localised: true + - code: recom_fr_3g + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_4g + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_accessoires + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_bluetooth + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_cartesd + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_das + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_deblocage + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_dimensions + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_ecran + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_garantie + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_iris + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_jack + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_lightning + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_nfc + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_photo + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_poids + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_resolution_ecran + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_sim + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: recom_fr_touchid + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: false + - code: model + type: simple-select + strategy: ad-hoc + group: general + scoped: true + localised: true + - code: weight + type: metric + strategy: ad-hoc + group: general + metric: + family: Weight + unit: KILOGRAM + - code: image + type: image + strategy: ad-hoc + group: general + - code: variation_image + type: image + strategy: ex-nihilo + group: general + scoped: true + localised: true + + groups: + - code: general + label: + de_CH: General + de_DE: General + en_GB: General + es_ES: General + fr_CH: Général + fr_FR: Général + it_CH: General + it_IT: General + + families: + - code: mobile + attributes: [ name, description, short_description, meta_title, meta_description, status, visibility, image, recom_fr_3g, recom_fr_4g, recom_fr_accessoires, recom_fr_bluetooth, recom_fr_cartesd, recom_fr_das, recom_fr_deblocage, recom_fr_dimensions, recom_fr_ecran, recom_fr_garantie, recom_fr_iris, recom_fr_jack, recom_fr_lightning, recom_fr_nfc, recom_fr_photo , recom_fr_poids, recom_fr_resolution_ecran, recom_fr_sim, recom_fr_touchid, model, manufacturer, color, capacity, grade, variation_name, variation_image, variation_description, news_to_date, news_from_date ] + label: name + image: image + requirements: + - scope: recommerce_eu + attributes: [ name, description ] + - scope: recommerce_ch + attributes: [ name, description ] + - scope: bmo_digitech + attributes: [ name, description ] + - scope: bmo_bouygues + attributes: [ name, description ] + - scope: bmo_mbudget + attributes: [ name, description ] + - scope: bmo_ricardo + attributes: [ name, description ] + - scope: amazon + attributes: [ name, description ] + - scope: advise + attributes: [ name, description ] + variations: + - code: mobile_color_capacity_grade + level-1: + axis: [ color, capacity ] + attributes: [ variation_name, variation_image, variation_description ] + level-2: + axis: [ grade ] + attributes: [ sku, status, visibility, news_from_date, news_to_date ] + - code: mobile_groupon + level-1: + axis: [ color, capacity ] + attributes: [ variation_name, variation_image, variation_description ] + + locales: + - code: de_CH + currency: CHF + store: 15 + - code: de_DE + currency: EUR + store: 21 + - code: en_GB + currency: GBP + store: 16 + - code: es_ES + currency: EUR + store: 16 + - code: fr_CH + currency: CHF + store: 16 + - code: fr_FR + currency: EUR + store: 18 + - code: it_CH + currency: CHF + store: 17 + - code: it_IT + currency: EUR + store: 17 + + scopes: + - code: recommerce_eu + store: 18 + locales: + - code: fr_FR + store: 18 + - code: de_DE + store: 21 +# - code: es_ES +# store: 1 + - code: recommerce_ch + store: 16 + locales: + - code: fr_CH + store: 16 + - code: de_CH + store: 15 + - code: it_CH + store: 17 + - code: bmo_digitech + store: 16 + locales: + - code: fr_CH + store: 16 + - code: de_CH + store: 15 + - code: it_CH + store: 17 + - code: en_GB + store: 16 + - code: bmo_bouygues + store: 3 + locales: + - code: fr_FR + store: 3 + - code: bmo_mbudget + store: 3 + locales: + - code: fr_CH + store: 3 + - code: de_CH + store: 4 + - code: it_CH + store: 5 + - code: bmo_ricardo + store: 7 + locales: + - code: fr_CH + store: 7 + - code: de_CH + store: 6 + - code: amazon + store: 8 + locales: + - code: fr_FR + store: 8 + - code: advise + store: 22 + locales: + - code: fr_CH + store: 22 + + codes-mapping: + - from: '"' + to: 'inches' + - from: 'â' + to: 'a' + - from: 'é' + to: 'e' + - from: 'è' + to: 'e' + - from: '/' + to: '_' + - from: '.' + to: '_' + - from: ' ' + to: '_' + - from: ',' + to: '_' + - from: '(' + to: '' + - from: ')' + to: '' + - from: '+' + to: '_plus' + - from: '_x_' + to: 'x' \ No newline at end of file diff --git a/composer.json b/composer.json index d94cde7..697b38e 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "symfony/console": "4.3.*", "symfony/debug": "4.3.*", "symfony/dotenv": "4.3.*", + "symfony/finder": "4.3.*", "symfony/flex": "^1.3.1", "symfony/serializer": "4.3.*", "symfony/yaml": "4.3.*", diff --git a/composer.lock b/composer.lock index d7fb2db..1cd2e40 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "af4fc2bcff0835e8df379e13372004c1", + "content-hash": "cc6e0c02b8535ac4782bab309e71b5e2", "packages": [ { "name": "psr/cache", @@ -586,6 +586,55 @@ "homepage": "https://symfony.com", "time": "2019-06-23T08:51:25+00:00" }, + { + "name": "symfony/finder", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2019-08-14T12:26:46+00:00" + }, { "name": "symfony/flex", "version": "v1.4.5", diff --git a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php b/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php index a45be2f..e4648a5 100644 --- a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php +++ b/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php @@ -3,33 +3,95 @@ namespace App\Domain\Fixture\Command; use App\Domain\Fixture\SqlToCsv; +use App\Domain\Magento\AttributeRenderer; +use App\Infrastructure\Command\CommandBus; +use App\Infrastructure\Command\TwigCommand; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\Debug\Exception\FatalThrowableError; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; -class ExtractSimpleProducts +class ExtractSimpleProducts implements LoggerAwareInterface { + use LoggerAwareTrait; + /** @var \PDO */ private $pdo; /** @var Environment */ private $twig; - public function __construct(\PDO $pdo, Environment $twig) + public function __construct(\PDO $pdo, Environment $twig, ?LoggerInterface $logger = null) { $this->pdo = $pdo; $this->twig = $twig; + $this->logger = $logger ?? new NullLogger(); } - public function __invoke(\SplFileObject $output, array $attributes, array $locales): void + /** + * @param AttributeRenderer[] $attributes + */ + public function __invoke(\SplFileObject $output, array $attributes, array $locales, array $families): void { + $bus = new CommandBus(); try { - $view = $this->twig->load('extract-products.sql.twig'); + $bus->add( + new TwigCommand( + $this->twig, + 'product/00-sku.sql.twig', + [], + $this->logger + ), + new TwigCommand( + $this->twig, + 'product/01-attribute-options.sql.twig', + [ + 'mapping' => [], + 'attributes' => $attributes, + ], + $this->logger +// ), +// new TwigCommand( +// $this->twig, +// 'product/02-prefill-axis-attributes.sql.twig', +// [ +// 'mapping' => [], +// 'axises' => array_merge([], ...array_map(function(AttributeRenderer $renderer) { +// return $renderer->fields(); +// }, $axises)), +// ], +// $this->logger + ) + ); + + foreach ($attributes as $attribute) { + if (!$attribute instanceof AttributeRenderer) { + throw new \RuntimeException(strtr( + 'Expected an instance of %expected%, but got %actual%.', + [ + '%expected%' => AttributeRenderer::class, + '%actual%' => is_object($attribute) ? get_class($attribute) : gettype($attribute), + ] + )); + } + + $bus->add( + new TwigCommand( + $this->twig, + $attribute->template(), + [ + 'renderer' => $attribute, + ], + $this->logger + ) + ); + } - $query = $view->render([ - 'attributes' => $attributes, - 'locales' => $locales, - ]); + $bus($this->pdo); $view = $this->twig->load('extract-products.sql.twig'); @@ -41,7 +103,7 @@ public function __invoke(\SplFileObject $output, array $attributes, array $local ]), $output ); - } catch (LoaderError|RuntimeError|SyntaxError $e) { + } catch (\RuntimeException|LoaderError|RuntimeError|SyntaxError|FatalThrowableError $e) { throw new \RuntimeException('An error occurred during the product data extraction.', null, $e); } } diff --git a/src/App/Domain/Magento/Attribute.php b/src/App/Domain/Magento/Attribute.php new file mode 100644 index 0000000..eb15572 --- /dev/null +++ b/src/App/Domain/Magento/Attribute.php @@ -0,0 +1,9 @@ +code = $code; } - public function codeInSource(): string + public function code(): string { return $this->code; } - public function codeInDestination(): string + public function source(): string { return $this->code; } diff --git a/src/App/Domain/Magento/Attribute/Aliased.php b/src/App/Domain/Magento/Attribute/Aliased.php new file mode 100644 index 0000000..3207a04 --- /dev/null +++ b/src/App/Domain/Magento/Attribute/Aliased.php @@ -0,0 +1,29 @@ +code = $code; + $this->source = $source; + } + + public function code(): string + { + return $this->code; + } + + public function source(): string + { + return $this->source; + } +} \ No newline at end of file diff --git a/src/Attribute/ExNihilo.php b/src/App/Domain/Magento/Attribute/ExNihilo.php similarity index 58% rename from src/Attribute/ExNihilo.php rename to src/App/Domain/Magento/Attribute/ExNihilo.php index 04a98b5..b6f43b4 100644 --- a/src/Attribute/ExNihilo.php +++ b/src/App/Domain/Magento/Attribute/ExNihilo.php @@ -1,8 +1,8 @@ code = $code; } - public function codeInSource(): string + public function code(): string { return $this->code; } - public function codeInDestination(): string + public function source(): string { return $this->code; } diff --git a/src/AttributeRenderer.php b/src/App/Domain/Magento/AttributeRenderer.php similarity index 89% rename from src/AttributeRenderer.php rename to src/App/Domain/Magento/AttributeRenderer.php index a388b4d..7237c92 100644 --- a/src/AttributeRenderer.php +++ b/src/App/Domain/Magento/AttributeRenderer.php @@ -1,6 +1,6 @@ attribute = $attribute; } + public function attribute(): string + { + return $this->attribute->code(); + } + public function table(): string { return strtr( 'tmp_{{ attribute }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code, ] ); } @@ -38,6 +43,6 @@ public function alias(): string public function column(): string { - return $this->attribute->codeInDestination(); + return $this->attribute->code(); } } \ No newline at end of file diff --git a/src/CodeGenerator/Localized.php b/src/App/Domain/Magento/CodeGenerator/Localized.php similarity index 72% rename from src/CodeGenerator/Localized.php rename to src/App/Domain/Magento/CodeGenerator/Localized.php index 7dcbb96..2e80d89 100644 --- a/src/CodeGenerator/Localized.php +++ b/src/App/Domain/Magento/CodeGenerator/Localized.php @@ -1,10 +1,10 @@ locale = $locale; } + public function attribute(): string + { + return $this->attribute->code(); + } + public function table(): string { return strtr( 'tmp_{{ attribute }}_{{ locale }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code, '{{ locale }}' => $this->locale->code(), ] ); @@ -52,7 +57,7 @@ public function column(): string return strtr( '{{ attribute }}-{{ locale }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code(), '{{ locale }}' => $this->locale->code(), ] ); diff --git a/src/CodeGenerator/Scoped.php b/src/App/Domain/Magento/CodeGenerator/Scoped.php similarity index 72% rename from src/CodeGenerator/Scoped.php rename to src/App/Domain/Magento/CodeGenerator/Scoped.php index 3e81025..b6ba247 100644 --- a/src/CodeGenerator/Scoped.php +++ b/src/App/Domain/Magento/CodeGenerator/Scoped.php @@ -1,11 +1,10 @@ scope = $scope; } + public function attribute(): string + { + return $this->attribute->code(); + } + public function table(): string { return strtr( 'tmp_{{ attribute }}_{{ scope }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code(), '{{ scope }}' => $this->scope->code(), ] ); @@ -53,7 +57,7 @@ public function column(): string return strtr( '{{ attribute }}-{{ scope }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code(), '{{ scope }}' => $this->scope->code(), ] ); diff --git a/src/CodeGenerator/ScopedAndLocalized.php b/src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php similarity index 76% rename from src/CodeGenerator/ScopedAndLocalized.php rename to src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php index dd144a6..202f0a9 100644 --- a/src/CodeGenerator/ScopedAndLocalized.php +++ b/src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php @@ -1,11 +1,11 @@ locale = $locale; } + public function attribute(): string + { + return $this->attribute->code(); + } + public function table(): string { return strtr( 'tmp_{{ attribute }}_{{ scope }}_{{ locale }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code(), '{{ scope }}' => $this->scope->code(), '{{ locale }}' => $this->locale->code(), ] @@ -59,7 +64,7 @@ public function column(): string return strtr( '{{ attribute }}-{{ locale }}-{{ scope }}', [ - '{{ attribute }}' => $this->attribute->codeInSource(), + '{{ attribute }}' => $this->attribute->code(), '{{ scope }}' => $this->scope->code(), '{{ locale }}' => $this->locale->code(), ] diff --git a/src/Field.php b/src/App/Domain/Magento/Field.php similarity index 91% rename from src/Field.php rename to src/App/Domain/Magento/Field.php index b12cb51..1e57e48 100644 --- a/src/Field.php +++ b/src/App/Domain/Magento/Field.php @@ -1,6 +1,6 @@ $attribute->codeInDestination(), + '{{ attribute }}' => $attribute->code(), ] ); }), @@ -36,7 +36,7 @@ public function getfunctions() return strtr( 'tmp_{{ attribute }}_default', [ - '{{ attribute }}' => $attribute->codeInDestination(), + '{{ attribute }}' => $attribute->code(), ] ); }), @@ -44,7 +44,7 @@ public function getfunctions() return strtr( 'tmp_{{ attribute }}_{{ locale }}', [ - '{{ attribute }}' => $attribute->codeInDestination(), + '{{ attribute }}' => $attribute->code(), '{{ locale }}' => $locale->code(), ] ); @@ -53,7 +53,7 @@ public function getfunctions() return strtr( 'tmp_{{ attribute }}_{{ scope }}', [ - '{{ attribute }}' => $attribute->codeInDestination(), + '{{ attribute }}' => $attribute->code(), '{{ scope }}' => $scope->code(), ] ); @@ -62,7 +62,7 @@ public function getfunctions() return strtr( 'tmp_{{ attribute }}_{{ scope }}_{{ locale }}', [ - '{{ attribute }}' => $attribute->codeInDestination(), + '{{ attribute }}' => $attribute->code(), '{{ scope }}' => $scope->code(), '{{ locale }}' => $locale->code(), ] diff --git a/src/VariantAxis.php b/src/App/Domain/Magento/VariantAxis.php similarity index 57% rename from src/VariantAxis.php rename to src/App/Domain/Magento/VariantAxis.php index 3ab5cf0..ffe4df1 100644 --- a/src/VariantAxis.php +++ b/src/App/Domain/Magento/VariantAxis.php @@ -1,6 +1,6 @@ walk($attributes, $axisAttributes, $config)); + } + + /** + * @param Attribute[] $attributes + * @param Attribute[] $axisAttributes + * @param array $config + * + * @return AttributeRenderer[]|\Iterator + */ + public function walk(iterable $attributes, iterable $axisAttributes, array $config): \Iterator + { + foreach ($attributes as $attribute) { + if (!isset($config[$attribute->code()])) { + continue; + } + + $attributeSpec = $config[$attribute->code()]; + + switch ($attributeSpec['type']) { + case 'datetime': + yield new AttributeRenderer\Datetime( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + + case 'simple-select': + yield new AttributeRenderer\SimpleSelect( + $attribute, + $this->buildFieldResolver( + in_array($attribute, $axisAttributes), + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + + case 'image': + yield new AttributeRenderer\Image( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + + case 'status': + yield new AttributeRenderer\Status( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + + case 'text': + yield new AttributeRenderer\Text( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + + case 'varchar': + yield new AttributeRenderer\Varchar( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + + case 'visibility': + yield new AttributeRenderer\Visibility( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false + ) + ); + break; + } + } + } + + private function buildFieldResolver(bool $isAxis, bool $scoped, bool $localised): FieldResolver + { + if ($isAxis === true) { + return new FieldResolver\VariantAxis(); + } + if ($localised === true && $scoped === true) { + return new FieldResolver\ScopedAndLocalized(); + } + if ($localised !== true && $scoped === true) { + return new FieldResolver\Scoped(); + } + if ($localised === true && $scoped !== true) { + return new FieldResolver\Localized(); + } + + return new FieldResolver\Globalised(); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Command/CommandBus.php b/src/App/Infrastructure/Command/CommandBus.php new file mode 100644 index 0000000..5f3e972 --- /dev/null +++ b/src/App/Infrastructure/Command/CommandBus.php @@ -0,0 +1,29 @@ +commands = new \SplQueue(); + $this->add(...$commands); + } + + public function add(CommandInterface ...$commands) + { + foreach ($commands as $command) { + $this->commands->enqueue($command); + } + } + + public function __invoke(\PDO $connection): void + { + foreach ($this->commands as $command) { + $command($connection); + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Command/CommandInterface.php b/src/App/Infrastructure/Command/CommandInterface.php new file mode 100644 index 0000000..c60af19 --- /dev/null +++ b/src/App/Infrastructure/Command/CommandInterface.php @@ -0,0 +1,8 @@ +twig = $twig; + $this->twigTemplate = $twigTemplate; + $this->twigContext = $twigContext; + $this->logger = $logger ?? new NullLogger(); + } + + public function __invoke(\PDO $connection): void + { + try { + $this->logger->debug('Compiling template {template}.', ['template' => $this->twigTemplate]); + + $view = $this->twig->load($this->twigTemplate); + + $this->logger->debug($query = $view->render($this->twigContext)); + + $connection->exec($query); + } catch (LoaderError|RuntimeError|SyntaxError $e) { + throw new \RuntimeException( + 'An error occurred during the data preparation SQL rendering: ' + . ($query ?? 'query was not generated'), null, $e); + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Configuration/Configuration.php b/src/App/Infrastructure/Configuration/Configuration.php index f7271a2..a61c3f5 100644 --- a/src/App/Infrastructure/Configuration/Configuration.php +++ b/src/App/Infrastructure/Configuration/Configuration.php @@ -13,10 +13,12 @@ public function getConfigTreeBuilder() $treeBuilder->getRootNode() ->children() - ->arrayNode('attributes')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayNode('attributes')->cannotBeEmpty()->useAttributeAsKey('code', false) ->arrayPrototype() ->children() ->scalarNode('code')->end() + ->booleanNode('scoped')->end() + ->booleanNode('localised')->end() ->arrayNode('label') ->cannotBeEmpty() ->scalarPrototype()->end() @@ -49,7 +51,7 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() - ->arrayNode('groups')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayNode('groups')->cannotBeEmpty()->useAttributeAsKey('code', false) ->arrayPrototype() ->children() ->scalarNode('code')->end() @@ -60,7 +62,7 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() - ->arrayNode('families')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayNode('families')->cannotBeEmpty()->useAttributeAsKey('code', false) ->arrayPrototype() ->children() ->scalarNode('code')->end() @@ -113,15 +115,16 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() - ->arrayNode('locales')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayNode('locales')->cannotBeEmpty()->useAttributeAsKey('code', false) ->arrayPrototype() ->children() ->scalarNode('code')->end() ->scalarNode('currency')->end() + ->scalarNode('store')->end() ->end() ->end() ->end() - ->arrayNode('scopes')->cannotBeEmpty()->useAttributeAsKey('code') + ->arrayNode('scopes')->cannotBeEmpty()->useAttributeAsKey('code', false) ->arrayPrototype() ->children() ->scalarNode('code')->end() diff --git a/src/App/Infrastructure/Console/Command/GoCommand.php b/src/App/Infrastructure/Console/Command/GoCommand.php index 807fd4f..11a3c89 100644 --- a/src/App/Infrastructure/Console/Command/GoCommand.php +++ b/src/App/Infrastructure/Console/Command/GoCommand.php @@ -3,7 +3,16 @@ namespace App\Infrastructure\Console\Command; use App\Domain\Fixture; +use App\Domain\Magento\Attribute; +use App\Domain\Magento\Locale; +use App\Domain\Magento\Scope; +use App\Domain\Magento\TwigExtension; +use App\Infrastructure\AttributeRendererFactory; use App\Infrastructure\Configuration\YamlFileLoader; +use App\Infrastructure\Normalizer\Magento\AttributeDenormalizerFactory; +use App\Infrastructure\Normalizer\Magento\LocaleDenormalizerFactory; +use App\Infrastructure\Normalizer\Magento\ScopeDenormalizerFactory; +use App\Infrastructure\VariantAxisesFactory; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Symfony\Component\Config\Exception\LoaderLoadException; @@ -18,6 +27,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Twig\Environment; +use Twig\Extension\DebugExtension; use Twig\Loader\FilesystemLoader; class GoCommand extends Command @@ -95,16 +105,26 @@ protected function execute(InputInterface $input, OutputInterface $output) return -1; } - $pdo = new \PDO( - $input->getOption('dsn') ?? $_ENV['APP_DSN'] ?? 'mysql:host=localhost;dbname=magento', - $input->getOption('username') ?? $_ENV['APP_USERNAME'] ?? 'root', - $input->getOption('password') ?? $_ENV['APP_PASSWORD'] ?? null, - [ - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, - \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', - \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, - ] - ); + try { + $pdo = new \PDO( + $input->getOption('dsn') ?? $_ENV['APP_DSN'] ?? 'mysql:host=localhost;dbname=magento', + $input->getOption('username') ?? $_ENV['APP_USERNAME'] ?? 'root', + $input->getOption('password') ?? $_ENV['APP_PASSWORD'] ?? null, + [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + ] + ); + } catch (\PDOException $e) { + $style->error($e->getMessage()); + $style->error(strtr('Connection parameters were: %dsn% with user %user%, using password: %password%.', [ + '%dsn%' => $input->getOption('dsn') ?? $_ENV['APP_DSN'] ?? 'mysql:host=localhost;dbname=magento', + '%user%' => $input->getOption('username') ?? $_ENV['APP_USERNAME'] ?? 'root', + '%password%' => ($input->getOption('password') ?? $_ENV['APP_PASSWORD'] ?? null) !== null ? 'Yes' : 'No', + ])); + return -1; + } $twig = new Environment( new FilesystemLoader([ @@ -112,9 +132,13 @@ protected function execute(InputInterface $input, OutputInterface $output) ]), [ 'autoescape' => false, + 'debug' => true, ] ); + $twig->addExtension(new DebugExtension()); + $twig->addExtension(new TwigExtension()); + (new Fixture\Command\ExtractLocales())( new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/locales.yml', 'w'), $config['locales'] @@ -164,10 +188,24 @@ protected function execute(InputInterface $input, OutputInterface $output) $style->writeln('family variants ok', SymfonyStyle::OUTPUT_PLAIN); - (new Fixture\Command\ExtractSimpleProducts($pdo, $twig))( + $attributes = (new AttributeDenormalizerFactory())() + ->denormalize($config['attributes'], Attribute::class.'[]'); + $scopes = (new ScopeDenormalizerFactory())() + ->denormalize($config['scopes'], Scope::class.'[]'); + $locales = (new LocaleDenormalizerFactory())() + ->denormalize($config['locales'], Locale::class.'[]'); + + /** @var Attribute[] $axises */ + $axises = (new VariantAxisesFactory($attributes))($config); + + $attributeRenderersFactory = new AttributeRendererFactory(); + $attributeRenderers = $attributeRenderersFactory($attributes, $axises, $config['attributes']); + + (new Fixture\Command\ExtractSimpleProducts($pdo, $twig, $this->logger))( new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/products.csv', 'w'), - $config['families'], - array_keys($config['locales']) + $attributeRenderers, + array_keys($config['locales']), + $config['families'] ); $style->writeln('products ok', SymfonyStyle::OUTPUT_PLAIN); diff --git a/src/App/Infrastructure/FieldsFactory.php b/src/App/Infrastructure/FieldsFactory.php new file mode 100644 index 0000000..49a04c8 --- /dev/null +++ b/src/App/Infrastructure/FieldsFactory.php @@ -0,0 +1,136 @@ +attributes = $attributes; + $this->scopes = $scopes; + $this->locales = $locales; + $this->axises = $axises; + } + + /** + * @return Field[] + */ + public function __invoke(array $config): array + { + return iterator_to_array($this->flatten($config)); + } + + private function flatten(array $config): \Iterator + { + foreach ($this->map($config) as $group) { + yield from $group; + } + } + + private function map(array $config): iterable + { + return array_map(function (array $config) { + try { + $attribute = $this->findAxis($config['code']); + yield from (new FieldResolver\VariantAxis()) + ->fields($attribute); + return; + } catch (AttributeNotFoundException $e) { + if ((isset($config['localised']) && $config['localised'] === true) && + (isset($config['scoped']) && $config['scoped'] === true) + ) { + yield from (new FieldResolver\ScopedAndLocalized()) + ->fields($this->findAttribute($config['code'])); + } else if ((!isset($config['localised']) || $config['localised'] !== true) && + (isset($config['scoped']) && $config['scoped'] === true) + ) { + yield from (new FieldResolver\Scoped()) + ->fields($this->findAttribute($config['code'])); + } else if ((isset($config['localised']) && $config['localised'] === true) && + (!isset($config['scoped']) || $config['scoped'] !== true) + ) { + yield from (new FieldResolver\Localized()) + ->fields($this->findAttribute($config['code'])); + } else { + yield from (new FieldResolver\Globalised()) + ->fields($this->findAttribute($config['code'])); + } + } + }, $config['attributes']); + } + + private function findAttribute(string $code): Attribute + { + $attributes = array_filter($this->attributes, function (Attribute $attribute) use ($code) { + return $attribute->code() === $code; + }); + + if (count($attributes) > 1) { + throw new AttributeNotFoundException(strtr( + 'Found several attributes configuration with code "%code%".', + [ + '%code%' => $code, + ] + )); + } + + if (count($attributes) < 1) { + throw new AttributeNotFoundException(strtr( + 'Attribute with code "%code%" was not found in configuration.', + [ + '%code%' => $code, + ] + )); + } + + return array_pop($attributes); + } + + private function findAxis(string $code): Attribute + { + $attributes = array_filter($this->axises, function (Attribute $attribute) use ($code) { + return $attribute->code() === $code; + }); + + if (count($attributes) > 1) { + throw new AttributeNotFoundException(strtr( + 'Found several axis attributes configuration with code "%code%".', + [ + '%code%' => $code, + ] + )); + } + + if (count($attributes) < 1) { + throw new AttributeNotFoundException(strtr( + 'Attribute axis with code "%code%" was not found in configuration.', + [ + '%code%' => $code, + ] + )); + } + + return array_pop($attributes); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/ListDenormalizer.php b/src/App/Infrastructure/Normalizer/ListDenormalizer.php new file mode 100644 index 0000000..3681050 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/ListDenormalizer.php @@ -0,0 +1,35 @@ +denormalizer = $denormalizer; + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + return iterator_to_array($this->denormalizeChild($data, substr($type, 0, -2), $format, $context)); + } + + private function denormalizeChild(iterable $object, $type, $format, array $context): \Iterator + { + foreach ($object as $item) { + yield $this->denormalizer->denormalize($item, $type, $format, $context); + } + } + + public function supportsDenormalization($data, $type, $format = null) + { + return substr($type, -2) === '[]' + && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2)); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/ListNormalizer.php b/src/App/Infrastructure/Normalizer/ListNormalizer.php index 1302426..09d20ba 100644 --- a/src/App/Infrastructure/Normalizer/ListNormalizer.php +++ b/src/App/Infrastructure/Normalizer/ListNormalizer.php @@ -5,19 +5,14 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class ListNormalizer implements NormalizerInterface, DenormalizerInterface +class ListNormalizer implements NormalizerInterface { /** @var NormalizerInterface */ private $normalizer; - /** @var DenormalizerInterface */ - private $denormalizer; - public function __construct( - NormalizerInterface $normalizer, - DenormalizerInterface $denormalizer - ) { + public function __construct(NormalizerInterface $normalizer) + { $this->normalizer = $normalizer; - $this->denormalizer = $denormalizer; } public function normalize($object, $format = null, array $context = []) @@ -25,24 +20,10 @@ public function normalize($object, $format = null, array $context = []) return iterator_to_array($this->normalizeChild($object, substr($format, 0, -2), $context)); } - public function normalizeChild(iterable $object, $format, array $context): \Iterator - { - $childFormat = substr($format, 0, -2); - foreach ($object as $item) { - yield $this->normalizer->normalize($item, $childFormat, $context); - } - } - - public function denormalize($data, $type, $format = null, array $context = []) + private function normalizeChild(iterable $object, $format, array $context): \Iterator { - return iterator_to_array($this->denormalizeChild($data, substr($type, 0, -2), $format, $context)); - } - - public function denormalizeChild(iterable $object, $type, $format, array $context): \Iterator - { - $childType = substr($type, 0, -2); foreach ($object as $item) { - yield $this->denormalizer->denormalize($item, $childType, $format, $context); + yield $this->normalizer->normalize($item, $format, $context); } } @@ -50,10 +31,4 @@ public function supportsNormalization($data, $format = null) { return is_iterable($data); } - - public function supportsDenormalization($data, $type, $format = null) - { - return substr($type, -2) === '[]' - && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2)); - } } \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php b/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php new file mode 100644 index 0000000..7fb3ce0 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php @@ -0,0 +1,22 @@ +strategiesDenormalizers = $strategiesDenormalizers; + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + $denormalizer = $this->findDenormalizer($data, $type, $format); + + return $denormalizer->denormalize($data, $type, $format, $context); + } + + public function supportsDenormalization($data, $type, $format = null) + { + try { + $this->findDenormalizer($data, $type, $format); + } catch (NoSuitableDenormalizer $e) { + return false; + } + + return true; + } + + private function findDenormalizer($data, $type, $format = null, array $context = []): DenormalizerInterface + { + foreach ($this->strategiesDenormalizers as $denormalizer) { + if ($denormalizer->supportsDenormalization($data, $type, $format)) { + return $denormalizer; + } + } + + throw new NoSuitableDenormalizer(isset($data['strategy']) ? + strtr( + 'There is no available denormalizer for this attribute strategy "%strategy%".', + [ + '%strategy%' => $data['strategy'], + ] + ) : + 'There is no available denormalizer for this attribute strategy.' + ); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Magento/AttributeDenormalizerFactory.php b/src/App/Infrastructure/Normalizer/Magento/AttributeDenormalizerFactory.php new file mode 100644 index 0000000..8f0c490 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Magento/AttributeDenormalizerFactory.php @@ -0,0 +1,20 @@ +localesDenormalizer = $localesDenormalizer; + } + + public function denormalize($data, $type, $format = null, array $context = []) + { + return new Scope\Scope( + $data['code'], + new MagentoStore($data['store']), + ...$this->localesDenormalizer->denormalize($data['locales'], Locale::class.'[]', $format, $context) + ); + } + + public function supportsDenormalization($data, $type, $format = null) + { + return isset($data['code']) + && is_string($data['code']) + && isset($data['store']) + && is_numeric($data['store']) + && isset($data['locales']) + && is_array($data['locales']) + && $type === Scope::class; + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/Normalizer/Magento/ScopeDenormalizerFactory.php b/src/App/Infrastructure/Normalizer/Magento/ScopeDenormalizerFactory.php new file mode 100644 index 0000000..23090b0 --- /dev/null +++ b/src/App/Infrastructure/Normalizer/Magento/ScopeDenormalizerFactory.php @@ -0,0 +1,20 @@ +attributes = $attributes; + } + + public function __invoke(array $config): array + { + $codes = []; + foreach (array_unique(iterator_to_array($this->walk($config))) as $code) { + array_push($codes, $this->findAttribute($code)); + } + + return $codes; + } + + private function walk(array $config): \Iterator + { + foreach ($config['families'] as $family) { + foreach ($family['variations'] as $variation) { + yield from $variation['level_1']['axis']; + + if (isset($variation['level_2']['axis'])) { + yield from $variation['level_2']['axis']; + } + } + } + } + + private function findAttribute(string $code): Attribute + { + $attributes = array_filter($this->attributes, function (Attribute $attribute) use ($code) { + return $attribute->code() === $code; + }); + + if (count($attributes) > 1) { + throw new AttributeNotFoundException(strtr( + 'Found several attributes configuration with code "%code%".', + [ + '%code%' => $code, + ] + )); + } + + if (count($attributes) < 1) { + throw new AttributeNotFoundException(strtr( + 'Attribute with code "%code%" was not found in configuration.', + [ + '%code%' => $code, + ] + )); + } + + return array_pop($attributes); + } +} \ No newline at end of file diff --git a/src/App/Resources/templates/product/00-sku.sql.twig b/src/App/Resources/templates/product/00-sku.sql.twig index 23b33bb..5ed8218 100644 --- a/src/App/Resources/templates/product/00-sku.sql.twig +++ b/src/App/Resources/templates/product/00-sku.sql.twig @@ -4,7 +4,7 @@ CREATE TEMPORARY TABLE tmp_sku ( type_id VARCHAR(32) NOT NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.sku, product.entity_id, diff --git a/src/App/Resources/templates/product/01-attribute-options.sql.twig b/src/App/Resources/templates/product/01-attribute-options.sql.twig index 05c84b1..7f4541f 100644 --- a/src/App/Resources/templates/product/01-attribute-options.sql.twig +++ b/src/App/Resources/templates/product/01-attribute-options.sql.twig @@ -7,7 +7,7 @@ CREATE TEMPORARY TABLE tmp_options ( PRIMARY KEY (option_id), INDEX (code, attribute), INDEX (attribute) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT opt_value_default.option_id, {% set codeMapping -%} @@ -26,7 +26,7 @@ SELECT FROM ( {%- set renderedAttributes = [] -%} -{%- for code in attributes -%} +{%- for code in attributes|keys -%} {% set renderedAttribute %} SELECT '{{ code }}' AS code {% endset %} diff --git a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig index a19e7f3..915a15d 100644 --- a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig +++ b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig @@ -1,11 +1,11 @@ -{% for axis in axises %} -CREATE TEMPORARY TABLE {{ as_field_table(axis) }} ( +{% for field in axises %} +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( entity_id INTEGER NOT NULL, code VARCHAR(256) NOT NULL, short_code VARCHAR(256) NOT NULL, PRIMARY KEY (entity_id, code), INDEX (code) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {{ as_default_alias() }}.entity_id, {{ as_default_alias() }}_option.short_code AS short_code, diff --git a/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig index b836eee..0e64f69 100644 --- a/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig @@ -1,27 +1,26 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {%- for field in fields -%} + {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` DATETIME NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 LEFT JOIN catalog_product_entity_datetime AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0 -{% for field in fields -%} +{% for field in renderer.fields -%} LEFT JOIN catalog_product_entity_datetime AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-image-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig similarity index 82% rename from src/App/Resources/templates/product/attribute/extract-image-scopable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig index bc0e9c9..e1d3572 100644 --- a/src/App/Resources/templates/product/attribute/extract-image-scopable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig @@ -1,5 +1,4 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -10,19 +9,19 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0 AND {{ as_default_alias() }}.value!='no_selection' -{% for field in fields -%} +{% for field in renderer.fields -%} LEFT JOIN catalog_product_entity_datetime AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-image.sql.twig b/src/App/Resources/templates/product/attribute/extract-image.sql.twig new file mode 100644 index 0000000..0e7caa1 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-image.sql.twig @@ -0,0 +1,23 @@ +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + {%- for field in fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + {% endfor -%} + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + {% for field in renderer.fields -%} + COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, + {% endfor -%} + product.* +FROM tmp_sku AS product +INNER JOIN eav_attribute AS attribute + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 +LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id + AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id + AND {{ as_default_alias() }}.store_id=0 + AND {{ as_default_alias() }}.value!='no_selection'; diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig index 66ca736..a35dd93 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig @@ -1,18 +1,17 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, - {{ as_default_alias() }}.code + {{ as_default_alias() }}.value FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id @@ -20,11 +19,11 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} LEFT JOIN ( SELECT * FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' + WHERE attribute LIKE '{{ renderer.attribute.code }}' ) AS {{ as_default_alias() }}_option - ON attr_default.value=option_default.option_id; + ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; -{% for field in fields %} +{% for field in renderer.fields %} CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, @@ -32,7 +31,7 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, COALESCE( @@ -41,8 +40,8 @@ SELECT ) AS `code` FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 -INNER JOIN {{ as_attribute_default_table(attribute) }} AS {{ as_default_alias() }} + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id @@ -51,28 +50,28 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} LEFT JOIN ( SELECT * FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' + WHERE attribute LIKE '{{ renderer.attribute.code }}' ) AS {{ as_field_alias(field) }}_option ON {{ as_field_alias(field) }}.value={{ as_field_alias(field) }}_option.option_id; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {% for field in fields %} + {% for field in renderer.fields %} `{{ field }}` VARCHAR(255) NOT NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} {{ as_field_alias(field) }}.value AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product -{% for field in fields %} +{% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id {% endfor %}; diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig index 188cd08..28045e2 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig @@ -1,18 +1,17 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, - {{ as_default_alias() }}.code + {{ as_default_alias() }}.value FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id @@ -20,11 +19,11 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} LEFT JOIN ( SELECT * FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' + WHERE attribute LIKE '{{ renderer.attribute.code }}' ) AS {{ as_default_alias() }}_option - ON attr_default.value=option_default.option_id; + ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; -{% for field in fields %} +{% for field in renderer.fields %} CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, @@ -32,7 +31,7 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, COALESCE( @@ -41,8 +40,8 @@ SELECT ) AS `code` FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 -INNER JOIN {{ as_attribute_default_table(attribute) }} AS {{ as_default_alias() }} + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id @@ -51,28 +50,28 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} LEFT JOIN ( SELECT * FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' + WHERE attribute LIKE '{{ renderer.attribute.code }}' ) AS {{ as_field_alias(field) }}_option ON {{ as_field_alias(field) }}.value={{ as_field_alias(field) }}_option.option_id; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {% for field in fields %} + {% for field in renderer.fields %} `{{ field }}` VARCHAR(255) NOT NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} {{ as_field_alias(field) }}.value AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product -{% for field in fields %} +{% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id {% endfor %}; diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig index a96f718..32c59f1 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig @@ -1,18 +1,17 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, - {{ as_default_alias() }}.code + {{ as_default_alias() }}.value FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id @@ -20,6 +19,6 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} LEFT JOIN ( SELECT * FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' + WHERE attribute LIKE '{{ renderer.attribute.code }}' ) AS {{ as_default_alias() }}_option - ON attr_default.value=option_default.option_id; + ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; diff --git a/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-status.sql.twig similarity index 72% rename from src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-status.sql.twig index aa210e4..1886b43 100644 --- a/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-status.sql.twig @@ -1,12 +1,11 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, CASE {{ as_default_alias() }}.value @@ -16,13 +15,13 @@ SELECT END AS value FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0; -{% for field in fields %} +{% for field in renderer.fields %} CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, @@ -30,7 +29,7 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, COALESCE(CASE {{ as_field_alias(field) }}.value @@ -42,8 +41,8 @@ SELECT ) AS `code` FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 -INNER JOIN {{ as_attribute_default_table(attribute) }} AS {{ as_default_alias() }} + ON attribute.attribute_code='{{ attribute.code }}' AND attribute.entity_type_id=4 +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id @@ -51,23 +50,23 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {% for field in fields %} - `{{ field }}` VARCHAR(255) NOT NULL, + {% for field in renderer.fields %} + `{{ as_field_column(field) }}` VARCHAR(255) NOT NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product -{% for field in fields %} +{% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id {% endfor %}; diff --git a/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig index c869198..b1addc6 100644 --- a/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig @@ -1,16 +1,15 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {%- for field in fields -%} + {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` TEXT NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* @@ -21,7 +20,7 @@ LEFT JOIN catalog_product_entity_text AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0 -{% for field in fields -%} +{% for field in renderer.fields -%} LEFT JOIN catalog_product_entity_text AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-text.sql.twig b/src/App/Resources/templates/product/attribute/extract-text.sql.twig index 3578728..4fcfef1 100644 --- a/src/App/Resources/templates/product/attribute/extract-text.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text.sql.twig @@ -1,16 +1,16 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {%- for field in fields -%} + {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` TEXT NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} {{ as_default_alias() }}.value AS `{{ as_field_column(field) }}`, {% endfor -%} product.* diff --git a/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig index 331861b..062cece 100644 --- a/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig @@ -1,16 +1,16 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {%- for field in fields -%} + {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` VARCHAR(255) NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* @@ -21,7 +21,7 @@ LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0 -{% for field in fields -%} +{% for field in renderer.fields -%} LEFT JOIN catalog_product_entity_varchar AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-visibility.sql.twig similarity index 74% rename from src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-visibility.sql.twig index 04a05df..87d0514 100644 --- a/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-visibility.sql.twig @@ -1,12 +1,11 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, CASE {{ as_default_alias() }}.value @@ -18,13 +17,13 @@ SELECT END AS value FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0; -{% for field in fields %} +{% for field in renderer.fields %} CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, @@ -32,7 +31,7 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( code VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT product.*, COALESCE(CASE {{ as_field_alias(field) }}.value @@ -41,12 +40,12 @@ SELECT WHEN 3 THEN 'visible_in_search' WHEN 4 THEN 'visible_in_catalog_and_search' END, - attr_default.code + {{ as_default_alias() }}.code ) AS `code` FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 -INNER JOIN {{ as_attribute_default_table(attribute) }} AS {{ as_default_alias() }} + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id @@ -54,23 +53,23 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {% for field in fields %} + {% for field in renderer.fields %} `{{ field }}` VARCHAR(255) NOT NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) -) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {% for field in fields -%} + {% for field in renderer.fields -%} COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product -{% for field in fields %} +{% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id {% endfor %}; diff --git a/src/Attribute.php b/src/Attribute.php deleted file mode 100644 index 781ef03..0000000 --- a/src/Attribute.php +++ /dev/null @@ -1,9 +0,0 @@ -code = $code; - $this->alias = $alias; - } - - public function codeInSource(): string - { - return $this->code; - } - - public function codeInDestination(): string - { - return $this->alias; - } -} \ No newline at end of file diff --git a/src/FieldResolver.php b/src/FieldResolver.php deleted file mode 100644 index 38cb10d..0000000 --- a/src/FieldResolver.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Wed, 16 Oct 2019 19:08:32 +0200 Subject: [PATCH 3/7] Added support for family variant axises --- README.md | 198 +-------- catalog.yml | 398 ------------------ .../Fixture/Command/ExtractSimpleProducts.php | 39 +- src/App/Domain/Fixture/SqlToCsv.php | 10 +- .../Magento/CodeGenerator/Globalised.php | 7 +- .../Magento/CodeGenerator/Localized.php | 10 +- .../Domain/Magento/CodeGenerator/Scoped.php | 10 +- .../CodeGenerator/ScopedAndLocalized.php | 10 +- src/App/Domain/Magento/FamilyVariant.php | 40 ++ src/App/Domain/Magento/FamilyVariantAxis.php | 14 + ...tension.php => SqlExportTwigExtension.php} | 53 ++- .../AttributeRendererFactory.php | 69 ++- .../Configuration/Configuration.php | 1 + .../Console/Command/GoCommand.php | 38 +- .../Infrastructure/VariantAxisesFactory.php | 55 ++- .../templates/extract-products.sql.twig | 16 + .../Resources/templates/initialize.sql.twig | 62 ++- .../product/01-attribute-options.sql.twig | 4 +- .../02-prefill-axis-attributes.sql.twig | 10 +- ...erate-intermediate-product-models.sql.twig | 48 +++ ...xtract-image-scopable-localizable.sql.twig | 4 +- .../product/attribute/extract-image.sql.twig | 4 +- .../extract-simpleselect-scopable.sql.twig | 4 +- .../attribute/extract-simpleselect.sql.twig | 10 +- 24 files changed, 424 insertions(+), 690 deletions(-) delete mode 100644 catalog.yml create mode 100644 src/App/Domain/Magento/FamilyVariant.php create mode 100644 src/App/Domain/Magento/FamilyVariantAxis.php rename src/App/Domain/Magento/{TwigExtension.php => SqlExportTwigExtension.php} (54%) create mode 100644 src/App/Resources/templates/extract-products.sql.twig create mode 100644 src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig diff --git a/README.md b/README.md index dfce6d3..6ef8d6b 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,28 @@ Akeneo Fixtures Generation Toolbox ================================== -This classes and templates toolbox helps generating CSV fixtues from Magento 1.9CE or 1.14EE catalog. +This toolbox helps generating CSV fixtures consumed by Akeneo's InstallerBundle from Magento 1.9CE or 1.14EE catalog data. -Example -------- - -```php -#!/ur/bin/env php -addExtension(new TwigExtension()); - -$locales = [ - $fr_FR = new Locale\Locale('fr_FR', new MagentoStore(1)), - $de_DE = new Locale\Locale('de_DE', new MagentoStore(2)), - $en_GB = new Locale\Locale('en_GB', new MagentoStore(2)), - $en_US = new Locale\Locale('en_US', new MagentoStore(4)), - $ja_JP = new Locale\Locale('ja_JP', new MagentoStore(5)), - $fr_CA = new Locale\Locale('fr_CA', new MagentoStore(8)), -]; - -$scopes = [ - new Scope\Scope( - 'europe', - new MagentoStore(1), - $fr_FR, - $de_DE, - $en_GB - ), - new Scope\Scope( - 'america', - new MagentoStore(4), - $en_US, - $fr_CA, - new Locale\LocaleMapping($en_US, new MagentoStore(9)) // Additional locale mapping for Canada - ), - new Scope\Scope( - 'asia', - new MagentoStore(5), - $ja_JP, - new Locale\LocaleMapping($en_GB, new MagentoStore(6)) // Additional locale mapping for Hong Kong - ), -]; - -$globalized = new FieldResolver\Globalised(); -$localized = new FieldResolver\Localized(...$locales); -$scoped = new FieldResolver\Scoped(...$scopes); -$scopedAndLocalized = new FieldResolver\ScopedAndLocalized(...$scopes); -$axis = new FieldResolver\VariantAxis(); - -$renderer = new Renderer( - 'initialize.sql.twig', - 'finalize-product-parents.sql.twig', - ['configurable'], - // 1st level Product Models - new AttributeRenderer\Image( - new Attribute\AdHoc('image'), - $scoped - ), - new AttributeRenderer\Varchar( - new Attribute\AdHoc('name'), - $scopedAndLocalized - ), - new AttributeRenderer\Text( - new Attribute\AdHoc('description'), - $scopedAndLocalized - ), - new AttributeRenderer\Text( - new Attribute\AdHoc('short_description'), - $scopedAndLocalized - ), - new AttributeRenderer\Varchar( - new Attribute\AdHoc('meta_title'), - $scopedAndLocalized - ), - new AttributeRenderer\Text( - new Attribute\AdHoc('meta_description'), - $scopedAndLocalized - ), - new AttributeRenderer\SimpleSelect( - new Attribute\AdHoc('model'), - $scoped - ), - new AttributeRenderer\SimpleSelect( - new Attribute\AdHoc('manufacturer'), - $scoped - ) -); - -$renderer(fopen('products_models1.sql', 'w'), $twig); - -$renderer = new Renderer( - 'initialize.sql.twig', - 'finalize-product-children.sql.twig', - [], - // 2nd level Product Models - new AttributeRenderer\SimpleSelect( - new Attribute\AdHoc('color'), - $axis - ) -); - -$renderer(fopen('products_models2.sql', 'w'), $twig); - -$renderer = new Renderer( - 'initialize.sql.twig', - 'finalize-products.sql.twig', - ['simple', 'virtual'], - // Product & Product variants - new AttributeRenderer\Status( - new Attribute\AdHoc('status'), - $scopedAndLocalized - ), - new AttributeRenderer\Visibility( - new Attribute\AdHoc('visibility'), - $scopedAndLocalized - ), - new AttributeRenderer\SimpleSelect( - new Attribute\AdHoc('size'), - $axis - ), - new AttributeRenderer\Image( - new Attribute\Aliased('image', 'variation_image'), - $scoped - ), - new AttributeRenderer\Varchar( - new Attribute\Aliased('name', 'variation_name'), - $scopedAndLocalized - ), - new AttributeRenderer\Text( - new Attribute\Aliased('description', 'variation_description'), - $scopedAndLocalized - ), - new AttributeRenderer\Datetime( - new Attribute\AdHoc('news_from_date'), - $scopedAndLocalized - ), - new AttributeRenderer\Datetime( - new Attribute\AdHoc('news_to_date'), - $scopedAndLocalized - ) -); - -$renderer(fopen('products.sql', 'w'), $twig); -``` Supported attribute types --- -| Magento Type | Akeneo Type | Not Localizable, not Scopable | Localizable, not Scopable | Not Localizable, Scopable | Localizable, Scopable | -| --- | --- | --- | --- | --- | --- | -| Gallery | Asset Collection | ❌ | ❌ | ❌ | ❌ | -| Datetime | Date | ✅ | ❌ | ❌ | ✅ | -| File | File | ❌ | ❌ | ❌ | ❌ | -| SKU | Identifier | ✅ | ❌ | ❌ | ❌ | -| Image | Image | ✅ | ❌ | ❌ | ✅ | -| Decimal | Metric | ❌ | ❌ | ❌ | ❌ | -| Multiselect | Multi select | ❌ | ❌ | ❌ | ❌ | -| Select | Simple select | ✅ | ❌ | ✅ | ✅ | -| Number | Number | ❌ | ❌ | ❌ | ❌ | -| Price | Price | ❌ | ❌ | ❌ | ❌ | -| Status | Simple select | ✅ | ❌ | ❌ | ❌ | -| - | Reference data multi select | ❌ | ❌ | ❌ | ❌ | -| - | Reference data simple select | ❌ | ❌ | ❌ | ❌ | -| Text | Text area | ✅ | ❌ | ❌ | ✅ | -| Varchar | Text | ✅ | ❌ | ❌ | ✅ | -| Visibility | Simple select | ✅ | ❌ | ❌ | ❌ | -| YesNo | Yes No | ❌ | ❌ | ❌ | ❌ | +| Magento Type | Akeneo Type | Not Localizable, not Scopable | Localizable, not Scopable | Not Localizable, Scopable | Localizable, Scopable | +| ------------ | ------------------ | --- | --- | --- | --- | +| Gallery | Asset Collection | ❌ | ❌ | ❌ | ❌ | +| Datetime | Date | ✅ | ❌ | ❌ | ✅ | +| File | File | ❌ | ❌ | ❌ | ❌ | +| SKU | Identifier | ✅ | ❌ | ❌ | ❌ | +| Image | Image | ✅ | ❌ | ❌ | ✅ | +| Decimal | Metric | ❌ | ❌ | ❌ | ❌ | +| Multiselect | Multi select | ❌ | ❌ | ❌ | ❌ | +| Select | Simple select | ✅ | ❌ | ✅ | ✅ | +| Number | Number | ❌ | ❌ | ❌ | ❌ | +| Price | Price | ❌ | ❌ | ❌ | ❌ | +| Status | Simple select | ✅ | ❌ | ❌ | ❌ | +| - | Ref. multi select | ❌ | ❌ | ❌ | ❌ | +| - | Ref. simple select | ❌ | ❌ | ❌ | ❌ | +| Text | Text area | ✅ | ❌ | ❌ | ✅ | +| Varchar | Text | ✅ | ❌ | ❌ | ✅ | +| Visibility | Simple select | ✅ | ❌ | ❌ | ❌ | +| YesNo | Yes No | ❌ | ❌ | ❌ | ❌ | diff --git a/catalog.yml b/catalog.yml deleted file mode 100644 index 83dd515..0000000 --- a/catalog.yml +++ /dev/null @@ -1,398 +0,0 @@ -catalog: - attributes: - - code: sku - type: identifier - strategy: ad-hoc - group: general - - code: name - type: string - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: variation_name - type: string - strategy: ex-nihilo - group: general - scoped: true - localised: true - - code: description - type: rich-text - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: variation_description - type: rich-text - strategy: aliased - source: description - group: general - scoped: true - localised: true - - code: short_description - type: rich-text - strategy: ex-nihilo - group: general - scoped: true - localised: true - - code: meta_title - type: string - strategy: ad-hoc - source: meta_title - group: general - scoped: true - localised: true - - code: meta_description - type: text - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: status - type: status - strategy: ad-hoc - group: general - - code: visibility - type: visibility - strategy: ad-hoc - group: general - - code: capacity - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: grade - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: color - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: news_from_date - type: datetime - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: news_to_date - type: datetime - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: manufacturer - type: datetime - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: pastille - type: simple-select - strategy: ex-nihilo - group: general - scoped: true - localised: true - - code: recom_fr_3g - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_4g - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_accessoires - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_bluetooth - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_cartesd - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_das - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_deblocage - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_dimensions - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_ecran - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_garantie - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_iris - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_jack - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_lightning - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_nfc - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_photo - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_poids - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_resolution_ecran - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_sim - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: recom_fr_touchid - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: false - - code: model - type: simple-select - strategy: ad-hoc - group: general - scoped: true - localised: true - - code: weight - type: metric - strategy: ad-hoc - group: general - metric: - family: Weight - unit: KILOGRAM - - code: image - type: image - strategy: ad-hoc - group: general - - code: variation_image - type: image - strategy: ex-nihilo - group: general - scoped: true - localised: true - - groups: - - code: general - label: - de_CH: General - de_DE: General - en_GB: General - es_ES: General - fr_CH: Général - fr_FR: Général - it_CH: General - it_IT: General - - families: - - code: mobile - attributes: [ name, description, short_description, meta_title, meta_description, status, visibility, image, recom_fr_3g, recom_fr_4g, recom_fr_accessoires, recom_fr_bluetooth, recom_fr_cartesd, recom_fr_das, recom_fr_deblocage, recom_fr_dimensions, recom_fr_ecran, recom_fr_garantie, recom_fr_iris, recom_fr_jack, recom_fr_lightning, recom_fr_nfc, recom_fr_photo , recom_fr_poids, recom_fr_resolution_ecran, recom_fr_sim, recom_fr_touchid, model, manufacturer, color, capacity, grade, variation_name, variation_image, variation_description, news_to_date, news_from_date ] - label: name - image: image - requirements: - - scope: recommerce_eu - attributes: [ name, description ] - - scope: recommerce_ch - attributes: [ name, description ] - - scope: bmo_digitech - attributes: [ name, description ] - - scope: bmo_bouygues - attributes: [ name, description ] - - scope: bmo_mbudget - attributes: [ name, description ] - - scope: bmo_ricardo - attributes: [ name, description ] - - scope: amazon - attributes: [ name, description ] - - scope: advise - attributes: [ name, description ] - variations: - - code: mobile_color_capacity_grade - level-1: - axis: [ color, capacity ] - attributes: [ variation_name, variation_image, variation_description ] - level-2: - axis: [ grade ] - attributes: [ sku, status, visibility, news_from_date, news_to_date ] - - code: mobile_groupon - level-1: - axis: [ color, capacity ] - attributes: [ variation_name, variation_image, variation_description ] - - locales: - - code: de_CH - currency: CHF - store: 15 - - code: de_DE - currency: EUR - store: 21 - - code: en_GB - currency: GBP - store: 16 - - code: es_ES - currency: EUR - store: 16 - - code: fr_CH - currency: CHF - store: 16 - - code: fr_FR - currency: EUR - store: 18 - - code: it_CH - currency: CHF - store: 17 - - code: it_IT - currency: EUR - store: 17 - - scopes: - - code: recommerce_eu - store: 18 - locales: - - code: fr_FR - store: 18 - - code: de_DE - store: 21 -# - code: es_ES -# store: 1 - - code: recommerce_ch - store: 16 - locales: - - code: fr_CH - store: 16 - - code: de_CH - store: 15 - - code: it_CH - store: 17 - - code: bmo_digitech - store: 16 - locales: - - code: fr_CH - store: 16 - - code: de_CH - store: 15 - - code: it_CH - store: 17 - - code: en_GB - store: 16 - - code: bmo_bouygues - store: 3 - locales: - - code: fr_FR - store: 3 - - code: bmo_mbudget - store: 3 - locales: - - code: fr_CH - store: 3 - - code: de_CH - store: 4 - - code: it_CH - store: 5 - - code: bmo_ricardo - store: 7 - locales: - - code: fr_CH - store: 7 - - code: de_CH - store: 6 - - code: amazon - store: 8 - locales: - - code: fr_FR - store: 8 - - code: advise - store: 22 - locales: - - code: fr_CH - store: 22 - - codes-mapping: - - from: '"' - to: 'inches' - - from: 'â' - to: 'a' - - from: 'é' - to: 'e' - - from: 'è' - to: 'e' - - from: '/' - to: '_' - - from: '.' - to: '_' - - from: ' ' - to: '_' - - from: ',' - to: '_' - - from: '(' - to: '' - - from: ')' - to: '' - - from: '+' - to: '_plus' - - from: '_x_' - to: 'x' \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php b/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php index e4648a5..65c1091 100644 --- a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php +++ b/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php @@ -4,6 +4,7 @@ use App\Domain\Fixture\SqlToCsv; use App\Domain\Magento\AttributeRenderer; +use App\Domain\Magento\FamilyVariant; use App\Infrastructure\Command\CommandBus; use App\Infrastructure\Command\TwigCommand; use Psr\Log\LoggerAwareInterface; @@ -34,8 +35,9 @@ public function __construct(\PDO $pdo, Environment $twig, ?LoggerInterface $logg /** * @param AttributeRenderer[] $attributes + * @param FamilyVariant[] $familyVariants */ - public function __invoke(\SplFileObject $output, array $attributes, array $locales, array $families): void + public function __invoke(\SplFileObject $output, array $attributes, array $familyVariants): void { $bus = new CommandBus(); try { @@ -54,17 +56,15 @@ public function __invoke(\SplFileObject $output, array $attributes, array $local 'attributes' => $attributes, ], $this->logger -// ), -// new TwigCommand( -// $this->twig, -// 'product/02-prefill-axis-attributes.sql.twig', -// [ -// 'mapping' => [], -// 'axises' => array_merge([], ...array_map(function(AttributeRenderer $renderer) { -// return $renderer->fields(); -// }, $axises)), -// ], -// $this->logger + ), + new TwigCommand( + $this->twig, + 'product/02-prefill-axis-attributes.sql.twig', + [ + 'mapping' => [], + 'variants' => $familyVariants, + ], + $this->logger ) ); @@ -91,15 +91,26 @@ public function __invoke(\SplFileObject $output, array $attributes, array $local ); } + $bus->add( + new TwigCommand( + $this->twig, + 'product/03-generate-intermediate-product-models.sql.twig', + [ + 'variants' => $familyVariants, + ], + $this->logger + ) + ); + $bus($this->pdo); $view = $this->twig->load('extract-products.sql.twig'); - (new SqlToCsv($this->pdo)) + (new SqlToCsv($this->pdo, $this->logger)) ( $view->render([ 'attributes' => $attributes, - 'locales' => $locales, + 'variants' => $familyVariants, ]), $output ); diff --git a/src/App/Domain/Fixture/SqlToCsv.php b/src/App/Domain/Fixture/SqlToCsv.php index 4163346..96845c2 100644 --- a/src/App/Domain/Fixture/SqlToCsv.php +++ b/src/App/Domain/Fixture/SqlToCsv.php @@ -2,18 +2,26 @@ namespace App\Domain\Fixture; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + class SqlToCsv { + use LoggerAwareTrait; + /** @var \PDO */ private $connection; - public function __construct(\PDO $connection) + public function __construct(\PDO $connection, ?LoggerInterface $logger = null) { $this->connection = $connection; + $this->logger = $logger ?? new NullLogger(); } public function __invoke(string $sql, \SplFileObject $output): self { + $this->logger->debug($sql); $statement = $this->connection->query($sql); $columnCount = $statement->columnCount(); diff --git a/src/App/Domain/Magento/CodeGenerator/Globalised.php b/src/App/Domain/Magento/CodeGenerator/Globalised.php index 559a939..22bf276 100644 --- a/src/App/Domain/Magento/CodeGenerator/Globalised.php +++ b/src/App/Domain/Magento/CodeGenerator/Globalised.php @@ -33,7 +33,12 @@ public function table(): string public function default(): string { - return 'attr_default'; + return strtr( + 'attr_default_{{ attribute }}', + [ + '{{ attribute }}' => $this->attribute->code, + ] + ); } public function alias(): string diff --git a/src/App/Domain/Magento/CodeGenerator/Localized.php b/src/App/Domain/Magento/CodeGenerator/Localized.php index 2e80d89..387e379 100644 --- a/src/App/Domain/Magento/CodeGenerator/Localized.php +++ b/src/App/Domain/Magento/CodeGenerator/Localized.php @@ -39,15 +39,21 @@ public function table(): string public function default(): string { - return 'attr_default'; + return strtr( + 'attr_default_{{ attribute }}', + [ + '{{ attribute }}' => $this->attribute->code, + ] + ); } public function alias(): string { return strtr( - 'attr_{{ locale }}', + 'attr_{{ attribute }}_{{ locale }}', [ '{{ locale }}' => $this->locale->code(), + '{{ attribute }}' => $this->attribute->code, ] ); } diff --git a/src/App/Domain/Magento/CodeGenerator/Scoped.php b/src/App/Domain/Magento/CodeGenerator/Scoped.php index b6ba247..17e89e3 100644 --- a/src/App/Domain/Magento/CodeGenerator/Scoped.php +++ b/src/App/Domain/Magento/CodeGenerator/Scoped.php @@ -39,15 +39,21 @@ public function table(): string public function default(): string { - return 'attr_default'; + return strtr( + 'attr_default_{{ attribute }}', + [ + '{{ attribute }}' => $this->attribute->code, + ] + ); } public function alias(): string { return strtr( - 'attr_{{ scope }}', + 'attr_{{ attribute }}_{{ scope }}', [ '{{ scope }}' => $this->scope->code(), + '{{ attribute }}' => $this->attribute->code, ] ); } diff --git a/src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php b/src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php index 202f0a9..7929764 100644 --- a/src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php +++ b/src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php @@ -45,16 +45,22 @@ public function table(): string public function default(): string { - return 'attr_default'; + return strtr( + 'attr_default_{{ attribute }}', + [ + '{{ attribute }}' => $this->attribute->code, + ] + ); } public function alias(): string { return strtr( - 'attr_{{ scope }}_{{ locale }}', + 'attr_{{ attribute }}_{{ scope }}_{{ locale }}', [ '{{ scope }}' => $this->scope->code(), '{{ locale }}' => $this->locale->code(), + '{{ attribute }}' => $this->attribute->code, ] ); } diff --git a/src/App/Domain/Magento/FamilyVariant.php b/src/App/Domain/Magento/FamilyVariant.php new file mode 100644 index 0000000..db53055 --- /dev/null +++ b/src/App/Domain/Magento/FamilyVariant.php @@ -0,0 +1,40 @@ + 2) { + throw new \OverflowException('Could not initialise a family variant with more than 2 axis levels.'); + } + + $this->skuTemplate = $skuTemplate; + $this->axises = $axises; + } + + public function axis(int $level): FamilyVariantAxis + { + if ($level > count($this->axises)) { + throw new \OutOfBoundsException('You requested an axis that does not exist.'); + } + + return $this->axises[$level - 1]; + } + + public function all(): \Iterator + { + yield from $this->axises; + } + + public function isTwoLevels(): bool + { + return count($this->axises) === 2; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/FamilyVariantAxis.php b/src/App/Domain/Magento/FamilyVariantAxis.php new file mode 100644 index 0000000..74f0e3d --- /dev/null +++ b/src/App/Domain/Magento/FamilyVariantAxis.php @@ -0,0 +1,14 @@ +attributes = $renderers; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/TwigExtension.php b/src/App/Domain/Magento/SqlExportTwigExtension.php similarity index 54% rename from src/App/Domain/Magento/TwigExtension.php rename to src/App/Domain/Magento/SqlExportTwigExtension.php index 3d41ec7..5b161ea 100644 --- a/src/App/Domain/Magento/TwigExtension.php +++ b/src/App/Domain/Magento/SqlExportTwigExtension.php @@ -5,9 +5,9 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; -class TwigExtension extends AbstractExtension +class SqlExportTwigExtension extends AbstractExtension { - public function getfunctions() + public function getFunctions() { return [ new TwigFunction('as_field_alias', function(Field $field) { @@ -24,6 +24,22 @@ public function getfunctions() return 'attr_default'; }), + new TwigFunction('as_attribute_axis_table', function(Attribute $attribute) { + return strtr( + 'tmp_axis_{{ attribute }}', + [ + '{{ attribute }}' => $attribute->code(), + ] + ); + }), + new TwigFunction('as_attribute_alias', function(Attribute $attribute) { + return strtr( + 'attr_default_{{ attribute }}', + [ + '{{ attribute }}' => $attribute->code(), + ] + ); + }), new TwigFunction('as_attribute_table', function(Attribute $attribute) { return strtr( 'tmp_{{ attribute }}', @@ -68,6 +84,39 @@ public function getfunctions() ] ); }), + + new TwigFunction('as_product_hierarchy_sku_field', function(string $skuField, FamilyVariant $family) { + $replacements = [ + '%parent%' => $skuField, + ]; + + foreach ($family->axis(1)->attributes as $attribute) { + /** @var Field $field */ + foreach ($attribute->fields() as $field) { + $replacements['%' . $attribute->attribute()->code() . '%'] = strtr( + '{{ alias }}.{{ field }}', + [ + '{{ alias }}' => $field->codeGenerator->alias(), + '{{ field }}' => $field->codeGenerator->column(), + ] + ); + } + } + + $pattern = 'CONCAT("' . preg_replace_callback('/{{\s*([a-z_]+)\s*}}/', function($matches) { + return '", %' . $matches[1] . '%, "'; + }, $family->skuTemplate) . '")'; + + return strtr($pattern, $replacements); + }), + new TwigFunction('as_product_hierarchy_axis_alias', function(Attribute $attribute) { + return strtr( + 'attr_{{ attribute }}', + [ + '{{ attribute }}' => $attribute->code(), + ] + ); + }), ]; } } \ No newline at end of file diff --git a/src/App/Infrastructure/AttributeRendererFactory.php b/src/App/Infrastructure/AttributeRendererFactory.php index 7f20e3f..68198e8 100644 --- a/src/App/Infrastructure/AttributeRendererFactory.php +++ b/src/App/Infrastructure/AttributeRendererFactory.php @@ -5,30 +5,46 @@ use App\Domain\Magento\Attribute; use App\Domain\Magento\AttributeRenderer; use App\Domain\Magento\FieldResolver; +use App\Domain\Magento\Locale; +use App\Domain\Magento\Scope; class AttributeRendererFactory { /** * @param Attribute[] $attributes * @param Attribute[] $axisAttributes + * @param Scope[] $scopes + * @param Locale[] $locales * @param array $config * * @return AttributeRenderer[] */ - public function __invoke(iterable $attributes, iterable $axisAttributes, array $config): array - { - return iterator_to_array($this->walk($attributes, $axisAttributes, $config)); + public function __invoke( + iterable $attributes, + iterable $axisAttributes, + iterable $scopes, + iterable $locales, + array $config + ): array { + return iterator_to_array($this->walk($attributes, $axisAttributes, $scopes, $locales, $config)); } /** * @param Attribute[] $attributes * @param Attribute[] $axisAttributes + * @param Scope[] $scopes + * @param Locale[] $locales * @param array $config * * @return AttributeRenderer[]|\Iterator */ - public function walk(iterable $attributes, iterable $axisAttributes, array $config): \Iterator - { + public function walk( + iterable $attributes, + iterable $axisAttributes, + iterable $scopes, + iterable $locales, + array $config + ): \Iterator { foreach ($attributes as $attribute) { if (!isset($config[$attribute->code()])) { continue; @@ -43,7 +59,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( false, $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -54,7 +72,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( in_array($attribute, $axisAttributes), $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -65,7 +85,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( false, $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -76,7 +98,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( false, $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -87,7 +111,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( false, $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -98,7 +124,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( false, $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -109,7 +137,9 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf $this->buildFieldResolver( false, $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false + $attributeSpec['localised'] ?? false, + $scopes, + $locales ) ); break; @@ -117,19 +147,24 @@ public function walk(iterable $attributes, iterable $axisAttributes, array $conf } } - private function buildFieldResolver(bool $isAxis, bool $scoped, bool $localised): FieldResolver - { + private function buildFieldResolver( + bool $isAxis, + bool $scoped, + bool $localised, + iterable $scopes, + iterable $locales + ): FieldResolver { if ($isAxis === true) { return new FieldResolver\VariantAxis(); } if ($localised === true && $scoped === true) { - return new FieldResolver\ScopedAndLocalized(); + return new FieldResolver\ScopedAndLocalized(...$scopes); } if ($localised !== true && $scoped === true) { - return new FieldResolver\Scoped(); + return new FieldResolver\Scoped(...$scopes); } if ($localised === true && $scoped !== true) { - return new FieldResolver\Localized(); + return new FieldResolver\Localized(...$locales); } return new FieldResolver\Globalised(); diff --git a/src/App/Infrastructure/Configuration/Configuration.php b/src/App/Infrastructure/Configuration/Configuration.php index a61c3f5..311f10b 100644 --- a/src/App/Infrastructure/Configuration/Configuration.php +++ b/src/App/Infrastructure/Configuration/Configuration.php @@ -75,6 +75,7 @@ public function getConfigTreeBuilder() ->arrayNode('variations') ->arrayPrototype() ->children() + ->scalarNode('skuPattern')->end() ->scalarNode('code')->isRequired()->end() ->arrayNode('level_1') ->children() diff --git a/src/App/Infrastructure/Console/Command/GoCommand.php b/src/App/Infrastructure/Console/Command/GoCommand.php index 11a3c89..67a7d93 100644 --- a/src/App/Infrastructure/Console/Command/GoCommand.php +++ b/src/App/Infrastructure/Console/Command/GoCommand.php @@ -4,9 +4,10 @@ use App\Domain\Fixture; use App\Domain\Magento\Attribute; +use App\Domain\Magento\AttributeRenderer; use App\Domain\Magento\Locale; use App\Domain\Magento\Scope; -use App\Domain\Magento\TwigExtension; +use App\Domain\Magento\SqlExportTwigExtension; use App\Infrastructure\AttributeRendererFactory; use App\Infrastructure\Configuration\YamlFileLoader; use App\Infrastructure\Normalizer\Magento\AttributeDenormalizerFactory; @@ -137,7 +138,7 @@ protected function execute(InputInterface $input, OutputInterface $output) ); $twig->addExtension(new DebugExtension()); - $twig->addExtension(new TwigExtension()); + $twig->addExtension(new SqlExportTwigExtension()); (new Fixture\Command\ExtractLocales())( new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/locales.yml', 'w'), @@ -193,19 +194,40 @@ protected function execute(InputInterface $input, OutputInterface $output) $scopes = (new ScopeDenormalizerFactory())() ->denormalize($config['scopes'], Scope::class.'[]'); $locales = (new LocaleDenormalizerFactory())() - ->denormalize($config['locales'], Locale::class.'[]'); + ->denormalize($config['scopes'], Locale::class.'[]'); + + $axisAttributes = array_filter($attributes, function (Attribute $renderer) use ($config) { + $code = $renderer->code(); + foreach ($config['families'] as $family) { + foreach ($family['variations'] as $variation) { + if (in_array($code, $variation['level_1']['axis']) || + (isset($variation['level_2']['axis']) && in_array($code, $variation['level_2']['axis'])) + ) { + return true; + } + } + } - /** @var Attribute[] $axises */ - $axises = (new VariantAxisesFactory($attributes))($config); + return false; + }); $attributeRenderersFactory = new AttributeRendererFactory(); - $attributeRenderers = $attributeRenderersFactory($attributes, $axises, $config['attributes']); + /** @var AttributeRenderer[] $attributeRenderers */ + $attributeRenderers = $attributeRenderersFactory( + $attributes, + $axisAttributes, + $scopes, + $locales, + $config['attributes'] + ); + + /** @var Attribute[] $axises */ + $axises = (new VariantAxisesFactory(...$attributeRenderers))($config); (new Fixture\Command\ExtractSimpleProducts($pdo, $twig, $this->logger))( new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/products.csv', 'w'), $attributeRenderers, - array_keys($config['locales']), - $config['families'] + $axises ); $style->writeln('products ok', SymfonyStyle::OUTPUT_PLAIN); diff --git a/src/App/Infrastructure/VariantAxisesFactory.php b/src/App/Infrastructure/VariantAxisesFactory.php index 68771ed..cb45112 100644 --- a/src/App/Infrastructure/VariantAxisesFactory.php +++ b/src/App/Infrastructure/VariantAxisesFactory.php @@ -3,47 +3,64 @@ namespace App\Infrastructure; use App\Domain\Magento\Attribute; +use App\Domain\Magento\AttributeRenderer; +use App\Domain\Magento\FamilyVariant; +use App\Domain\Magento\FamilyVariantAxis; class VariantAxisesFactory { - /** @var Attribute[] */ - private $attributes; + /** @var AttributeRenderer[] */ + private $renderers; - public function __construct(array $attributes) + public function __construct(AttributeRenderer ...$renderers) { - $this->attributes = $attributes; + $this->renderers = $renderers; } public function __invoke(array $config): array { - $codes = []; - foreach (array_unique(iterator_to_array($this->walk($config))) as $code) { - array_push($codes, $this->findAttribute($code)); - } - - return $codes; + return iterator_to_array($this->walk($config)); } private function walk(array $config): \Iterator { foreach ($config['families'] as $family) { foreach ($family['variations'] as $variation) { - yield from $variation['level_1']['axis']; - if (isset($variation['level_2']['axis'])) { - yield from $variation['level_2']['axis']; + yield new FamilyVariant( + $variation['skuPattern'], + new FamilyVariantAxis(...$this->extractAttributes($variation['level_1']['axis'])), + new FamilyVariantAxis(...$this->extractAttributes($variation['level_2']['axis'])) + ); + } else { + yield new FamilyVariant( + null, + new FamilyVariantAxis(...$this->extractAttributes($variation['level_1']['axis'])) + ); } } } } - private function findAttribute(string $code): Attribute + /** + * @param string[] $axises + * + * @return \Iterator|AttributeRenderer[] + */ + private function extractAttributes(array $axises): \Iterator + { + foreach ($axises as $code) { + yield $this->findAttributeRenderer($code); + } + } + + private function findAttributeRenderer(string $code): AttributeRenderer { - $attributes = array_filter($this->attributes, function (Attribute $attribute) use ($code) { - return $attribute->code() === $code; + $renderers = array_filter($this->renderers, function (AttributeRenderer $renderer) use ($code) { + return $renderer->attribute()->code() === $code; }); - if (count($attributes) > 1) { + if (count($renderers) > 1) { throw new AttributeNotFoundException(strtr( 'Found several attributes configuration with code "%code%".', [ @@ -52,7 +69,7 @@ private function findAttribute(string $code): Attribute )); } - if (count($attributes) < 1) { + if (count($renderers) < 1) { throw new AttributeNotFoundException(strtr( 'Attribute with code "%code%" was not found in configuration.', [ @@ -61,6 +78,6 @@ private function findAttribute(string $code): Attribute )); } - return array_pop($attributes); + return array_pop($renderers); } } \ No newline at end of file diff --git a/src/App/Resources/templates/extract-products.sql.twig b/src/App/Resources/templates/extract-products.sql.twig new file mode 100644 index 0000000..a43ecbc --- /dev/null +++ b/src/App/Resources/templates/extract-products.sql.twig @@ -0,0 +1,16 @@ +SELECT +{% for attribute in attributes %} + {% for field in attribute.fields %} + {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, + {% endfor %} +{% endfor %} + product.sku AS sku, + COALESCE(hierarchy.child, hierarchy.parent) AS product_model +FROM tmp_sku AS product +{% for attribute in attributes %} +INNER JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias(attribute.attribute) }} + ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id +{% endfor %} +LEFT JOIN tmp_hierarchy as hierarchy + ON hierarchy.variant=product.sku +WHERE product.type_id IN ('simple', 'virtual') diff --git a/src/App/Resources/templates/initialize.sql.twig b/src/App/Resources/templates/initialize.sql.twig index 37579e6..33705e8 100644 --- a/src/App/Resources/templates/initialize.sql.twig +++ b/src/App/Resources/templates/initialize.sql.twig @@ -1,32 +1,30 @@ - - -{#CREATE TEMPORARY TABLE tmp_hierarchy (#} -{# parent VARCHAR(128) NOT NULL,#} -{# child VARCHAR(128) NOT NULL,#} -{# variant VARCHAR(128) NOT NULL,#} -{# color VARCHAR(128) NULL,#} -{# capacity VARCHAR(128) NULL,#} -{# PRIMARY KEY (parent, child, variant),#} -{# INDEX (parent),#} -{# INDEX (child),#} -{# INDEX (variant)#} -{#)#} -{#SELECT#} -{# product.sku AS parent,#} -{# CONCAT(product.sku{%- for axis in axises -%}, ':', {{ as_field_alias(axis) }}.short_code{%- endfor -%}) AS child,#} -{#{%- for axis in axises -%}#} -{# {{ as_field_alias(axis) }}.code AS `{{ attribute.codeInDestination }}`,#} -{#{% endfor -%}#} -{# child.sku AS variant#} -{#FROM catalog_product_entity AS product#} -{#INNER JOIN catalog_product_super_link AS link#} -{# ON link.parent_id=product.entity_id#} -{#INNER JOIN catalog_product_entity AS child#} -{# ON child.entity_id=link.product_id#} -{#{% for axis in axises -%}#} -{#INNER JOIN {{ as_attribute_table(axis) }} AS {{ as_field_alias(axis) }}#} -{# ON {{ as_field_alias(axis) }}.entity_id = child.entity_id#} -{#{% endfor -%}#} -{#WHERE product.sku IS NOT NULL#} -{# AND child.sku IS NOT NULL#} -{# AND product.type_id IN ('configurable');#} \ No newline at end of file +CREATE TEMPORARY TABLE tmp_hierarchy ( + parent VARCHAR(128) NOT NULL, + child VARCHAR(128) NOT NULL, + variant VARCHAR(128) NOT NULL, + color VARCHAR(128) NULL, + capacity VARCHAR(128) NULL, + PRIMARY KEY (parent, child, variant), + INDEX (parent), + INDEX (child), + INDEX (variant) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + product.sku AS parent, + CONCAT(product.sku{%- for axis in axises -%}, ':', {{ as_field_alias(axis) }}.short_code{%- endfor -%}) AS child, +{%- for axis in axises -%} + {{ as_field_alias(axis) }}.code AS `{{ attribute.alias }}`, +{% endfor -%} + child.sku AS variant +FROM catalog_product_entity AS product +INNER JOIN catalog_product_super_link AS link + ON link.parent_id=product.entity_id +INNER JOIN catalog_product_entity AS child + ON child.entity_id=link.product_id +{% for axis in axises -%} +INNER JOIN {{ as_attribute_table(axis) }} AS {{ as_field_alias(axis) }} + ON {{ as_field_alias(axis) }}.entity_id = child.entity_id +{% endfor -%} +WHERE product.sku IS NOT NULL + AND child.sku IS NOT NULL + AND product.type_id IN ('configurable'); \ No newline at end of file diff --git a/src/App/Resources/templates/product/01-attribute-options.sql.twig b/src/App/Resources/templates/product/01-attribute-options.sql.twig index 7f4541f..75eb8a0 100644 --- a/src/App/Resources/templates/product/01-attribute-options.sql.twig +++ b/src/App/Resources/templates/product/01-attribute-options.sql.twig @@ -26,9 +26,9 @@ SELECT FROM ( {%- set renderedAttributes = [] -%} -{%- for code in attributes|keys -%} +{%- for attribute in attributes -%} {% set renderedAttribute %} - SELECT '{{ code }}' AS code + SELECT '{{ attribute.attribute.code }}' AS code {% endset %} {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} diff --git a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig index 915a15d..a627126 100644 --- a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig +++ b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig @@ -1,5 +1,7 @@ -{% for field in axises %} -CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( +{% for variant in variants %} + {% for axis in variant.all %} + {% for attribute in axis.attributes %} +CREATE TEMPORARY TABLE {{ as_attribute_axis_table(attribute.attribute) }} ( entity_id INTEGER NOT NULL, code VARCHAR(256) NOT NULL, short_code VARCHAR(256) NOT NULL, @@ -16,8 +18,10 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} INNER JOIN tmp_options AS {{ as_default_alias() }}_option ON {{ as_default_alias() }}_option.attribute = attribute.attribute_code AND {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id -WHERE attribute.attribute_code = '{{ attribute.codeInSource }}' +WHERE attribute.attribute_code = '{{ attribute.alias }}' AND attribute.entity_type_id = 4 AND attribute.attribute_id={{ as_default_alias() }}.attribute_id AND {{ as_default_alias() }}.store_id = 0; + {% endfor %} + {% endfor %} {% endfor %} \ No newline at end of file diff --git a/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig new file mode 100644 index 0000000..56c3797 --- /dev/null +++ b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig @@ -0,0 +1,48 @@ +CREATE TEMPORARY TABLE tmp_hierarchy ( + parent VARCHAR(128) NOT NULL, + child VARCHAR(128) NOT NULL, + variant VARCHAR(128) NOT NULL, + PRIMARY KEY (parent, child, variant), + INDEX (parent), + INDEX (child), + INDEX (variant) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci + +{% set variantRequests = [] %} +{% for variant in variants %} + {% if variant.isTwoLevels == true %} + {% set request %} +SELECT + product.sku AS parent, + {{ as_product_hierarchy_sku_field('product.sku', variant) }} AS child, + child.sku AS variant +FROM catalog_product_entity AS product +INNER JOIN catalog_product_super_link AS link + ON link.parent_id=product.entity_id +INNER JOIN catalog_product_entity AS child + ON child.entity_id=link.product_id +{% for attribute in variant.axis(1).attributes %} +INNER JOIN {{ as_attribute_table(attribute.attribute) }} AS {{ as_attribute_alias(attribute.attribute) }} + ON {{ as_attribute_alias(attribute.attribute) }}.entity_id = child.entity_id +{% endfor %} +WHERE product.sku IS NOT NULL + AND child.sku IS NOT NULL + AND product.type_id IN ('configurable') +{% for attribute in variant.axis(1).attributes -%} + {% for field in attribute.fields() %} + AND {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}` IS NOT NULL + {% endfor -%} +{% endfor -%} + {% endset %} + + {% set variantRequests = variantRequests|merge([request]) %} + + {% endif %} +{% endfor %} + +{% if variantRequests|length > 0 %} +SELECT * +FROM ( +{{ variantRequests|join('UNION\n') }} +) AS subrequests; +{% endif %} \ No newline at end of file diff --git a/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig index e1d3572..899e7a2 100644 --- a/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig @@ -2,7 +2,7 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {%- for field in fields -%} + {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` VARCHAR(255) NULL, {% endfor -%} PRIMARY KEY (sku), @@ -22,7 +22,7 @@ LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} AND {{ as_default_alias() }}.store_id=0 AND {{ as_default_alias() }}.value!='no_selection' {% for field in renderer.fields -%} -LEFT JOIN catalog_product_entity_datetime AS {{ as_field_alias(field) }} +LEFT JOIN catalog_product_entity_varchar AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} diff --git a/src/App/Resources/templates/product/attribute/extract-image.sql.twig b/src/App/Resources/templates/product/attribute/extract-image.sql.twig index 0e7caa1..68c3fee 100644 --- a/src/App/Resources/templates/product/attribute/extract-image.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image.sql.twig @@ -2,7 +2,7 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - {%- for field in fields -%} + {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` VARCHAR(255) NULL, {% endfor -%} PRIMARY KEY (sku), @@ -10,7 +10,7 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, + {{ as_default_alias() }}.value AS `{{ as_field_alias(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig index 28045e2..f6ab3ba 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig @@ -60,14 +60,14 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {% for field in renderer.fields %} - `{{ field }}` VARCHAR(255) NOT NULL, + `{{ as_field_alias(field) }}` VARCHAR(255) NOT NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - {{ as_field_alias(field) }}.value AS `{{ as_field_column(field) }}`, + {{ as_field_table(field) }}.value AS `{{ as_field_alias(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig index 32c59f1..74b719b 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig @@ -2,13 +2,17 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, - {{ as_default_alias() }}.value + {% for field in renderer.fields -%} + {{ as_default_alias() }}.value AS `{{ as_field_alias(field) }}`, + {% endfor -%} + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 From b62637e0f21e78eecb6fb295aa963225d628550a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Fri, 18 Oct 2019 10:50:49 +0200 Subject: [PATCH 4/7] Added product model export --- ...SimpleProducts.php => ExtractProducts.php} | 67 ++++++++++++----- .../Magento/AttributeRenderer/Datetime.php | 2 +- .../Magento/AttributeRenderer/Identifier.php | 63 ++++++++++++++++ .../Magento/AttributeRenderer/Image.php | 2 +- .../{Varchar.php => Metric.php} | 6 +- .../Magento/AttributeRenderer/RichText.php | 67 +++++++++++++++++ .../Magento/AttributeRenderer/Status.php | 2 +- .../Domain/Magento/AttributeRenderer/Text.php | 2 +- .../Magento/AttributeRenderer/TextArea.php | 67 +++++++++++++++++ .../Magento/AttributeRenderer/Visibility.php | 2 +- src/App/Domain/Magento/Family.php | 17 +++++ src/App/Domain/Magento/FamilyVariant.php | 5 +- src/App/Domain/Magento/Renderer.php | 8 ++- .../Domain/Magento/SqlExportTwigExtension.php | 5 +- .../Infrastructure/AttributeAggregator.php | 20 ++++++ .../AttributeRendererFactory.php | 60 ++++++++++++++-- .../Infrastructure/Command/TwigCommand.php | 14 ++-- .../Configuration/Configuration.php | 2 +- .../Console/Command/GoCommand.php | 15 +++- src/App/Infrastructure/FamiliesFactory.php | 72 +++++++++++++++++++ .../FamilyVariantAxisAttributeAggregator.php | 23 ++++++ .../Infrastructure/VariantAxisesFactory.php | 2 + .../templates/extract-product-models.sql.twig | 3 + .../templates/extract-products.sql.twig | 6 +- .../Resources/templates/initialize.sql.twig | 2 +- .../templates/product/00-sku.sql.twig | 2 +- .../product/01-attribute-options.sql.twig | 4 +- .../02-prefill-axis-attributes.sql.twig | 2 +- ...erate-intermediate-product-models.sql.twig | 2 +- .../04-consolidate-product-models.sql.twig | 45 ++++++++++++ ...act-datetime-scopable-localizable.sql.twig | 2 +- ...xtract-image-scopable-localizable.sql.twig | 2 +- .../product/attribute/extract-image.sql.twig | 2 +- ...ract-metric-scopable-localizable.sql.twig} | 9 ++- ...ct-rich-text-scopable-localizable.sql.twig | 28 ++++++++ ...xt.sql.twig => extract-rich-text.sql.twig} | 2 +- ...simpleselect-scopable-localizable.sql.twig | 22 +++--- .../extract-simpleselect-scopable.sql.twig | 22 +++--- .../attribute/extract-simpleselect.sql.twig | 4 +- ...ract-status-scopable-localizable.sql.twig} | 31 ++++---- ...ct-text-area-scopable-localizable.sql.twig | 28 ++++++++ .../attribute/extract-text-area.sql.twig | 23 ++++++ ...extract-text-scopable-localizable.sql.twig | 9 +-- ...-visibility-scopable-localizable.sql.twig} | 31 ++++---- 44 files changed, 689 insertions(+), 115 deletions(-) rename src/App/Domain/Fixture/Command/{ExtractSimpleProducts.php => ExtractProducts.php} (63%) create mode 100644 src/App/Domain/Magento/AttributeRenderer/Identifier.php rename src/App/Domain/Magento/AttributeRenderer/{Varchar.php => Metric.php} (92%) create mode 100644 src/App/Domain/Magento/AttributeRenderer/RichText.php create mode 100644 src/App/Domain/Magento/AttributeRenderer/TextArea.php create mode 100644 src/App/Domain/Magento/Family.php create mode 100644 src/App/Infrastructure/AttributeAggregator.php create mode 100644 src/App/Infrastructure/FamiliesFactory.php create mode 100644 src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php create mode 100644 src/App/Resources/templates/extract-product-models.sql.twig create mode 100644 src/App/Resources/templates/product/04-consolidate-product-models.sql.twig rename src/App/Resources/templates/product/attribute/{extract-varchar-scopable-localizable.sql.twig => extract-metric-scopable-localizable.sql.twig} (80%) create mode 100644 src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig rename src/App/Resources/templates/product/attribute/{extract-text.sql.twig => extract-rich-text.sql.twig} (91%) rename src/App/Resources/templates/product/attribute/{extract-status.sql.twig => extract-status-scopable-localizable.sql.twig} (70%) create mode 100644 src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig create mode 100644 src/App/Resources/templates/product/attribute/extract-text-area.sql.twig rename src/App/Resources/templates/product/attribute/{extract-visibility.sql.twig => extract-visibility-scopable-localizable.sql.twig} (73%) diff --git a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php b/src/App/Domain/Fixture/Command/ExtractProducts.php similarity index 63% rename from src/App/Domain/Fixture/Command/ExtractSimpleProducts.php rename to src/App/Domain/Fixture/Command/ExtractProducts.php index 65c1091..afc6ec9 100644 --- a/src/App/Domain/Fixture/Command/ExtractSimpleProducts.php +++ b/src/App/Domain/Fixture/Command/ExtractProducts.php @@ -4,7 +4,10 @@ use App\Domain\Fixture\SqlToCsv; use App\Domain\Magento\AttributeRenderer; +use App\Domain\Magento\Family; use App\Domain\Magento\FamilyVariant; +use App\Infrastructure\AttributeAggregator; +use App\Infrastructure\FamilyVariantAxisAttributeAggregator; use App\Infrastructure\Command\CommandBus; use App\Infrastructure\Command\TwigCommand; use Psr\Log\LoggerAwareInterface; @@ -17,7 +20,7 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; -class ExtractSimpleProducts implements LoggerAwareInterface +class ExtractProducts implements LoggerAwareInterface { use LoggerAwareTrait; @@ -35,10 +38,16 @@ public function __construct(\PDO $pdo, Environment $twig, ?LoggerInterface $logg /** * @param AttributeRenderer[] $attributes + * @param Family[] $families * @param FamilyVariant[] $familyVariants */ - public function __invoke(\SplFileObject $output, array $attributes, array $familyVariants): void - { + public function __invoke( + \SplFileObject $productModelOutput, + \SplFileObject $productOutput, + array $attributes, + array $families, + array $familyVariants + ): void { $bus = new CommandBus(); try { $bus->add( @@ -53,7 +62,7 @@ public function __invoke(\SplFileObject $output, array $attributes, array $famil 'product/01-attribute-options.sql.twig', [ 'mapping' => [], - 'attributes' => $attributes, + 'attributes' => (new AttributeAggregator())(...$families), ], $this->logger ), @@ -79,16 +88,20 @@ public function __invoke(\SplFileObject $output, array $attributes, array $famil )); } - $bus->add( - new TwigCommand( - $this->twig, - $attribute->template(), - [ - 'renderer' => $attribute, - ], - $this->logger - ) - ); + try { + $bus->add( + new TwigCommand( + $this->twig, + $attribute->template(), + [ + 'renderer' => $attribute, + ], + $this->logger + ) + ); + } catch (\RuntimeException $e) { + continue; + } } $bus->add( @@ -102,6 +115,17 @@ public function __invoke(\SplFileObject $output, array $attributes, array $famil ) ); + $bus->add( + new TwigCommand( + $this->twig, + 'product/04-consolidate-product-models.sql.twig', + [ + 'attributes' => (new FamilyVariantAxisAttributeAggregator())(...$familyVariants), + ], + $this->logger + ) + ); + $bus($this->pdo); $view = $this->twig->load('extract-products.sql.twig'); @@ -109,10 +133,21 @@ public function __invoke(\SplFileObject $output, array $attributes, array $famil (new SqlToCsv($this->pdo, $this->logger)) ( $view->render([ - 'attributes' => $attributes, + 'attributes' => (new AttributeAggregator())(...$families), + 'variants' => $familyVariants, + ]), + $productOutput + ); + + $view = $this->twig->load('extract-product-models.sql.twig'); + + (new SqlToCsv($this->pdo, $this->logger)) + ( + $view->render([ + 'attributes' => (new AttributeAggregator())(...$families), 'variants' => $familyVariants, ]), - $output + $productModelOutput ); } catch (\RuntimeException|LoaderError|RuntimeError|SyntaxError|FatalThrowableError $e) { throw new \RuntimeException('An error occurred during the product data extraction.', null, $e); diff --git a/src/App/Domain/Magento/AttributeRenderer/Datetime.php b/src/App/Domain/Magento/AttributeRenderer/Datetime.php index 8c8ea26..f303c7c 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Datetime.php +++ b/src/App/Domain/Magento/AttributeRenderer/Datetime.php @@ -22,7 +22,7 @@ public function __construct( $this->attribute = $attribute; if ($fieldResolver instanceof VariantAxis) { - throw new \TypeError('Could not accept a VariantAxis renderer in a Datetime attrinute.'); + throw new \TypeError('Could not accept a VariantAxis renderer in a Datetime attribute.'); } $this->fieldResolver = $fieldResolver; diff --git a/src/App/Domain/Magento/AttributeRenderer/Identifier.php b/src/App/Domain/Magento/AttributeRenderer/Identifier.php new file mode 100644 index 0000000..3d70b3c --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/Identifier.php @@ -0,0 +1,63 @@ +attribute = $attribute; + + if (!$attribute instanceof Attribute\AdHoc) { + throw new \TypeError('The identifier attribute must be be ad-hoc.'); + } + if ($fieldResolver instanceof VariantAxis) { + throw new \TypeError('Could not accept a VariantAxis renderer in an Identifier attribute.'); + } + + $this->fieldResolver = $fieldResolver; + } + + public function __toString() + { + return 'identifier'; + } + + public function __invoke(TemplateWrapper $template): string + { + return ''; + } + + public function template(): string + { + throw new \RuntimeException('This renderer has no template.'); + } + + public function attribute(): Attribute + { + return $this->attribute; + } + + public function fields(): iterable + { + return $this->fieldResolver->fields($this->attribute); + } + + public function isAxis(): bool + { + return false; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/AttributeRenderer/Image.php b/src/App/Domain/Magento/AttributeRenderer/Image.php index 163b3c8..d7b513a 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Image.php +++ b/src/App/Domain/Magento/AttributeRenderer/Image.php @@ -22,7 +22,7 @@ public function __construct( $this->attribute = $attribute; if ($fieldResolver instanceof VariantAxis) { - throw new \TypeError('Could not accept a VariantAxis renderer in am Image attrinute.'); + throw new \TypeError('Could not accept a VariantAxis renderer in am Image attribute.'); } $this->fieldResolver = $fieldResolver; diff --git a/src/App/Domain/Magento/AttributeRenderer/Varchar.php b/src/App/Domain/Magento/AttributeRenderer/Metric.php similarity index 92% rename from src/App/Domain/Magento/AttributeRenderer/Varchar.php rename to src/App/Domain/Magento/AttributeRenderer/Metric.php index e2ca0a3..c519ee8 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Varchar.php +++ b/src/App/Domain/Magento/AttributeRenderer/Metric.php @@ -8,7 +8,7 @@ use App\Domain\Magento\VariantAxis; use Twig\TemplateWrapper; -class Varchar implements AttributeRenderer +class Metric implements AttributeRenderer { /** @var Attribute */ private $attribute; @@ -22,7 +22,7 @@ public function __construct( $this->attribute = $attribute; if ($fieldResolver instanceof VariantAxis) { - throw new \TypeError('Could not accept a VariantAxis renderer in a Varchar attrinute.'); + throw new \TypeError('Could not accept a VariantAxis renderer in a Metric attribute.'); } $this->fieldResolver = $fieldResolver; @@ -30,7 +30,7 @@ public function __construct( public function __toString() { - return 'varchar'; + return 'metric'; } public function __invoke(TemplateWrapper $template): string diff --git a/src/App/Domain/Magento/AttributeRenderer/RichText.php b/src/App/Domain/Magento/AttributeRenderer/RichText.php new file mode 100644 index 0000000..af9ff49 --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/RichText.php @@ -0,0 +1,67 @@ +attribute = $attribute; + + if ($fieldResolver instanceof VariantAxis) { + throw new \TypeError('Could not accept a VariantAxis renderer in a RichText attribute.'); + } + + $this->fieldResolver = $fieldResolver; + } + + public function __toString() + { + return 'rich-text'; + } + + public function __invoke(TemplateWrapper $template): string + { + if ($this->attribute instanceof Attribute\ExNihilo) { + return ''; + } + + return $template->render([ + 'attribute' => $this->attribute, + 'fields' => $this->fields(), + ]); + } + + public function template(): string + { + return $this->fieldResolver->template($this); + } + + public function attribute(): Attribute + { + return $this->attribute; + } + + public function fields(): iterable + { + return $this->fieldResolver->fields($this->attribute); + } + + public function isAxis(): bool + { + return false; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/AttributeRenderer/Status.php b/src/App/Domain/Magento/AttributeRenderer/Status.php index 89330e9..31d8866 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Status.php +++ b/src/App/Domain/Magento/AttributeRenderer/Status.php @@ -22,7 +22,7 @@ public function __construct( $this->attribute = $attribute; if ($fieldResolver instanceof VariantAxis) { - throw new \TypeError('Could not accept a VariantAxis renderer in a Status attrinute.'); + throw new \TypeError('Could not accept a VariantAxis renderer in a Status attribute.'); } $this->fieldResolver = $fieldResolver; diff --git a/src/App/Domain/Magento/AttributeRenderer/Text.php b/src/App/Domain/Magento/AttributeRenderer/Text.php index d502245..52fd916 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Text.php +++ b/src/App/Domain/Magento/AttributeRenderer/Text.php @@ -22,7 +22,7 @@ public function __construct( $this->attribute = $attribute; if ($fieldResolver instanceof VariantAxis) { - throw new \TypeError('Could not accept a VariantAxis renderer in a Text attrinute.'); + throw new \TypeError('Could not accept a VariantAxis renderer in a Text attribute.'); } $this->fieldResolver = $fieldResolver; diff --git a/src/App/Domain/Magento/AttributeRenderer/TextArea.php b/src/App/Domain/Magento/AttributeRenderer/TextArea.php new file mode 100644 index 0000000..3ed4f55 --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/TextArea.php @@ -0,0 +1,67 @@ +attribute = $attribute; + + if ($fieldResolver instanceof VariantAxis) { + throw new \TypeError('Could not accept a VariantAxis renderer in a TextArea attribute.'); + } + + $this->fieldResolver = $fieldResolver; + } + + public function __toString() + { + return 'text-area'; + } + + public function __invoke(TemplateWrapper $template): string + { + if ($this->attribute instanceof Attribute\ExNihilo) { + return ''; + } + + return $template->render([ + 'attribute' => $this->attribute, + 'fields' => $this->fields(), + ]); + } + + public function template(): string + { + return $this->fieldResolver->template($this); + } + + public function attribute(): Attribute + { + return $this->attribute; + } + + public function fields(): iterable + { + return $this->fieldResolver->fields($this->attribute); + } + + public function isAxis(): bool + { + return false; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/AttributeRenderer/Visibility.php b/src/App/Domain/Magento/AttributeRenderer/Visibility.php index 60cfa62..1d3bed2 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Visibility.php +++ b/src/App/Domain/Magento/AttributeRenderer/Visibility.php @@ -22,7 +22,7 @@ public function __construct( $this->attribute = $attribute; if ($fieldResolver instanceof VariantAxis) { - throw new \TypeError('Could not accept a VariantAxis renderer in a Visibility attrinute.'); + throw new \TypeError('Could not accept a VariantAxis renderer in a Visibility attribute.'); } $this->fieldResolver = $fieldResolver; diff --git a/src/App/Domain/Magento/Family.php b/src/App/Domain/Magento/Family.php new file mode 100644 index 0000000..5f19934 --- /dev/null +++ b/src/App/Domain/Magento/Family.php @@ -0,0 +1,17 @@ +code = $code; + $this->attributes = $attributes; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/FamilyVariant.php b/src/App/Domain/Magento/FamilyVariant.php index db53055..2a0cdd2 100644 --- a/src/App/Domain/Magento/FamilyVariant.php +++ b/src/App/Domain/Magento/FamilyVariant.php @@ -4,17 +4,20 @@ class FamilyVariant { + /** @var string */ + public $code; /** @var string */ public $skuTemplate; /** @var FamilyVariantAxis[] */ private $axises; - public function __construct(?string $skuTemplate, FamilyVariantAxis ...$axises) + public function __construct(string $code, ?string $skuTemplate, FamilyVariantAxis ...$axises) { if (count($axises) > 2) { throw new \OverflowException('Could not initialise a family variant with more than 2 axis levels.'); } + $this->code = $code; $this->skuTemplate = $skuTemplate; $this->axises = $axises; } diff --git a/src/App/Domain/Magento/Renderer.php b/src/App/Domain/Magento/Renderer.php index c2cee5d..e51870b 100644 --- a/src/App/Domain/Magento/Renderer.php +++ b/src/App/Domain/Magento/Renderer.php @@ -53,9 +53,13 @@ public function __invoke($stream, Environment $twig) /** @var AttributeRenderer $attribute */ foreach ($this->attributes as $attribute) { - $template = $twig->load($attribute->template()); + try { + $template = $twig->load($attribute->template()); - fwrite($stream, $attribute($template) . PHP_EOL); + fwrite($stream, $attribute($template) . PHP_EOL); + } catch (\RuntimeException $e) { + continue; + } } // $template = $twig->load($this->finalization); diff --git a/src/App/Domain/Magento/SqlExportTwigExtension.php b/src/App/Domain/Magento/SqlExportTwigExtension.php index 5b161ea..cfc5472 100644 --- a/src/App/Domain/Magento/SqlExportTwigExtension.php +++ b/src/App/Domain/Magento/SqlExportTwigExtension.php @@ -24,10 +24,11 @@ public function getFunctions() return 'attr_default'; }), - new TwigFunction('as_attribute_axis_table', function(Attribute $attribute) { + new TwigFunction('as_attribute_axis_table', function(FamilyVariant $variant, Attribute $attribute) { return strtr( - 'tmp_axis_{{ attribute }}', + 'tmp_axis_{{ variant }}__{{ attribute }}', [ + '{{ variant }}' => $variant->code, '{{ attribute }}' => $attribute->code(), ] ); diff --git a/src/App/Infrastructure/AttributeAggregator.php b/src/App/Infrastructure/AttributeAggregator.php new file mode 100644 index 0000000..f2b3d65 --- /dev/null +++ b/src/App/Infrastructure/AttributeAggregator.php @@ -0,0 +1,20 @@ +walk(...$families))); + } + + private function walk(Family ...$families): \Iterator + { + foreach ($families as $family) { + yield from $family->attributes; + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/AttributeRendererFactory.php b/src/App/Infrastructure/AttributeRendererFactory.php index 68198e8..7794eeb 100644 --- a/src/App/Infrastructure/AttributeRendererFactory.php +++ b/src/App/Infrastructure/AttributeRendererFactory.php @@ -53,6 +53,19 @@ public function walk( $attributeSpec = $config[$attribute->code()]; switch ($attributeSpec['type']) { + case 'identifier': + yield new AttributeRenderer\Identifier( + $attribute, + $this->buildFieldResolver( + false, + false, + false, + $scopes, + $locales + ) + ); + break; + case 'datetime': yield new AttributeRenderer\Datetime( $attribute, @@ -94,6 +107,19 @@ public function walk( case 'status': yield new AttributeRenderer\Status( + $attribute, + $this->buildFieldResolver( + false, + true, + true, + $scopes, + $locales + ) + ); + break; + + case 'text-area': + yield new AttributeRenderer\TextArea( $attribute, $this->buildFieldResolver( false, @@ -105,8 +131,8 @@ public function walk( ); break; - case 'text': - yield new AttributeRenderer\Text( + case 'rich-text': + yield new AttributeRenderer\RichText( $attribute, $this->buildFieldResolver( false, @@ -118,8 +144,8 @@ public function walk( ); break; - case 'varchar': - yield new AttributeRenderer\Varchar( + case 'text': + yield new AttributeRenderer\Text( $attribute, $this->buildFieldResolver( false, @@ -136,13 +162,35 @@ public function walk( $attribute, $this->buildFieldResolver( false, - $attributeSpec['scoped'] ?? false, - $attributeSpec['localised'] ?? false, + true, + true, + $scopes, + $locales + ) + ); + break; + + case 'metric': + yield new AttributeRenderer\Metric( + $attribute, + $this->buildFieldResolver( + false, + true, + true, $scopes, $locales ) ); break; + + default: + throw new \UnexpectedValueException(strtr( + 'Could not handle attribute of type %type%', + [ + '%type%' => $attributeSpec['type'] + ] + )); + break; } } } diff --git a/src/App/Infrastructure/Command/TwigCommand.php b/src/App/Infrastructure/Command/TwigCommand.php index 3679752..3bf99c8 100644 --- a/src/App/Infrastructure/Command/TwigCommand.php +++ b/src/App/Infrastructure/Command/TwigCommand.php @@ -40,10 +40,16 @@ public function __invoke(\PDO $connection): void $this->logger->debug('Compiling template {template}.', ['template' => $this->twigTemplate]); $view = $this->twig->load($this->twigTemplate); - - $this->logger->debug($query = $view->render($this->twigContext)); - - $connection->exec($query); + $queries = explode(';', $view->render($this->twigContext)); + + foreach ($queries as $query) { + $query = trim($query); + if (empty($query)) { + continue; + } + $this->logger->debug($query); + $connection->exec($query); + } } catch (LoaderError|RuntimeError|SyntaxError $e) { throw new \RuntimeException( 'An error occurred during the data preparation SQL rendering: ' diff --git a/src/App/Infrastructure/Configuration/Configuration.php b/src/App/Infrastructure/Configuration/Configuration.php index 311f10b..1c4006c 100644 --- a/src/App/Infrastructure/Configuration/Configuration.php +++ b/src/App/Infrastructure/Configuration/Configuration.php @@ -27,7 +27,7 @@ public function getConfigTreeBuilder() ->isRequired() ->values([ 'identifier', - 'string', + 'text-area', 'text', 'rich-text', 'simple-select', diff --git a/src/App/Infrastructure/Console/Command/GoCommand.php b/src/App/Infrastructure/Console/Command/GoCommand.php index 67a7d93..b4c77a8 100644 --- a/src/App/Infrastructure/Console/Command/GoCommand.php +++ b/src/App/Infrastructure/Console/Command/GoCommand.php @@ -5,11 +5,13 @@ use App\Domain\Fixture; use App\Domain\Magento\Attribute; use App\Domain\Magento\AttributeRenderer; +use App\Domain\Magento\Family; use App\Domain\Magento\Locale; use App\Domain\Magento\Scope; use App\Domain\Magento\SqlExportTwigExtension; use App\Infrastructure\AttributeRendererFactory; use App\Infrastructure\Configuration\YamlFileLoader; +use App\Infrastructure\FamiliesFactory; use App\Infrastructure\Normalizer\Magento\AttributeDenormalizerFactory; use App\Infrastructure\Normalizer\Magento\LocaleDenormalizerFactory; use App\Infrastructure\Normalizer\Magento\ScopeDenormalizerFactory; @@ -161,6 +163,7 @@ protected function execute(InputInterface $input, OutputInterface $output) array_keys($config['locales']), $config['codes_mapping'] ); + die; $style->writeln('attribute options ok', SymfonyStyle::OUTPUT_PLAIN); @@ -189,10 +192,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $style->writeln('family variants ok', SymfonyStyle::OUTPUT_PLAIN); + /** @var Attribute[] $attributes */ $attributes = (new AttributeDenormalizerFactory())() ->denormalize($config['attributes'], Attribute::class.'[]'); + /** @var Scope[] $scopes */ $scopes = (new ScopeDenormalizerFactory())() ->denormalize($config['scopes'], Scope::class.'[]'); + /** @var Locale[] $locales */ $locales = (new LocaleDenormalizerFactory())() ->denormalize($config['scopes'], Locale::class.'[]'); @@ -221,17 +227,20 @@ protected function execute(InputInterface $input, OutputInterface $output) $config['attributes'] ); + /** @var Family[] $families */ + $families = (new FamiliesFactory(...$attributeRenderers))($config); /** @var Attribute[] $axises */ $axises = (new VariantAxisesFactory(...$attributeRenderers))($config); - (new Fixture\Command\ExtractSimpleProducts($pdo, $twig, $this->logger))( + (new Fixture\Command\ExtractProducts($pdo, $twig, $this->logger))( new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/products.csv', 'w'), + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/product-models.csv', 'w'), $attributeRenderers, + $families, $axises ); - $style->writeln('products ok', SymfonyStyle::OUTPUT_PLAIN); - $style->writeln('product models nok', SymfonyStyle::OUTPUT_PLAIN); + $style->writeln('products and product models ok', SymfonyStyle::OUTPUT_PLAIN); return 0; } diff --git a/src/App/Infrastructure/FamiliesFactory.php b/src/App/Infrastructure/FamiliesFactory.php new file mode 100644 index 0000000..20a8f09 --- /dev/null +++ b/src/App/Infrastructure/FamiliesFactory.php @@ -0,0 +1,72 @@ +renderers = $renderers; + } + + public function __invoke(array $config): array + { + return iterator_to_array($this->walk($config)); + } + + private function walk(array $config): \Iterator + { + foreach ($config['families'] as $family) { + yield new Family( + $family['code'], + ...$this->extractAttributes($family['attributes']) + ); + } + } + + /** + * @param string[] $axises + * + * @return \Iterator|AttributeRenderer[] + */ + private function extractAttributes(array $axises): \Iterator + { + foreach ($axises as $code) { + yield $this->findAttributeRenderer($code); + } + } + + private function findAttributeRenderer(string $code): AttributeRenderer + { + $renderers = array_filter($this->renderers, function (AttributeRenderer $renderer) use ($code) { + return $renderer->attribute()->code() === $code; + }); + + if (count($renderers) > 1) { + throw new AttributeNotFoundException(strtr( + 'Found several attributes configuration with code "%code%".', + [ + '%code%' => $code, + ] + )); + } + + if (count($renderers) < 1) { + throw new AttributeNotFoundException(strtr( + 'Attribute with code "%code%" was not found in configuration.', + [ + '%code%' => $code, + ] + )); + } + + return array_pop($renderers); + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php b/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php new file mode 100644 index 0000000..90fbc64 --- /dev/null +++ b/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php @@ -0,0 +1,23 @@ +walk(...$variants))); + } + + private function walk(FamilyVariant ...$variants): \Iterator + { + foreach ($variants as $variant) { + yield from $variant->axis(1)->attributes; + if ($variant->isTwoLevels()) { + yield from $variant->axis(2)->attributes; + } + } + } +} \ No newline at end of file diff --git a/src/App/Infrastructure/VariantAxisesFactory.php b/src/App/Infrastructure/VariantAxisesFactory.php index cb45112..883e641 100644 --- a/src/App/Infrastructure/VariantAxisesFactory.php +++ b/src/App/Infrastructure/VariantAxisesFactory.php @@ -28,12 +28,14 @@ private function walk(array $config): \Iterator foreach ($family['variations'] as $variation) { if (isset($variation['level_2']['axis'])) { yield new FamilyVariant( + $variation['code'], $variation['skuPattern'], new FamilyVariantAxis(...$this->extractAttributes($variation['level_1']['axis'])), new FamilyVariantAxis(...$this->extractAttributes($variation['level_2']['axis'])) ); } else { yield new FamilyVariant( + $variation['code'], null, new FamilyVariantAxis(...$this->extractAttributes($variation['level_1']['axis'])) ); diff --git a/src/App/Resources/templates/extract-product-models.sql.twig b/src/App/Resources/templates/extract-product-models.sql.twig new file mode 100644 index 0000000..6f19726 --- /dev/null +++ b/src/App/Resources/templates/extract-product-models.sql.twig @@ -0,0 +1,3 @@ +SELECT * FROM tmp_parent_models +UNION +SELECT * FROM tmp_child_models \ No newline at end of file diff --git a/src/App/Resources/templates/extract-products.sql.twig b/src/App/Resources/templates/extract-products.sql.twig index a43ecbc..a94cdb1 100644 --- a/src/App/Resources/templates/extract-products.sql.twig +++ b/src/App/Resources/templates/extract-products.sql.twig @@ -4,11 +4,11 @@ SELECT {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, {% endfor %} {% endfor %} - product.sku AS sku, - COALESCE(hierarchy.child, hierarchy.parent) AS product_model + product.sku AS code, + COALESCE(hierarchy.child, hierarchy.parent) AS parent FROM tmp_sku AS product {% for attribute in attributes %} -INNER JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias(attribute.attribute) }} +LEFT JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias(attribute.attribute) }} ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id {% endfor %} LEFT JOIN tmp_hierarchy as hierarchy diff --git a/src/App/Resources/templates/initialize.sql.twig b/src/App/Resources/templates/initialize.sql.twig index 33705e8..5f65d67 100644 --- a/src/App/Resources/templates/initialize.sql.twig +++ b/src/App/Resources/templates/initialize.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE tmp_hierarchy ( +CREATE /*TEMPORARY*/ TABLE tmp_hierarchy ( parent VARCHAR(128) NOT NULL, child VARCHAR(128) NOT NULL, variant VARCHAR(128) NOT NULL, diff --git a/src/App/Resources/templates/product/00-sku.sql.twig b/src/App/Resources/templates/product/00-sku.sql.twig index 5ed8218..3d00f69 100644 --- a/src/App/Resources/templates/product/00-sku.sql.twig +++ b/src/App/Resources/templates/product/00-sku.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE tmp_sku ( +CREATE /*TEMPORARY*/ TABLE tmp_sku ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/01-attribute-options.sql.twig b/src/App/Resources/templates/product/01-attribute-options.sql.twig index 75eb8a0..6a5e785 100644 --- a/src/App/Resources/templates/product/01-attribute-options.sql.twig +++ b/src/App/Resources/templates/product/01-attribute-options.sql.twig @@ -1,7 +1,7 @@ -CREATE TEMPORARY TABLE tmp_options ( +CREATE /*TEMPORARY*/ TABLE tmp_options ( option_id INTEGER NOT NULL, code VARCHAR(256) NOT NULL, - short_code VARCHAR(256) NOT NULL, + short_code VARCHAR(255) NOT NULL, attribute VARCHAR(128) NOT NULL, sort_order INTEGER NOT NULL, PRIMARY KEY (option_id), diff --git a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig index a627126..8c66233 100644 --- a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig +++ b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig @@ -1,7 +1,7 @@ {% for variant in variants %} {% for axis in variant.all %} {% for attribute in axis.attributes %} -CREATE TEMPORARY TABLE {{ as_attribute_axis_table(attribute.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_axis_table(variant, attribute.attribute) }} ( entity_id INTEGER NOT NULL, code VARCHAR(256) NOT NULL, short_code VARCHAR(256) NOT NULL, diff --git a/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig index 56c3797..e1b5748 100644 --- a/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig +++ b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE tmp_hierarchy ( +CREATE /*TEMPORARY*/ TABLE tmp_hierarchy ( parent VARCHAR(128) NOT NULL, child VARCHAR(128) NOT NULL, variant VARCHAR(128) NOT NULL, diff --git a/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig b/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig new file mode 100644 index 0000000..53234d5 --- /dev/null +++ b/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig @@ -0,0 +1,45 @@ +CREATE /*TEMPORARY*/ TABLE tmp_parent_models +SELECT +{% for attribute in attributes %} + {% for field in attribute.fields %} + {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, + {% endfor %} +{% endfor %} + hierarchy.parent AS code, + NULL AS parent +FROM tmp_sku AS product +{% for attribute in attributes %} +LEFT JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias(attribute.attribute) }} + ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id +{% endfor %} +INNER JOIN ( + SELECT DISTINCT hierarchy.parent + FROM tmp_hierarchy as hierarchy + GROUP BY hierarchy.parent +) AS hierarchy + ON hierarchy.parent=product.sku +WHERE product.type_id IN ('configurable'); + +CREATE /*TEMPORARY*/ TABLE tmp_child_models +SELECT +{% for attribute in attributes %} + {% for field in attribute.fields %} + {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, + {% endfor %} +{% endfor %} + hierarchy.child AS code, + hierarchy.parent AS parent +FROM tmp_sku AS product +{% for attribute in attributes %} +LEFT JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias(attribute.attribute) }} + ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id +{% endfor %} +INNER JOIN ( + SELECT MAX(hierarchy.parent) AS parent, hierarchy.child + FROM tmp_hierarchy as hierarchy + GROUP BY hierarchy.child +) AS hierarchy + ON hierarchy.parent=product.sku +WHERE product.type_id IN ('configurable'); + + diff --git a/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig index 0e64f69..69a21db 100644 --- a/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig index 899e7a2..6d52a20 100644 --- a/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-image.sql.twig b/src/App/Resources/templates/product/attribute/extract-image.sql.twig index 68c3fee..35d1d64 100644 --- a/src/App/Resources/templates/product/attribute/extract-image.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig similarity index 80% rename from src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig index 062cece..3d0ea35 100644 --- a/src/App/Resources/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig @@ -1,10 +1,9 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {%- for field in renderer.fields -%} - `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}` DECIMAL(12, 4) NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) @@ -17,12 +16,12 @@ SELECT FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 -LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} +LEFT JOIN catalog_product_entity_decimal AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0 {% for field in renderer.fields -%} -LEFT JOIN catalog_product_entity_varchar AS {{ as_field_alias(field) }} +LEFT JOIN catalog_product_entity_decimal AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} diff --git a/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig new file mode 100644 index 0000000..fabab75 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig @@ -0,0 +1,28 @@ +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` TEXT NULL, + {% endfor -%} + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + {% for field in renderer.fields -%} + COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, + {% endfor -%} + product.* +FROM tmp_sku AS product +INNER JOIN eav_attribute AS attribute + ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 +LEFT JOIN catalog_product_entity_text AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id + AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id + AND {{ as_default_alias() }}.store_id=0 +{% for field in renderer.fields -%} +LEFT JOIN catalog_product_entity_text AS {{ as_field_alias(field) }} + ON {{ as_field_alias(field) }}.entity_id=product.entity_id + AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id + AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} +{% endfor -%}; diff --git a/src/App/Resources/templates/product/attribute/extract-text.sql.twig b/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig similarity index 91% rename from src/App/Resources/templates/product/attribute/extract-text.sql.twig rename to src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig index 4fcfef1..c82b6d3 100644 --- a/src/App/Resources/templates/product/attribute/extract-text.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig @@ -1,5 +1,5 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig index a35dd93..d3e5a83 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -7,8 +7,8 @@ CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, - {{ as_default_alias() }}.value + {{ as_default_alias() }}_option.code AS code, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 @@ -24,7 +24,7 @@ LEFT JOIN ( ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; {% for field in renderer.fields %} -CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -33,11 +33,11 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, COALESCE( - {{ as_field_alias(field) }}.code, + {{ as_field_alias(field) }}_option.code, {{ as_default_alias() }}.code - ) AS `code` + ) AS `code`, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 @@ -55,22 +55,24 @@ LEFT JOIN ( ON {{ as_field_alias(field) }}.value={{ as_field_alias(field) }}_option.option_id; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {% for field in renderer.fields %} - `{{ field }}` VARCHAR(255) NOT NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - {{ as_field_alias(field) }}.value AS `{{ as_field_column(field) }}`, + COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id {% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig index f6ab3ba..f882ff6 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -7,8 +7,8 @@ CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, - {{ as_default_alias() }}.value + {{ as_default_alias() }}_option.code AS code, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 @@ -24,7 +24,7 @@ LEFT JOIN ( ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; {% for field in renderer.fields %} -CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -33,11 +33,11 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, COALESCE( - {{ as_field_alias(field) }}.code, + {{ as_field_alias(field) }}_option.code, {{ as_default_alias() }}.code - ) AS `code` + ) AS `code`, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 @@ -55,22 +55,24 @@ LEFT JOIN ( ON {{ as_field_alias(field) }}.value={{ as_field_alias(field) }}_option.option_id; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {% for field in renderer.fields %} - `{{ as_field_alias(field) }}` VARCHAR(255) NOT NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - {{ as_field_table(field) }}.value AS `{{ as_field_alias(field) }}`, + COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id {% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig index 74b719b..cab0c0c 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -10,7 +10,7 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - {{ as_default_alias() }}.value AS `{{ as_field_alias(field) }}`, + {{ as_default_alias() }}_option.code AS `{{ as_field_alias(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-status.sql.twig b/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig similarity index 70% rename from src/App/Resources/templates/product/attribute/extract-status.sql.twig rename to src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig index 1886b43..8ba8cad 100644 --- a/src/App/Resources/templates/product/attribute/extract-status.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -7,22 +7,22 @@ CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, CASE {{ as_default_alias() }}.value WHEN 1 THEN 'enabled' WHEN 2 THEN 'disabled' ELSE 'disabled' - END AS value + END AS code, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.code }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0; {% for field in renderer.fields %} -CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -31,17 +31,18 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, - COALESCE(CASE {{ as_field_alias(field) }}.value - WHEN 1 THEN 'enabled' - WHEN 2 THEN 'disabled' - ELSE 'disabled' + COALESCE( + CASE {{ as_field_alias(field) }}.value + WHEN 1 THEN 'enabled' + WHEN 2 THEN 'disabled' + ELSE 'disabled' END, {{ as_default_alias() }}.code - ) AS `code` + ) AS `code`, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.code }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} @@ -50,7 +51,7 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -62,10 +63,12 @@ CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, + COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id {% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id diff --git a/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig new file mode 100644 index 0000000..fabab75 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig @@ -0,0 +1,28 @@ +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` TEXT NULL, + {% endfor -%} + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + {% for field in renderer.fields -%} + COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, + {% endfor -%} + product.* +FROM tmp_sku AS product +INNER JOIN eav_attribute AS attribute + ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 +LEFT JOIN catalog_product_entity_text AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id + AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id + AND {{ as_default_alias() }}.store_id=0 +{% for field in renderer.fields -%} +LEFT JOIN catalog_product_entity_text AS {{ as_field_alias(field) }} + ON {{ as_field_alias(field) }}.entity_id=product.entity_id + AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id + AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} +{% endfor -%}; diff --git a/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig new file mode 100644 index 0000000..c82b6d3 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig @@ -0,0 +1,23 @@ + +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` TEXT NULL, + {% endfor -%} + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + {% for field in renderer.fields -%} + {{ as_default_alias() }}.value AS `{{ as_field_column(field) }}`, + {% endfor -%} + product.* +FROM tmp_sku AS product +INNER JOIN eav_attribute AS attribute + ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 +INNER JOIN catalog_product_entity_text AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id + AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id + AND {{ as_default_alias() }}.store_id=0 \ No newline at end of file diff --git a/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig index b1addc6..6b43fa1 100644 --- a/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig @@ -1,9 +1,10 @@ -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( + +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {%- for field in renderer.fields -%} - `{{ as_field_column(field) }}` TEXT NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) @@ -16,12 +17,12 @@ SELECT FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 -LEFT JOIN catalog_product_entity_text AS {{ as_default_alias() }} +LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id AND {{ as_default_alias() }}.store_id=0 {% for field in renderer.fields -%} -LEFT JOIN catalog_product_entity_text AS {{ as_field_alias(field) }} +LEFT JOIN catalog_product_entity_varchar AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} diff --git a/src/App/Resources/templates/product/attribute/extract-visibility.sql.twig b/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig similarity index 73% rename from src/App/Resources/templates/product/attribute/extract-visibility.sql.twig rename to src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig index 87d0514..f02b75f 100644 --- a/src/App/Resources/templates/product/attribute/extract-visibility.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -7,14 +7,14 @@ CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, CASE {{ as_default_alias() }}.value WHEN 1 THEN 'not_visible' WHEN 2 THEN 'visible_in_catalog' WHEN 3 THEN 'visible_in_search' WHEN 4 THEN 'visible_in_catalog_and_search' ELSE 'not_visible' - END AS value + END AS code, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 @@ -24,7 +24,7 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} AND {{ as_default_alias() }}.store_id=0; {% for field in renderer.fields %} -CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -33,15 +33,16 @@ CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - product.*, - COALESCE(CASE {{ as_field_alias(field) }}.value - WHEN 1 THEN 'not_visible' - WHEN 2 THEN 'visible_in_catalog' - WHEN 3 THEN 'visible_in_search' - WHEN 4 THEN 'visible_in_catalog_and_search' + COALESCE( + CASE {{ as_field_alias(field) }}.value + WHEN 1 THEN 'not_visible' + WHEN 2 THEN 'visible_in_catalog' + WHEN 3 THEN 'visible_in_search' + WHEN 4 THEN 'visible_in_catalog_and_search' END, {{ as_default_alias() }}.code - ) AS `code` + ) AS `code`, + product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 @@ -53,22 +54,24 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; {% endfor %} -CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {% for field in renderer.fields %} - `{{ field }}` VARCHAR(255) NOT NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NOT NULL, {% endfor %} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.value, {{ as_default_alias() }}.value) AS `{{ as_field_column(field) }}`, + COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product +INNER JOIN {{ as_attribute_default_table(renderer.attribute) }} AS {{ as_default_alias() }} + ON {{ as_default_alias() }}.entity_id=product.entity_id {% for field in renderer.fields %} LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} ON {{ as_field_alias(field) }}.entity_id=product.entity_id From ffbbcd546a92f4222d1243f7e7e321f7f6b4117b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Mon, 21 Oct 2019 16:34:20 +0200 Subject: [PATCH 5/7] Fixed old refactoring bugs --- .../Command/ExtractAttributeOptions.php | 12 +- .../Fixture/Command/ExtractAttributes.php | 19 ++- .../Fixture/Command/ExtractChannels.php | 60 +++++++++ .../Fixture/Command/ExtractFamilyVariants.php | 13 +- .../Fixture/Command/ExtractProducts.php | 22 +++- src/App/Domain/Magento/Attribute/AdHoc.php | 10 +- src/App/Domain/Magento/Attribute/Aliased.php | 10 +- src/App/Domain/Magento/Attribute/ExNihilo.php | 10 +- src/App/Domain/Magento/AttributeRenderer.php | 2 + .../Magento/AttributeRenderer/Datetime.php | 3 + .../Magento/AttributeRenderer/Identifier.php | 3 + .../Magento/AttributeRenderer/Image.php | 3 + .../LocalizationAwareTrait.php | 17 +++ .../Magento/AttributeRenderer/Metric.php | 3 + .../Magento/AttributeRenderer/RichText.php | 3 + .../AttributeRenderer/ScopingAwareTrait.php | 17 +++ .../AttributeRenderer/SimpleSelect.php | 5 +- .../Magento/AttributeRenderer/Status.php | 3 + .../Domain/Magento/AttributeRenderer/Text.php | 3 + .../Magento/AttributeRenderer/TextArea.php | 3 + .../Magento/AttributeRenderer/Visibility.php | 3 + src/App/Domain/Magento/Locale/Locale.php | 5 + .../Domain/Magento/Locale/LocaleMapping.php | 5 + src/App/Domain/Magento/Scope/Scope.php | 5 + src/App/Domain/Magento/Scope/ScopeMapping.php | 5 + .../Domain/Magento/SqlExportTwigExtension.php | 2 +- .../Infrastructure/AttributeAggregator.php | 7 +- .../{GoCommand.php => MagentoCommand.php} | 121 ++++++++++-------- .../FamilyVariantAxisAttributeAggregator.php | 7 +- .../Magento/Attribute/AdHocNormalizer.php | 2 +- .../Magento/Attribute/AliasedNormalizer.php | 2 +- .../Magento/Attribute/ExNihiloNormalizer.php | 2 +- .../extract-attribute-options.sql.twig | 52 +++++++- .../templates/extract-attributes.sql.twig | 89 ++++++++----- .../templates/extract-products.sql.twig | 17 ++- .../finalize-product-children.sql.twig | 22 ---- .../finalize-product-parents.sql.twig | 21 --- .../templates/finalize-products.sql.twig | 17 --- .../Resources/templates/initialize.sql.twig | 30 ----- .../templates/product/00-sku.sql.twig | 2 +- .../product/01-attribute-options.sql.twig | 5 +- .../02-prefill-axis-attributes.sql.twig | 2 +- ...erate-intermediate-product-models.sql.twig | 23 +++- .../04-consolidate-product-models.sql.twig | 31 ++++- ...act-datetime-scopable-localizable.sql.twig | 2 +- ...xtract-image-scopable-localizable.sql.twig | 2 +- .../product/attribute/extract-image.sql.twig | 2 +- ...tract-metric-scopable-localizable.sql.twig | 4 +- ...ct-rich-text-scopable-localizable.sql.twig | 4 +- .../attribute/extract-rich-text.sql.twig | 4 +- ...mple-select-scopable-localizable.sql.twig} | 36 ++++-- ...> extract-simple-select-scopable.sql.twig} | 36 ++++-- ...ql.twig => extract-simple-select.sql.twig} | 6 +- ...tract-status-scopable-localizable.sql.twig | 25 ++-- ...ct-text-area-scopable-localizable.sql.twig | 4 +- .../attribute/extract-text-area.sql.twig | 4 +- ...extract-text-scopable-localizable.sql.twig | 4 +- ...t-visibility-scopable-localizable.sql.twig | 25 ++-- 58 files changed, 574 insertions(+), 282 deletions(-) create mode 100644 src/App/Domain/Fixture/Command/ExtractChannels.php create mode 100644 src/App/Domain/Magento/AttributeRenderer/LocalizationAwareTrait.php create mode 100644 src/App/Domain/Magento/AttributeRenderer/ScopingAwareTrait.php rename src/App/Infrastructure/Console/Command/{GoCommand.php => MagentoCommand.php} (89%) delete mode 100644 src/App/Resources/templates/finalize-product-children.sql.twig delete mode 100644 src/App/Resources/templates/finalize-product-parents.sql.twig delete mode 100644 src/App/Resources/templates/finalize-products.sql.twig delete mode 100644 src/App/Resources/templates/initialize.sql.twig rename src/App/Resources/templates/product/attribute/{extract-simpleselect-scopable-localizable.sql.twig => extract-simple-select-scopable-localizable.sql.twig} (66%) rename src/App/Resources/templates/product/attribute/{extract-simpleselect-scopable.sql.twig => extract-simple-select-scopable.sql.twig} (66%) rename src/App/Resources/templates/product/attribute/{extract-simpleselect.sql.twig => extract-simple-select.sql.twig} (77%) diff --git a/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php b/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php index 34c96a0..7fca365 100644 --- a/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php +++ b/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php @@ -3,6 +3,11 @@ namespace App\Domain\Fixture\Command; use App\Domain\Fixture\SqlToCsv; +use App\Infrastructure\Command\CommandBus; +use App\Infrastructure\Command\TwigCommand; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; @@ -10,15 +15,18 @@ class ExtractAttributeOptions { + use LoggerAwareTrait; + /** @var \PDO */ private $pdo; /** @var Environment */ private $twig; - public function __construct(\PDO $pdo, Environment $twig) + public function __construct(\PDO $pdo, Environment $twig, ?LoggerInterface $logger = null) { $this->pdo = $pdo; $this->twig = $twig; + $this->logger = $logger ?? new NullLogger(); } public function __invoke( @@ -33,7 +41,7 @@ public function __invoke( throw new \RuntimeException(null, null, $e); } - (new SqlToCsv($this->pdo)) + (new SqlToCsv($this->pdo, $this->logger)) ( $view->render([ 'attributes' => $attributes, diff --git a/src/App/Domain/Fixture/Command/ExtractAttributes.php b/src/App/Domain/Fixture/Command/ExtractAttributes.php index 8494288..9d04f14 100644 --- a/src/App/Domain/Fixture/Command/ExtractAttributes.php +++ b/src/App/Domain/Fixture/Command/ExtractAttributes.php @@ -3,6 +3,9 @@ namespace App\Domain\Fixture\Command; use App\Domain\Fixture\SqlToCsv; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; @@ -10,30 +13,38 @@ class ExtractAttributes { + use LoggerAwareTrait; + /** @var \PDO */ private $pdo; /** @var Environment */ private $twig; - public function __construct(\PDO $pdo, Environment $twig) + public function __construct(\PDO $pdo, Environment $twig, ?LoggerInterface $logger = null) { $this->pdo = $pdo; $this->twig = $twig; + $this->logger = $logger ?? new NullLogger(); } - public function __invoke(\SplFileObject $output, array $attributes, array $locales): void - { + public function __invoke( + \SplFileObject $output, + array $attributes, + array $locales, + array $mapping + ): void { try { $view = $this->twig->load('extract-attributes.sql.twig'); } catch (LoaderError|RuntimeError|SyntaxError $e) { throw new \RuntimeException(null, null, $e); } - (new SqlToCsv($this->pdo)) + (new SqlToCsv($this->pdo, $this->logger)) ( $view->render([ 'attributes' => $attributes, 'locales' => $locales, + 'mapping' => $mapping, ]), $output ); diff --git a/src/App/Domain/Fixture/Command/ExtractChannels.php b/src/App/Domain/Fixture/Command/ExtractChannels.php new file mode 100644 index 0000000..8e5e92d --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractChannels.php @@ -0,0 +1,60 @@ + $scope) { + yield array_merge( + [ + 'code' => $code, + 'tree' => 'root_catalog', + 'locales' => implode(',', array_map(function($locale) { + return $locale['code']; + }, $scope['locales'])), + 'currencies' => implode(',', array_values(array_unique(array_map( + function(array $item) { + return $item['currency']; + }, + array_filter( + $locales, + function (array $locale) use ($scope) { + return in_array($locale['code'], array_column($scope['locales'], 'code')); + } + ) + )))), + ], + ...array_map(function($locale) use($code) { + return [ + 'label-' . $locale['code'] => sprintf('%s (%s)', $code, $locale['code']), + ]; + }, $scope['locales']) + ); + } + })($scopes, $locales), + $output + ); + } +} \ No newline at end of file diff --git a/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php b/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php index 523c689..a53b2dd 100644 --- a/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php +++ b/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php @@ -28,19 +28,24 @@ public function __invoke(\SplFileObject $output, array $families, array $locales ( (function(array $families, array $locales) { foreach ($families as $familyCode => $family) { - foreach ($family['variations'] as $variationCode => $variation) { + foreach ($family['variations'] as $variation) { yield array_merge( [ - 'code' => $variationCode, + 'code' => $variation['code'], 'family' => $familyCode, 'variant-axes_1' => isset($variation['level_1']) ? implode(',', $variation['level_1']['axis']) : null, 'variant-axes_2' => isset($variation['level_2']) ? implode(',', $variation['level_2']['axis']) : null, 'variant-attributes_1' => isset($variation['level_1']) ? implode(',', $variation['level_1']['attributes']) : null, 'variant-attributes_2' => isset($variation['level_2']) ? implode(',', $variation['level_2']['attributes']) : null, ], - ...array_map(function ($locale) use ($familyCode) { + ...array_map(function ($locale) use ($variation) { return [ - 'label-' . $locale => sprintf('%s (%s)', $familyCode, $locale), + 'label-' . $locale => sprintf( + '%s [%s] (%s)', + $variation['code'], + implode(', ', array_merge($variation['level_1']['axis'] ?? [], $variation['level_2']['axis'] ?? [])), + $locale + ), ]; }, $locales) ); diff --git a/src/App/Domain/Fixture/Command/ExtractProducts.php b/src/App/Domain/Fixture/Command/ExtractProducts.php index afc6ec9..820b41d 100644 --- a/src/App/Domain/Fixture/Command/ExtractProducts.php +++ b/src/App/Domain/Fixture/Command/ExtractProducts.php @@ -3,6 +3,7 @@ namespace App\Domain\Fixture\Command; use App\Domain\Fixture\SqlToCsv; +use App\Domain\Magento\Attribute\ExNihilo; use App\Domain\Magento\AttributeRenderer; use App\Domain\Magento\Family; use App\Domain\Magento\FamilyVariant; @@ -42,11 +43,12 @@ public function __construct(\PDO $pdo, Environment $twig, ?LoggerInterface $logg * @param FamilyVariant[] $familyVariants */ public function __invoke( - \SplFileObject $productModelOutput, \SplFileObject $productOutput, + \SplFileObject $productModelOutput, array $attributes, array $families, - array $familyVariants + array $familyVariants, + array $mapping ): void { $bus = new CommandBus(); try { @@ -61,8 +63,11 @@ public function __invoke( $this->twig, 'product/01-attribute-options.sql.twig', [ - 'mapping' => [], - 'attributes' => (new AttributeAggregator())(...$families), + 'mapping' => $mapping, + 'attributes' => array_merge( + (new AttributeAggregator())(...$families), + (new FamilyVariantAxisAttributeAggregator())(...$familyVariants) + ), ], $this->logger ), @@ -70,7 +75,7 @@ public function __invoke( $this->twig, 'product/02-prefill-axis-attributes.sql.twig', [ - 'mapping' => [], + 'mapping' => $mapping, 'variants' => $familyVariants, ], $this->logger @@ -88,6 +93,10 @@ public function __invoke( )); } + if ($attribute->attribute() instanceof ExNihilo) { + continue; + } + try { $bus->add( new TwigCommand( @@ -110,6 +119,7 @@ public function __invoke( 'product/03-generate-intermediate-product-models.sql.twig', [ 'variants' => $familyVariants, + 'attributes' => (new FamilyVariantAxisAttributeAggregator())(...$familyVariants), ], $this->logger ) @@ -144,7 +154,7 @@ public function __invoke( (new SqlToCsv($this->pdo, $this->logger)) ( $view->render([ - 'attributes' => (new AttributeAggregator())(...$families), + 'attributes' => (new FamilyVariantAxisAttributeAggregator())(...$familyVariants), 'variants' => $familyVariants, ]), $productModelOutput diff --git a/src/App/Domain/Magento/Attribute/AdHoc.php b/src/App/Domain/Magento/Attribute/AdHoc.php index e3a2f67..ecdba20 100644 --- a/src/App/Domain/Magento/Attribute/AdHoc.php +++ b/src/App/Domain/Magento/Attribute/AdHoc.php @@ -8,10 +8,18 @@ class AdHoc implements Attribute { /** @var string */ public $code; + /** @var string */ + public $group; - public function __construct(string $code) + public function __construct(string $code, string $group) { $this->code = $code; + $this->group = $group; + } + + public function __toString() + { + return 'ad-hoc'; } public function code(): string diff --git a/src/App/Domain/Magento/Attribute/Aliased.php b/src/App/Domain/Magento/Attribute/Aliased.php index 3207a04..d9bf1d0 100644 --- a/src/App/Domain/Magento/Attribute/Aliased.php +++ b/src/App/Domain/Magento/Attribute/Aliased.php @@ -10,11 +10,19 @@ class Aliased implements Attribute public $code; /** @var string */ public $source; + /** @var string */ + public $group; - public function __construct(string $code, string $source) + public function __construct(string $code, string $source, string $group) { $this->code = $code; $this->source = $source; + $this->group = $group; + } + + public function __toString() + { + return 'aliased'; } public function code(): string diff --git a/src/App/Domain/Magento/Attribute/ExNihilo.php b/src/App/Domain/Magento/Attribute/ExNihilo.php index b6f43b4..feafd64 100644 --- a/src/App/Domain/Magento/Attribute/ExNihilo.php +++ b/src/App/Domain/Magento/Attribute/ExNihilo.php @@ -8,10 +8,18 @@ class ExNihilo implements Attribute { /** @var string */ public $code; + /** @var string */ + public $group; - public function __construct(string $code) + public function __construct(string $code, string $group) { $this->code = $code; + $this->group = $group; + } + + public function __toString() + { + return 'ex-nihilo'; } public function code(): string diff --git a/src/App/Domain/Magento/AttributeRenderer.php b/src/App/Domain/Magento/AttributeRenderer.php index 7237c92..da45ec9 100644 --- a/src/App/Domain/Magento/AttributeRenderer.php +++ b/src/App/Domain/Magento/AttributeRenderer.php @@ -12,4 +12,6 @@ public function template(): string; public function attribute(): Attribute; public function fields(): iterable; public function isAxis(): bool; + public function isScoped(): bool; + public function isLocalized(): bool; } \ No newline at end of file diff --git a/src/App/Domain/Magento/AttributeRenderer/Datetime.php b/src/App/Domain/Magento/AttributeRenderer/Datetime.php index f303c7c..deff2da 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Datetime.php +++ b/src/App/Domain/Magento/AttributeRenderer/Datetime.php @@ -10,6 +10,9 @@ class Datetime implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/Identifier.php b/src/App/Domain/Magento/AttributeRenderer/Identifier.php index 3d70b3c..23d3384 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Identifier.php +++ b/src/App/Domain/Magento/AttributeRenderer/Identifier.php @@ -10,6 +10,9 @@ class Identifier implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/Image.php b/src/App/Domain/Magento/AttributeRenderer/Image.php index d7b513a..0aad105 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Image.php +++ b/src/App/Domain/Magento/AttributeRenderer/Image.php @@ -10,6 +10,9 @@ class Image implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/LocalizationAwareTrait.php b/src/App/Domain/Magento/AttributeRenderer/LocalizationAwareTrait.php new file mode 100644 index 0000000..ed0caaa --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/LocalizationAwareTrait.php @@ -0,0 +1,17 @@ +fieldResolver instanceof FieldResolver\Localized + || $this->fieldResolver instanceof FieldResolver\ScopedAndLocalized; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/AttributeRenderer/Metric.php b/src/App/Domain/Magento/AttributeRenderer/Metric.php index c519ee8..97f777f 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Metric.php +++ b/src/App/Domain/Magento/AttributeRenderer/Metric.php @@ -10,6 +10,9 @@ class Metric implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/RichText.php b/src/App/Domain/Magento/AttributeRenderer/RichText.php index af9ff49..1dce15c 100644 --- a/src/App/Domain/Magento/AttributeRenderer/RichText.php +++ b/src/App/Domain/Magento/AttributeRenderer/RichText.php @@ -10,6 +10,9 @@ class RichText implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/ScopingAwareTrait.php b/src/App/Domain/Magento/AttributeRenderer/ScopingAwareTrait.php new file mode 100644 index 0000000..afcd668 --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/ScopingAwareTrait.php @@ -0,0 +1,17 @@ +fieldResolver instanceof FieldResolver\Scoped + || $this->fieldResolver instanceof FieldResolver\ScopedAndLocalized; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php b/src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php index 037128f..550e649 100644 --- a/src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php +++ b/src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php @@ -10,6 +10,9 @@ class SimpleSelect implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ @@ -25,7 +28,7 @@ public function __construct( public function __toString() { - return 'simpleselect'; + return 'simple-select'; } public function __invoke(TemplateWrapper $template): string diff --git a/src/App/Domain/Magento/AttributeRenderer/Status.php b/src/App/Domain/Magento/AttributeRenderer/Status.php index 31d8866..41040a5 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Status.php +++ b/src/App/Domain/Magento/AttributeRenderer/Status.php @@ -10,6 +10,9 @@ class Status implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/Text.php b/src/App/Domain/Magento/AttributeRenderer/Text.php index 52fd916..6e15a9c 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Text.php +++ b/src/App/Domain/Magento/AttributeRenderer/Text.php @@ -10,6 +10,9 @@ class Text implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/TextArea.php b/src/App/Domain/Magento/AttributeRenderer/TextArea.php index 3ed4f55..297868c 100644 --- a/src/App/Domain/Magento/AttributeRenderer/TextArea.php +++ b/src/App/Domain/Magento/AttributeRenderer/TextArea.php @@ -10,6 +10,9 @@ class TextArea implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/AttributeRenderer/Visibility.php b/src/App/Domain/Magento/AttributeRenderer/Visibility.php index 1d3bed2..74dc6ec 100644 --- a/src/App/Domain/Magento/AttributeRenderer/Visibility.php +++ b/src/App/Domain/Magento/AttributeRenderer/Visibility.php @@ -10,6 +10,9 @@ class Visibility implements AttributeRenderer { + use ScopingAwareTrait; + use LocalizationAwareTrait; + /** @var Attribute */ private $attribute; /** @var FieldResolver */ diff --git a/src/App/Domain/Magento/Locale/Locale.php b/src/App/Domain/Magento/Locale/Locale.php index 0adddb8..21ce837 100644 --- a/src/App/Domain/Magento/Locale/Locale.php +++ b/src/App/Domain/Magento/Locale/Locale.php @@ -18,6 +18,11 @@ public function __construct(string $code, MagentoStore $default) $this->default = $default; } + public function __toString() + { + return $this->code; + } + public function code(): string { return $this->code; diff --git a/src/App/Domain/Magento/Locale/LocaleMapping.php b/src/App/Domain/Magento/Locale/LocaleMapping.php index cf96ba6..2c06ebf 100644 --- a/src/App/Domain/Magento/Locale/LocaleMapping.php +++ b/src/App/Domain/Magento/Locale/LocaleMapping.php @@ -18,6 +18,11 @@ public function __construct(Locale $locale, MagentoStore $store) $this->store = $store; } + public function __toString() + { + return (string) $this->locale; + } + public function code(): string { return $this->locale->code(); diff --git a/src/App/Domain/Magento/Scope/Scope.php b/src/App/Domain/Magento/Scope/Scope.php index eca773a..2c7632e 100644 --- a/src/App/Domain/Magento/Scope/Scope.php +++ b/src/App/Domain/Magento/Scope/Scope.php @@ -22,6 +22,11 @@ public function __construct(string $code, MagentoStore $default, Locale ...$loca $this->locales = $locales; } + public function __toString() + { + return $this->code; + } + public function locales(): iterable { return $this->locales; diff --git a/src/App/Domain/Magento/Scope/ScopeMapping.php b/src/App/Domain/Magento/Scope/ScopeMapping.php index c2ffa2c..cd0d3fb 100644 --- a/src/App/Domain/Magento/Scope/ScopeMapping.php +++ b/src/App/Domain/Magento/Scope/ScopeMapping.php @@ -18,6 +18,11 @@ public function __construct(Scope $scope, MagentoStore $store) $this->store = $store; } + public function __toString() + { + return (string) $this->scope; + } + public function locales(): iterable { return $this->scope->locales(); diff --git a/src/App/Domain/Magento/SqlExportTwigExtension.php b/src/App/Domain/Magento/SqlExportTwigExtension.php index cfc5472..56ac3c3 100644 --- a/src/App/Domain/Magento/SqlExportTwigExtension.php +++ b/src/App/Domain/Magento/SqlExportTwigExtension.php @@ -95,7 +95,7 @@ public function getFunctions() /** @var Field $field */ foreach ($attribute->fields() as $field) { $replacements['%' . $attribute->attribute()->code() . '%'] = strtr( - '{{ alias }}.{{ field }}', + '{{ alias }}.{{ field }}__short', [ '{{ alias }}' => $field->codeGenerator->alias(), '{{ field }}' => $field->codeGenerator->column(), diff --git a/src/App/Infrastructure/AttributeAggregator.php b/src/App/Infrastructure/AttributeAggregator.php index f2b3d65..1bf1ec1 100644 --- a/src/App/Infrastructure/AttributeAggregator.php +++ b/src/App/Infrastructure/AttributeAggregator.php @@ -6,9 +6,12 @@ class AttributeAggregator { - public function __invoke(Family ...$families): iterable + public function __invoke(Family ...$families): array { - return array_unique(iterator_to_array($this->walk(...$families))); + return array_unique( + iterator_to_array($this->walk(...$families)), + SORT_REGULAR + ); } private function walk(Family ...$families): \Iterator diff --git a/src/App/Infrastructure/Console/Command/GoCommand.php b/src/App/Infrastructure/Console/Command/MagentoCommand.php similarity index 89% rename from src/App/Infrastructure/Console/Command/GoCommand.php rename to src/App/Infrastructure/Console/Command/MagentoCommand.php index b4c77a8..426755b 100644 --- a/src/App/Infrastructure/Console/Command/GoCommand.php +++ b/src/App/Infrastructure/Console/Command/MagentoCommand.php @@ -33,9 +33,9 @@ use Twig\Extension\DebugExtension; use Twig\Loader\FilesystemLoader; -class GoCommand extends Command +class MagentoCommand extends Command { - protected static $defaultName = 'go'; + protected static $defaultName = 'magento'; private $logger; @@ -142,56 +142,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $twig->addExtension(new DebugExtension()); $twig->addExtension(new SqlExportTwigExtension()); - (new Fixture\Command\ExtractLocales())( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/locales.yml', 'w'), - $config['locales'] - ); - - $style->writeln('locales ok', SymfonyStyle::OUTPUT_PLAIN); - - (new Fixture\Command\ExtractAttributes($pdo, $twig))( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attributes.csv', 'w'), - $config['attributes'], - array_keys($config['locales']) - ); - - $style->writeln('attributes ok', SymfonyStyle::OUTPUT_PLAIN); - - (new Fixture\Command\ExtractAttributeOptions($pdo, $twig))( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attribute_options.csv', 'w'), - array_keys($config['attributes']), - array_keys($config['locales']), - $config['codes_mapping'] - ); - die; - - $style->writeln('attribute options ok', SymfonyStyle::OUTPUT_PLAIN); - - (new Fixture\Command\ExtractAttributeGroups())( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attribute_groups.csv', 'w'), - $config['groups'], - array_keys($config['locales']) - ); - - $style->writeln('attribute groups ok', SymfonyStyle::OUTPUT_PLAIN); - - (new Fixture\Command\ExtractFamilies())( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/families.csv', 'w'), - $config['families'], - $config['scopes'], - array_keys($config['locales']) - ); - - $style->writeln('families ok', SymfonyStyle::OUTPUT_PLAIN); - - (new Fixture\Command\ExtractFamilyVariants())( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/family_variants.csv', 'w'), - $config['families'], - array_keys($config['locales']) - ); - - $style->writeln('family variants ok', SymfonyStyle::OUTPUT_PLAIN); - /** @var Attribute[] $attributes */ $attributes = (new AttributeDenormalizerFactory())() ->denormalize($config['attributes'], Attribute::class.'[]'); @@ -200,7 +150,7 @@ protected function execute(InputInterface $input, OutputInterface $output) ->denormalize($config['scopes'], Scope::class.'[]'); /** @var Locale[] $locales */ $locales = (new LocaleDenormalizerFactory())() - ->denormalize($config['scopes'], Locale::class.'[]'); + ->denormalize($config['locales'], Locale::class.'[]'); $axisAttributes = array_filter($attributes, function (Attribute $renderer) use ($config) { $code = $renderer->code(); @@ -232,12 +182,71 @@ protected function execute(InputInterface $input, OutputInterface $output) /** @var Attribute[] $axises */ $axises = (new VariantAxisesFactory(...$attributeRenderers))($config); + (new Fixture\Command\ExtractLocales())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/locales.yml', 'x'), + $config['locales'] + ); + + $style->writeln('locales ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractChannels())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/channels.csv', 'x'), + $config['scopes'], + $config['locales'] + ); + + $style->writeln('channels ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractAttributes($pdo, $twig, $this->logger))( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attributes.csv', 'x'), + $attributeRenderers, + $locales, + $config['codes_mapping'] + ); + + $style->writeln('attributes ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractAttributeOptions($pdo, $twig, $this->logger))( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attribute_options.csv', 'x'), + array_keys($config['attributes']), + array_keys($config['locales']), + $config['codes_mapping'] + ); + + $style->writeln('attribute options ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractAttributeGroups())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/attribute_groups.csv', 'x'), + $config['groups'], + array_keys($config['locales']) + ); + + $style->writeln('attribute groups ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractFamilies())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/families.csv', 'x'), + $config['families'], + $config['scopes'], + array_keys($config['locales']) + ); + + $style->writeln('families ok', SymfonyStyle::OUTPUT_PLAIN); + + (new Fixture\Command\ExtractFamilyVariants())( + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/family_variants.csv', 'x'), + $config['families'], + array_keys($config['locales']) + ); + + $style->writeln('family variants ok', SymfonyStyle::OUTPUT_PLAIN); + (new Fixture\Command\ExtractProducts($pdo, $twig, $this->logger))( - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/products.csv', 'w'), - new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/product-models.csv', 'w'), + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/products.csv', 'x'), + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/product_models.csv', 'x'), $attributeRenderers, $families, - $axises + $axises, + $config['codes_mapping'] ); $style->writeln('products and product models ok', SymfonyStyle::OUTPUT_PLAIN); diff --git a/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php b/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php index 90fbc64..7ecf6bf 100644 --- a/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php +++ b/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php @@ -6,9 +6,12 @@ class FamilyVariantAxisAttributeAggregator { - public function __invoke(FamilyVariant ...$variants): iterable + public function __invoke(FamilyVariant ...$variants): array { - return array_unique(iterator_to_array($this->walk(...$variants))); + return array_unique( + iterator_to_array($this->walk(...$variants)), + SORT_REGULAR + ); } private function walk(FamilyVariant ...$variants): \Iterator diff --git a/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php b/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php index 7fb3ce0..547fea3 100644 --- a/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php +++ b/src/App/Infrastructure/Normalizer/Magento/Attribute/AdHocNormalizer.php @@ -9,7 +9,7 @@ class AdHocNormalizer implements DenormalizerInterface { public function denormalize($data, $type, $format = null, array $context = []) { - return new Attribute\AdHoc($data['code']); + return new Attribute\AdHoc($data['code'], $data['group']); } public function supportsDenormalization($data, $type, $format = null) diff --git a/src/App/Infrastructure/Normalizer/Magento/Attribute/AliasedNormalizer.php b/src/App/Infrastructure/Normalizer/Magento/Attribute/AliasedNormalizer.php index e3e31a1..218e9c2 100644 --- a/src/App/Infrastructure/Normalizer/Magento/Attribute/AliasedNormalizer.php +++ b/src/App/Infrastructure/Normalizer/Magento/Attribute/AliasedNormalizer.php @@ -9,7 +9,7 @@ class AliasedNormalizer implements DenormalizerInterface { public function denormalize($data, $type, $format = null, array $context = []) { - return new Attribute\Aliased($data['code'], $data['source']); + return new Attribute\Aliased($data['code'], $data['source'], $data['group']); } public function supportsDenormalization($data, $type, $format = null) diff --git a/src/App/Infrastructure/Normalizer/Magento/Attribute/ExNihiloNormalizer.php b/src/App/Infrastructure/Normalizer/Magento/Attribute/ExNihiloNormalizer.php index 44716d6..9fb69f1 100644 --- a/src/App/Infrastructure/Normalizer/Magento/Attribute/ExNihiloNormalizer.php +++ b/src/App/Infrastructure/Normalizer/Magento/Attribute/ExNihiloNormalizer.php @@ -9,7 +9,7 @@ class ExNihiloNormalizer implements DenormalizerInterface { public function denormalize($data, $type, $format = null, array $context = []) { - return new Attribute\ExNihilo($data['code']); + return new Attribute\ExNihilo($data['code'], $data['group']); } public function supportsDenormalization($data, $type, $format = null) diff --git a/src/App/Resources/templates/extract-attribute-options.sql.twig b/src/App/Resources/templates/extract-attribute-options.sql.twig index 7455cb7..15451f8 100644 --- a/src/App/Resources/templates/extract-attribute-options.sql.twig +++ b/src/App/Resources/templates/extract-attribute-options.sql.twig @@ -24,7 +24,7 @@ FROM ( {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} {%- endfor -%} - {{ renderedAttributes|join(' UNION ') }} + {{ renderedAttributes|join('UNION\n') }} ) AS codes INNER JOIN eav_attribute AS attribute ON codes.code = attribute.attribute_code @@ -37,4 +37,52 @@ INNER JOIN eav_attribute_option_value AS opt_value_default LEFT JOIN eav_attribute_option_value AS opt_value_{{ locale }} ON opt_value_{{ locale }}.option_id=opt.option_id AND opt_value_{{ locale }}.store_id=0 -{% endfor -%} \ No newline at end of file +{% endfor -%} +UNION +SELECT + 'enabled' AS code, + 'status' AS attribute, + {% for locale in locales %} + 'Enabled' AS "label-{{ locale }}", + {% endfor -%} + 1 AS sort_order +UNION +SELECT + 'disabled' AS code, + 'status' AS attribute, + {% for locale in locales %} + 'Disabled' AS "label-{{ locale }}", + {% endfor -%} + 2 AS sort_order +UNION +SELECT + 'not_visible' AS code, + 'visibility' AS attribute, + {% for locale in locales %} + 'Not visible' AS "label-{{ locale }}", + {% endfor -%} + 1 AS sort_order +UNION +SELECT + 'visible_in_catalog' AS code, + 'visibility' AS attribute, + {% for locale in locales %} + 'Visible in catalog' AS "label-{{ locale }}", + {% endfor -%} + 2 AS sort_order +UNION +SELECT + 'visible_in_search' AS code, + 'visibility' AS attribute, + {% for locale in locales %} + 'Visible in search' AS "label-{{ locale }}", + {% endfor -%} + 3 AS sort_order +UNION +SELECT + 'visible_in_catalog_and_search' AS code, + 'visibility' AS attribute, + {% for locale in locales %} + 'Visible in catalog and search' AS "label-{{ locale }}", + {% endfor -%} + 4 AS sort_order diff --git a/src/App/Resources/templates/extract-attributes.sql.twig b/src/App/Resources/templates/extract-attributes.sql.twig index 39d7d82..af84dc9 100644 --- a/src/App/Resources/templates/extract-attributes.sql.twig +++ b/src/App/Resources/templates/extract-attributes.sql.twig @@ -4,8 +4,7 @@ SELECT {% for locale in locales %} attributes.frontend_label AS "label-{{ locale }}", {% endfor -%} - attributes.groups, --- GROUP_CONCAT(', ', ) AS "group", + codes.`group` AS `group`, IF(attributes.is_unique, '1', '0') AS "unique", COALESCE( codes.useable_as_grid_filter, @@ -19,8 +18,8 @@ SELECT END ) AS "useable_as_grid_filter", NULL AS "allowed_extensions", - NULL AS "metric_family", - NULL AS "default_metric_unit", + type_mapping.metric_family AS "metric_family", + type_mapping.default_metric_unit AS "default_metric_unit", NULL AS "reference_data_name", COALESCE( codes.localizable, @@ -31,47 +30,68 @@ SELECT WHEN 'pim_catalog_textarea' THEN '0' WHEN 'pim_catalog_text' THEN '0' WHEN 'pim_catalog_image' THEN '0' - ELSE '1' END + ELSE '1' + END ) AS "localizable", COALESCE( codes.scopable, CASE COALESCE(codes.type, type_mapping.akeneo_type) WHEN 'pim_catalog_identifier' THEN '0' - ELSE '1' END + ELSE '1' + END ) AS "scopable", - CASE codes.code - WHEN 'description' THEN '1' - ELSE '' END - AS "wysiwyg_enabled" + codes.wysiwyg_enabled AS "wysiwyg_enabled", + CASE COALESCE(codes.type, type_mapping.akeneo_type) + WHEN 'pim_catalog_metric' THEN '1' + WHEN 'pim_catalog_number' THEN '0' + ELSE NULL + END AS "negative_allowed", + CASE COALESCE(codes.type, type_mapping.akeneo_type) + WHEN 'pim_catalog_metric' THEN '1' + WHEN 'pim_catalog_number' THEN '1' + ELSE NULL + END AS "decimals_allowed" FROM ( {%- set renderedAttributes = [] -%} -{%- for code, attribute in attributes -%} +{%- for attribute in attributes -%} {% set renderedAttribute %} - SELECT '{{ attribute.code|default(code) }}' AS code, - {% if attribute.type is same as('string') -%} - 'pim_catalog_varchar' - {%- elseif attribute.type is same as('identifier') -%} - 'pim_catalog_identifier' - {%- elseif attribute.type is same as('text') or attribute.type is same as('rich-text') -%} - 'pim_catalog_textarea' - {%- elseif attribute.type is same as('simple-select') or attribute.type is same as('status') or attribute.type is same as('visibility') -%} - 'pim_catalog_simpleselect' - {%- elseif attribute.type is same as('multi-select') -%} - 'pim_catalog_multiselect' - {%- elseif attribute.type is same as('image') -%} - 'pim_catalog_image' - {%- elseif attribute.type is same as('metric') -%} - 'pim_catalog_metric' - {%- elseif attribute.type is same as('datetime') -%} - 'pim_catalog_datetime' - {%- else -%} NULL - {%- endif %} AS type, - {{ attribute.localizable|default(false) ? '1' : '0' }} AS localizable, - {{ attribute.scopable|default(false) ? '1' : '0' }} AS scopable, + SELECT '{{ attribute.attribute.code }}' AS code, + {% if (attribute ~ '') is same as('text') -%} + 'pim_catalog_text' AS type, + {%- elseif (attribute ~ '') is same as('identifier') -%} + 'pim_catalog_identifier' AS type, + {%- elseif (attribute ~ '') is same as('text-area') or (attribute ~ '') is same as('rich-text') -%} + 'pim_catalog_textarea' AS type, + {%- elseif (attribute ~ '') is same as('simple-select') or (attribute ~ '') is same as('status') or (attribute ~ '') is same as('visibility') -%} + 'pim_catalog_simpleselect' AS type, + {%- elseif (attribute ~ '') is same as('multi-select') -%} + 'pim_catalog_multiselect' AS type, + {%- elseif (attribute ~ '') is same as('image') -%} + 'pim_catalog_image' AS type, + {%- elseif (attribute ~ '') is same as('metric') -%} + 'pim_catalog_metric' AS type, + {%- elseif (attribute ~ '') is same as('number') -%} + 'pim_catalog_number' AS type, + {%- elseif (attribute ~ '') is same as('datetime') -%} + 'pim_catalog_date' AS type, + {%- else -%} + NULL AS type, + {%- endif %} + {{ attribute.isLocalized|default(false) ? '1' : '0' }} AS localizable, + {{ attribute.isScoped|default(false) ? '1' : '0' }} AS scopable, + {% if (attribute ~ '') is same as('identifier') -%} + 1 AS useable_as_grid_filter, + {% else %} {{ attribute.usableAsGridFilter|default(false) ? '1' : '0' }} AS useable_as_grid_filter, - {{ attribute.type is same as('rich-text') ? '1' : '0' }} AS wysiwyg_enabled + {% endif %} + {% if (attribute ~ '') is same as('rich-text') or (attribute ~ '') is same as('text-area') -%} + {{ (attribute ~ '') is same as('rich-text') ? '1' : '0' }} AS wysiwyg_enabled, + {%- else -%} + NULL AS wysiwyg_enabled, + {%- endif %} + '{{ attribute.attribute.group }}' AS `group` {% endset %} {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} @@ -79,7 +99,8 @@ FROM ( {{ renderedAttributes|join(' UNION ') }} ) AS codes LEFT JOIN ( - SELECT attribute.*, GROUP_CONCAT(', ', attribute_group.attribute_group_name) AS groups + SELECT + attribute.* FROM eav_attribute AS attribute INNER JOIN eav_entity_attribute AS entity_attribute ON attribute.attribute_id=entity_attribute.attribute_id diff --git a/src/App/Resources/templates/extract-products.sql.twig b/src/App/Resources/templates/extract-products.sql.twig index a94cdb1..4285ca3 100644 --- a/src/App/Resources/templates/extract-products.sql.twig +++ b/src/App/Resources/templates/extract-products.sql.twig @@ -4,13 +4,24 @@ SELECT {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, {% endfor %} {% endfor %} - product.sku AS code, + product.sku AS sku, COALESCE(hierarchy.child, hierarchy.parent) AS parent FROM tmp_sku AS product {% for attribute in attributes %} -LEFT JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias(attribute.attribute) }} + {% if (attribute.attribute ~ '') is same as('ex-nihilo') %} +LEFT JOIN ( + SELECT + {% for field in attribute.fields %} + NULL AS `{{ as_field_column(field) }}`, + {% endfor %} + NULL AS placeholder +) AS {{ as_attribute_alias(attribute.attribute) }} + ON {{ as_attribute_alias(attribute.attribute) }}.placeholder IS NULL + {% else %} +LEFT JOIN {{ as_attribute_table(attribute.attribute) }} AS {{ as_attribute_alias(attribute.attribute) }} ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id + {% endif %} {% endfor %} LEFT JOIN tmp_hierarchy as hierarchy ON hierarchy.variant=product.sku -WHERE product.type_id IN ('simple', 'virtual') +WHERE product.type_id IN ('simple', 'virtual'); diff --git a/src/App/Resources/templates/finalize-product-children.sql.twig b/src/App/Resources/templates/finalize-product-children.sql.twig deleted file mode 100644 index c7de07e..0000000 --- a/src/App/Resources/templates/finalize-product-children.sql.twig +++ /dev/null @@ -1,22 +0,0 @@ -SELECT -{% for attribute in attributes %} - {% for field in attribute.fields %} - {{ as_attribute_table(attribute) }}.`{{ as_field_column(field) }}`, - {% endfor %} -{% endfor %} - hierarchy.child AS code, - hierarchy.parent AS parent, - hierarchy.color AS color, - hierarchy.capacity AS capacity, - 'default_variant' AS family_variant -FROM ( - SELECT hierarchy.parent, hierarchy.child, hierarchy.color, hierarchy.capacity, MIN(product.entity_id) AS entity_id - FROM tmp_hierarchy AS hierarchy - INNER JOIN tmp_sku AS product - ON hierarchy.iv=product.sku - GROUP BY hierarchy.parent, hierarchy.child, hierarchy.color, hierarchy.capacity -) AS hierarchy -{% for attribute in attributes %} -INNER JOIN {{ as_attribute_table(attribute) }} - ON {{ as_attribute_table(attribute) }}.entity_id=product.entity_id -{% endfor %} diff --git a/src/App/Resources/templates/finalize-product-parents.sql.twig b/src/App/Resources/templates/finalize-product-parents.sql.twig deleted file mode 100644 index 2489621..0000000 --- a/src/App/Resources/templates/finalize-product-parents.sql.twig +++ /dev/null @@ -1,21 +0,0 @@ -SELECT -{% for attribute in attributes %} - {% for field in attribute.fields %} - {{ as_attribute_table(attribute) }}.`{{ as_field_column(field) }}`, - {% endfor %} -{% endfor %} - hierarchy.parent AS code, - 'default_variant' AS family_variant -FROM ( - SELECT - hierarchy.parent, - product.entity_id AS entity_id - FROM tmp_hierarchy AS hierarchy - INNER JOIN tmp_sku AS product - ON hierarchy.parent=product.sku - GROUP BY hierarchy.parent -) AS hierarchy -{% for attribute in attributes %} -INNER JOIN {{ as_attribute_table(attribute) }} - ON {{ as_attribute_table(attribute) }}.entity_id=product.entity_id -{% endfor %} diff --git a/src/App/Resources/templates/finalize-products.sql.twig b/src/App/Resources/templates/finalize-products.sql.twig deleted file mode 100644 index 8ab509d..0000000 --- a/src/App/Resources/templates/finalize-products.sql.twig +++ /dev/null @@ -1,17 +0,0 @@ -SELECT -{% for attribute in attributes %} - {% for field in attribute.fields %} - {{ as_attribute_table(attribute) }}.`{{ as_field_column(field) }}`, - {% endfor %} -{% endfor %} - product.sku AS sku, - hierarchy.child AS parent, - 'default' AS family -FROM tmp_sku AS product -INNER JOIN tmp_hierarchy as hierarchy - ON hierarchy.iv=product.sku -{% for attribute in attributes %} -INNER JOIN {{ as_attribute_table(attribute) }} - ON {{ as_attribute_table(attribute) }}.entity_id=product.entity_id -{% endfor %} -WHERE product.type_id IN ({{ types|map(item => "\'#{item}\'")|join(', ')|raw }}) diff --git a/src/App/Resources/templates/initialize.sql.twig b/src/App/Resources/templates/initialize.sql.twig deleted file mode 100644 index 5f65d67..0000000 --- a/src/App/Resources/templates/initialize.sql.twig +++ /dev/null @@ -1,30 +0,0 @@ -CREATE /*TEMPORARY*/ TABLE tmp_hierarchy ( - parent VARCHAR(128) NOT NULL, - child VARCHAR(128) NOT NULL, - variant VARCHAR(128) NOT NULL, - color VARCHAR(128) NULL, - capacity VARCHAR(128) NULL, - PRIMARY KEY (parent, child, variant), - INDEX (parent), - INDEX (child), - INDEX (variant) -) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci -SELECT - product.sku AS parent, - CONCAT(product.sku{%- for axis in axises -%}, ':', {{ as_field_alias(axis) }}.short_code{%- endfor -%}) AS child, -{%- for axis in axises -%} - {{ as_field_alias(axis) }}.code AS `{{ attribute.alias }}`, -{% endfor -%} - child.sku AS variant -FROM catalog_product_entity AS product -INNER JOIN catalog_product_super_link AS link - ON link.parent_id=product.entity_id -INNER JOIN catalog_product_entity AS child - ON child.entity_id=link.product_id -{% for axis in axises -%} -INNER JOIN {{ as_attribute_table(axis) }} AS {{ as_field_alias(axis) }} - ON {{ as_field_alias(axis) }}.entity_id = child.entity_id -{% endfor -%} -WHERE product.sku IS NOT NULL - AND child.sku IS NOT NULL - AND product.type_id IN ('configurable'); \ No newline at end of file diff --git a/src/App/Resources/templates/product/00-sku.sql.twig b/src/App/Resources/templates/product/00-sku.sql.twig index 3d00f69..5ed8218 100644 --- a/src/App/Resources/templates/product/00-sku.sql.twig +++ b/src/App/Resources/templates/product/00-sku.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE tmp_sku ( +CREATE TEMPORARY TABLE tmp_sku ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/01-attribute-options.sql.twig b/src/App/Resources/templates/product/01-attribute-options.sql.twig index 6a5e785..a86c4ca 100644 --- a/src/App/Resources/templates/product/01-attribute-options.sql.twig +++ b/src/App/Resources/templates/product/01-attribute-options.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE tmp_options ( +CREATE TEMPORARY TABLE tmp_options ( option_id INTEGER NOT NULL, code VARCHAR(256) NOT NULL, short_code VARCHAR(255) NOT NULL, @@ -22,7 +22,6 @@ SELECT SUBSTRING({{ codeMapping }}, 1, 99 - LENGTH(codes.code)) AS short_code, attribute.attribute_code AS attribute, opt.sort_order AS sort_order - FROM ( {%- set renderedAttributes = [] -%} @@ -33,7 +32,7 @@ FROM ( {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} {%- endfor -%} - {{ renderedAttributes|join(' UNION ') }} + {{ renderedAttributes|join('UNION\n') }} ) AS codes INNER JOIN eav_attribute AS attribute ON codes.code = attribute.attribute_code diff --git a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig index 8c66233..7bf3afa 100644 --- a/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig +++ b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig @@ -1,7 +1,7 @@ {% for variant in variants %} {% for axis in variant.all %} {% for attribute in axis.attributes %} -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_axis_table(variant, attribute.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_axis_table(variant, attribute.attribute) }} ( entity_id INTEGER NOT NULL, code VARCHAR(256) NOT NULL, short_code VARCHAR(256) NOT NULL, diff --git a/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig index e1b5748..a923f00 100644 --- a/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig +++ b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig @@ -1,8 +1,16 @@ -CREATE /*TEMPORARY*/ TABLE tmp_hierarchy ( - parent VARCHAR(128) NOT NULL, - child VARCHAR(128) NOT NULL, - variant VARCHAR(128) NOT NULL, +CREATE TEMPORARY TABLE tmp_hierarchy ( + parent VARCHAR(255) NOT NULL, + child VARCHAR(255) NOT NULL, + variant VARCHAR(255) NOT NULL, + {% for attribute in attributes %} + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}__short` VARCHAR(255) NULL, + {% endfor -%} + {% endfor %} + family_variant VARCHAR(255) NOT NULL, PRIMARY KEY (parent, child, variant), + INDEX (family_variant), INDEX (parent), INDEX (child), INDEX (variant) @@ -14,7 +22,14 @@ CREATE /*TEMPORARY*/ TABLE tmp_hierarchy ( {% set request %} SELECT product.sku AS parent, + '{{ variant.code }}' AS family_variant, {{ as_product_hierarchy_sku_field('product.sku', variant) }} AS child, + {% for attribute in variant.axis(1).attributes -%} + {% for field in attribute.fields() %} + {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, + {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}__short`, + {% endfor -%} + {% endfor -%} child.sku AS variant FROM catalog_product_entity AS product INNER JOIN catalog_product_super_link AS link diff --git a/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig b/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig index 53234d5..20b5c79 100644 --- a/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig +++ b/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE tmp_parent_models +CREATE TEMPORARY TABLE tmp_parent_models SELECT {% for attribute in attributes %} {% for field in attribute.fields %} @@ -6,6 +6,7 @@ SELECT {% endfor %} {% endfor %} hierarchy.parent AS code, + hierarchy.family_variant AS family_variant, NULL AS parent FROM tmp_sku AS product {% for attribute in attributes %} @@ -13,21 +14,22 @@ LEFT JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id {% endfor %} INNER JOIN ( - SELECT DISTINCT hierarchy.parent + SELECT DISTINCT hierarchy.parent, hierarchy.family_variant FROM tmp_hierarchy as hierarchy - GROUP BY hierarchy.parent + GROUP BY hierarchy.parent, hierarchy.family_variant ) AS hierarchy ON hierarchy.parent=product.sku WHERE product.type_id IN ('configurable'); -CREATE /*TEMPORARY*/ TABLE tmp_child_models +CREATE TEMPORARY TABLE tmp_child_models SELECT {% for attribute in attributes %} {% for field in attribute.fields %} - {{ as_attribute_alias(attribute.attribute) }}.`{{ as_field_column(field) }}`, + hierarchy.`{{ as_field_column(field) }}`, {% endfor %} {% endfor %} hierarchy.child AS code, + hierarchy.family_variant AS family_variant, hierarchy.parent AS parent FROM tmp_sku AS product {% for attribute in attributes %} @@ -35,9 +37,24 @@ LEFT JOIN {{ as_attribute_table(attribute.attribute) }} as {{ as_attribute_alias ON {{ as_attribute_alias(attribute.attribute) }}.entity_id=product.entity_id {% endfor %} INNER JOIN ( - SELECT MAX(hierarchy.parent) AS parent, hierarchy.child + SELECT + {% for attribute in attributes %} + {% for field in attribute.fields %} + hierarchy.`{{ as_field_column(field) }}`, + {% endfor %} + {% endfor %} + MAX(hierarchy.parent) AS parent, + MAX(hierarchy.family_variant) AS family_variant, + hierarchy.child FROM tmp_hierarchy as hierarchy - GROUP BY hierarchy.child + GROUP BY + {% for attribute in attributes %} + {% for field in attribute.fields %} + hierarchy.`{{ as_field_column(field) }}`, + {% endfor %} + {% endfor %} + hierarchy.child, + hierarchy.family_variant ) AS hierarchy ON hierarchy.parent=product.sku WHERE product.type_id IN ('configurable'); diff --git a/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig index 69a21db..0e64f69 100644 --- a/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig index 6d52a20..899e7a2 100644 --- a/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-image.sql.twig b/src/App/Resources/templates/product/attribute/extract-image.sql.twig index 35d1d64..68c3fee 100644 --- a/src/App/Resources/templates/product/attribute/extract-image.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, diff --git a/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig index 3d0ea35..f6a2156 100644 --- a/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -15,7 +15,7 @@ SELECT product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 LEFT JOIN catalog_product_entity_decimal AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig index fabab75..9e16243 100644 --- a/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -15,7 +15,7 @@ SELECT product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 LEFT JOIN catalog_product_entity_text AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig b/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig index c82b6d3..0324ed5 100644 --- a/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig @@ -1,5 +1,5 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -16,7 +16,7 @@ SELECT product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_text AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable-localizable.sql.twig similarity index 66% rename from src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-simple-select-scopable-localizable.sql.twig index d3e5a83..f077aa0 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable-localizable.sql.twig @@ -1,13 +1,19 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}__short` VARCHAR(255) NULL, + {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {{ as_default_alias() }}_option.code AS code, + {% for field in renderer.fields -%} + {{ as_default_alias() }}_option.code AS `{{ as_field_column(field) }}`, + {{ as_default_alias() }}_option.short_code AS `{{ as_field_column(field) }}__short`, + {% endfor -%} product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -24,19 +30,24 @@ LEFT JOIN ( ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; {% for field in renderer.fields %} -CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}__short` VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT COALESCE( {{ as_field_alias(field) }}_option.code, - {{ as_default_alias() }}.code - ) AS `code`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}_option.short_code, + {{ as_default_alias() }}.`{{ as_field_column(field) }}__short` + ) AS `{{ as_field_column(field) }}__short`, product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -55,7 +66,7 @@ LEFT JOIN ( ON {{ as_field_alias(field) }}.value={{ as_field_alias(field) }}_option.option_id; {% endfor %} -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -67,7 +78,14 @@ CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}.`{{ as_field_column(field) }}`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}.`{{ as_field_column(field) }}__short`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}__short` + ) AS `{{ as_field_column(field) }}__short`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable.sql.twig similarity index 66% rename from src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-simple-select-scopable.sql.twig index f882ff6..7ad9f9b 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect-scopable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable.sql.twig @@ -1,13 +1,19 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}__short` VARCHAR(255) NULL, + {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT - {{ as_default_alias() }}_option.code AS code, + {% for field in renderer.fields -%} + {{ as_default_alias() }}_option.code AS `{{ as_field_column(field) }}`, + {{ as_default_alias() }}_option.short_code AS `{{ as_field_column(field) }}__short`, + {% endfor -%} product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -24,19 +30,24 @@ LEFT JOIN ( ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; {% for field in renderer.fields %} -CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}__short` VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT COALESCE( {{ as_field_alias(field) }}_option.code, - {{ as_default_alias() }}.code - ) AS `code`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}_option.short_code, + {{ as_default_alias() }}.`{{ as_field_column(field) }}__short` + ) AS `{{ as_field_column(field) }}__short`, product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -55,7 +66,7 @@ LEFT JOIN ( ON {{ as_field_alias(field) }}.value={{ as_field_alias(field) }}_option.option_id; {% endfor %} -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -67,7 +78,14 @@ CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}.`{{ as_field_column(field) }}`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}.`{{ as_field_column(field) }}__short`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}__short` + ) AS `{{ as_field_column(field) }}__short`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig b/src/App/Resources/templates/product/attribute/extract-simple-select.sql.twig similarity index 77% rename from src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig rename to src/App/Resources/templates/product/attribute/extract-simple-select.sql.twig index cab0c0c..3b7993d 100644 --- a/src/App/Resources/templates/product/attribute/extract-simpleselect.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-simple-select.sql.twig @@ -1,16 +1,18 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, {%- for field in renderer.fields -%} `{{ as_field_column(field) }}` VARCHAR(255) NULL, + `{{ as_field_column(field) }}__short` VARCHAR(255) NULL, {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - {{ as_default_alias() }}_option.code AS `{{ as_field_alias(field) }}`, + {{ as_default_alias() }}_option.code AS `{{ as_field_column(field) }}`, + {{ as_default_alias() }}_option.short_code AS `{{ as_field_column(field) }}__short`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig index 8ba8cad..d0d5b4a 100644 --- a/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig @@ -1,17 +1,21 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT + {% for field in renderer.fields -%} CASE {{ as_default_alias() }}.value WHEN 1 THEN 'enabled' WHEN 2 THEN 'disabled' ELSE 'disabled' - END AS code, + END AS `{{ as_field_column(field) }}`, + {% endfor -%} product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -22,11 +26,11 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} AND {{ as_default_alias() }}.store_id=0; {% for field in renderer.fields %} -CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci @@ -37,8 +41,8 @@ SELECT WHEN 2 THEN 'disabled' ELSE 'disabled' END, - {{ as_default_alias() }}.code - ) AS `code`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -51,7 +55,7 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; {% endfor %} -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -63,7 +67,10 @@ CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}.`{{ as_field_column(field) }}`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product diff --git a/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig index fabab75..9e16243 100644 --- a/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text-area-scopable-localizable.sql.twig @@ -1,4 +1,4 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -15,7 +15,7 @@ SELECT product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 LEFT JOIN catalog_product_entity_text AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig index c82b6d3..0324ed5 100644 --- a/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text-area.sql.twig @@ -1,5 +1,5 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -16,7 +16,7 @@ SELECT product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 INNER JOIN catalog_product_entity_text AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig index 6b43fa1..31add71 100644 --- a/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig @@ -1,5 +1,5 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -16,7 +16,7 @@ SELECT product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute - ON attribute.attribute_code='{{ attribute.codeInSource }}' AND attribute.entity_type_id=4 + ON attribute.attribute_code='{{ renderer.attribute.code }}' AND attribute.entity_type_id=4 LEFT JOIN catalog_product_entity_varchar AS {{ as_default_alias() }} ON {{ as_default_alias() }}.entity_id=product.entity_id AND {{ as_default_alias() }}.attribute_id=attribute.attribute_id diff --git a/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig index f02b75f..5220177 100644 --- a/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig @@ -1,19 +1,23 @@ -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_default_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_default_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + {%- for field in renderer.fields -%} + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + {% endfor -%} PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT + {% for field in renderer.fields -%} CASE {{ as_default_alias() }}.value WHEN 1 THEN 'not_visible' WHEN 2 THEN 'visible_in_catalog' WHEN 3 THEN 'visible_in_search' WHEN 4 THEN 'visible_in_catalog_and_search' ELSE 'not_visible' - END AS code, + END AS `{{ as_field_column(field) }}`, + {% endfor -%} product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -24,11 +28,11 @@ INNER JOIN catalog_product_entity_int AS {{ as_default_alias() }} AND {{ as_default_alias() }}.store_id=0; {% for field in renderer.fields %} -CREATE /*TEMPORARY*/ TABLE {{ as_field_table(field) }} ( +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, - code VARCHAR(255) NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, PRIMARY KEY (sku), UNIQUE INDEX (entity_id) ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci @@ -40,8 +44,8 @@ SELECT WHEN 3 THEN 'visible_in_search' WHEN 4 THEN 'visible_in_catalog_and_search' END, - {{ as_default_alias() }}.code - ) AS `code`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, product.* FROM tmp_sku AS product INNER JOIN eav_attribute AS attribute @@ -54,7 +58,7 @@ LEFT JOIN catalog_product_entity_int AS {{ as_field_alias(field) }} AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; {% endfor %} -CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( +CREATE TEMPORARY TABLE {{ as_attribute_table(renderer.attribute) }} ( sku VARCHAR(128) NOT NULL, entity_id INTEGER NOT NULL, type_id VARCHAR(32) NOT NULL, @@ -66,7 +70,10 @@ CREATE /*TEMPORARY*/ TABLE {{ as_attribute_table(renderer.attribute) }} ( ) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci SELECT {% for field in renderer.fields -%} - COALESCE({{ as_field_alias(field) }}.code, {{ as_default_alias() }}.code) AS `{{ as_field_column(field) }}`, + COALESCE( + {{ as_field_alias(field) }}.`{{ as_field_column(field) }}`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, {% endfor -%} product.* FROM tmp_sku AS product From a9cd2445dad0682ecef5a9670c369c86745ff18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Mon, 21 Oct 2019 17:20:43 +0200 Subject: [PATCH 6/7] Updated the readme and Docker stack --- README.md | 204 ++++++++++++++++++++++++++++++++++++++++++++- bin/console | 2 +- composer.json | 2 +- docker-compose.yml | 49 +++++++++-- 4 files changed, 245 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6ef8d6b..4c9d52a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Akeneo Fixtures Generation Toolbox This toolbox helps generating CSV fixtures consumed by Akeneo's InstallerBundle from Magento 1.9CE or 1.14EE catalog data. +This package is here to help you import your Magento catalog into a fresh new Akeneo instance. It is not aimed at synchronising on a daily basis Akeneo and Magento together. + +Be aware that all your existing Akeneo product data will be reset by this tool, and lost. Supported attribute types --- @@ -19,10 +22,207 @@ Supported attribute types | Select | Simple select | ✅ | ❌ | ✅ | ✅ | | Number | Number | ❌ | ❌ | ❌ | ❌ | | Price | Price | ❌ | ❌ | ❌ | ❌ | -| Status | Simple select | ✅ | ❌ | ❌ | ❌ | +| Status | Simple select | ❌ | ❌ | ❌ | ✅ | | - | Ref. multi select | ❌ | ❌ | ❌ | ❌ | | - | Ref. simple select | ❌ | ❌ | ❌ | ❌ | | Text | Text area | ✅ | ❌ | ❌ | ✅ | | Varchar | Text | ✅ | ❌ | ❌ | ✅ | -| Visibility | Simple select | ✅ | ❌ | ❌ | ❌ | +| Visibility | Simple select | ❌ | ❌ | ❌ | ✅ | | YesNo | Yes No | ❌ | ❌ | ❌ | ❌ | + +How to start +--- + +You will primarily need to install the tool in your environment: + +`composer create-project kiboko/bisous` + +This command will create a folder named `bisous/`, just go into this directory: + +`cd bisous` + +Once you are there, you will need to create an `.env` file, with the following environment variables properly set: + +* `COMPOSER_AUTH={"github-oauth":{"github.com":"0123456789abcdef0123456789abcdef01234567"}}`, you will need to change the key by your github access token. +* `COMPOSER_PROCESS_TIMEOUT=600`, some times you will need to set this timeout value to a higher value, depending on your network connection speed +* `APP_DSN=mysql:host=mysql;dbname=magento`, see [PDO MySQL Data Source Name](https://www.php.net/manual/en/ref.pdo-mysql.connection.php) +* `APP_USERNAME=root`, the MySQL user name +* `APP_PASSWORD=password`, the MySQL password + +You will then need to create a `catalog.yml` file in this directory, describing your catalog structure. + +Run the tool +--- + +Once properly installed, run `bin/console magento /src/InstallerBundle/Resources/fixtures/default`. + +This command will create fixtures file required by Akeneo, with your Magento catalog data and structure. + +The `catalog.yml` file +--- + +The `catalog.yml` file has a root node named `catalog:`, and 5 sub-nodes described in the following paragraphs: + +### The `attributes:` section + +This section is useful for describing your attribute list. It is an array of configuration fields, with the following fields: + +* `code` (string): Your attribute code, as seen in Akeneo +* `type` (string): The attribute's type (valid values are `identifier`, `text`, `text-area`, `rich-text`, `status`, `visibility`, `simple-select`, `datetime`, `metric`, `image`) +* `strategy` (string): The import strategy, following the next possible values: + * `ad-hoc`: the attribute will be created in Akeneo in the same way it was created in Magento + * `aliased`: the attrib ute will be created in Akeneo with another code than the one existing in Magento + * `ex-nihilo`: the attribute will be created in Akeneo without taking into account any attribute present in Magento +* `group` (string): the attribute group in which the attribute will be assigned in Akeneo +* `source` (string) (for strategy `aliased` only): the attribute code in Magento +* `scoped` (bool): to specify it the attribute is scopable (only applies to types `text`, `text-area`, `rich-text`, `status`, `visibility`, `simple-select`, `datetime`, `metric`, `image`, will produce an error in Akeneo if used on a variant axis attribute) +* `localised` (bool): to specify it the attribute is localizable (only applies to types `text`, `text-area`, `rich-text`, `status`, `visibility`, `simple-select`, `datetime`, `metric`, `image`, will produce an error in Akeneo if used on a variant axis attribute) + +Example: + +```yaml +catalog: + attributes: + - code: sku + type: identifier + strategy: ad-hoc + group: general + - code: name + type: text + strategy: ad-hoc + group: marketing + scoped: true + localised: true + - code: variation_name + type: text + strategy: ex-nihilo + group: marketing + scoped: true + localised: true +``` + +### The `groups:` section + +This section describes the attribute groups that will be created in Akeneo. + +Example: + +```yaml +catalog: + groups: + - code: general + label: + fr_FR: Général + en_GB: General + - code: marketing + label: + fr_FR: Général + en_GB: General +``` + +### The `families:` section + +Example: + +```yaml +catalog: + families: + - code: jeans + attributes: [ name, description, short_description, meta_title, meta_description, status, visibility, image, variation_name, variation_image, variation_description, news_to_date, news_from_date, length, width, color, size ] + label: name + image: image + requirements: + - scope: america + attributes: [ name, description, image ] + - scope: europe + attributes: [ name, description, image ] + - scope: france + attributes: [ name, description, image ] + - scope: japan + attributes: [ name, description, image ] + - scope: china + attributes: [ name, description, image ] + - scope: asia + attributes: [ name, description, image ] + - scope: amazon + attributes: [ name, description, image ] + - scope: ebay + attributes: [ name, description, image ] + variations: + - code: jeans_by_size_and_color + skuPattern: '{{ parent }}:{{ length }}:{{ width }}' + level-1: + axis: [ length, width ] + attributes: [ variation_name, variation_image, variation_description, news_from_date, news_to_date ] + level-2: + axis: [ color ] + attributes: [ sku, status, visibility ] + - code: jeans_by_size + level-1: + axis: [ size ] + attributes: [ sku, status, visibility, variation_name, variation_image, variation_description, news_from_date, news_to_date ] + +``` + +### The `locales:` section + +Example: + +```yaml +catalog: + locales: + - code: fr_FR + currency: EUR + store: 15 + - code: en_GB + currency: GBP + store: 21 +``` + +### The `scopes:` section + +Example: + +```yaml +catalog: + scopes: + - code: europe + store: 1 + locales: + - code: fr_FR + store: 1 + - code: de_DE + store: 4 + - code: es_ES + store: 3 + - code: it_IT + store: 2 + - code: america + store: 5 + locales: + - code: en_US + store: 5 + - code: en_CA + store: 8 + - code: fr_CA + store: 6 +``` + +### The `codes-mapping:` section + +Example: + +```yaml +catalog: + codes-mapping: + - from: '"' + to: 'inches' + - from: 'â' + to: 'a' + - from: 'é' + to: 'e' + - from: 'è' + to: 'e' + - from: '/' + to: '_' +``` \ No newline at end of file diff --git a/bin/console b/bin/console index b3c2877..d6ff312 100755 --- a/bin/console +++ b/bin/console @@ -61,7 +61,7 @@ $factory = new class($output) { $application = new Application('Bisous', '1.0.0'); $application->setCommandLoader(new FactoryCommandLoader([ 'init' => $factory(\App\Infrastructure\Console\Command\InitializeCommand::class), - 'go' => $factory(\App\Infrastructure\Console\Command\GoCommand::class), + 'magento' => $factory(\App\Infrastructure\Console\Command\MagentoCommand::class), 'test' => $factory(\App\Infrastructure\Console\Command\TestCommand::class), // 'categories' => function(){}, 'products' => $factory(\App\Infrastructure\Console\Command\ProductCommand::class), diff --git a/composer.json b/composer.json index 697b38e..72afa35 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "kiboko/akeneo-initialize-from-magento", + "name": "kiboko/bisous", "type": "project", "require": { "php": "^7.2", diff --git a/docker-compose.yml b/docker-compose.yml index 5d07a31..f60a4d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,51 @@ version: '2' services: - cli: + blackfire: + image: blackfire/blackfire + environment: + - BLACKFIRE_SERVER_ID + - BLACKFIRE_SERVER_TOKEN + + sh: build: - context: .docker/php@7.2 + context: ./.docker/php@7.2/cli + user: docker:docker volumes: - - ./.docker/php@7.2/config/memory.ini:/usr/local/etc/php/conf.d/memory.ini:ro - - ./:/app - composer: - extends: - service: cli + - $HOME/.ssh:/opt/docker/.ssh:cached + - ./:/var/www/html + - composer:/opt/docker/.composer/:cached + environment: + - COMPOSER_AUTH + - COMPOSER_PROCESS_TIMEOUT + command: [ "sleep", "31536000" ] + restart: "always" + + sh-xdebug: + build: + context: ./.docker/php@7.2/cli + user: docker:docker volumes: + - $HOME/.ssh:/opt/docker/.ssh:cached + - ./:/var/www/html - composer:/opt/docker/.composer/:cached - entrypoint: [ 'composer' ] + environment: + - COMPOSER_AUTH + - COMPOSER_PROCESS_TIMEOUT + command: [ "sleep", "31536000" ] + restart: "always" + + mysql: + image: 'mysql:5.7' + environment: + MYSQL_ROOT_PASSWORD: 'password' + MYSQL_USER: 'magento' + MYSQL_PASSWORD: 'password' + MYSQL_DATABASE: 'magento' + ports: + - 13006:3306 + volumes: + - ./dump-recommerce-20190506.sql:/docker-entrypoint-initdb.d/dump-recommerce-20190506.sql:ro volumes: composer: From 6320353e5795a34f99b9debcac89b452ad750df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Mon, 21 Oct 2019 21:24:45 +0200 Subject: [PATCH 7/7] Integrated phar builds --- .docker/php@7.2/cli/Dockerfile | 5 + .gitignore | 3 + bin/console | 21 +- bin/sql-to-csv | 44 ----- box.json.dist | 20 ++ composer.json | 1 + composer.lock | 179 +++++++++++++++++- config/bootstrap.php | 4 +- .../Console/Command/SelfUpdateCommand.php | 70 +++++++ symfony.lock | 9 + 10 files changed, 301 insertions(+), 55 deletions(-) delete mode 100755 bin/sql-to-csv create mode 100644 box.json.dist create mode 100644 src/App/Infrastructure/Console/Command/SelfUpdateCommand.php diff --git a/.docker/php@7.2/cli/Dockerfile b/.docker/php@7.2/cli/Dockerfile index 535f8d5..d843d31 100644 --- a/.docker/php@7.2/cli/Dockerfile +++ b/.docker/php@7.2/cli/Dockerfile @@ -62,4 +62,9 @@ RUN apk del \ sqlite-libs \ && rm -rf /tmp/* /var/cache/apk/* +RUN pwd && curl -LSs https://box-project.github.io/box2/installer.php | php \ + && mv box.phar /usr/local/bin/box \ + && chmod 0755 /usr/local/bin/box \ + && echo "phar.readonly=0" >> /usr/local/etc/php/conf.d/phar.ini + WORKDIR /var/www/html diff --git a/.gitignore b/.gitignore index f10862a..88f9762 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /.env +/bin/bisous.phar +/bisous-phar-private.pem +/bisous-phar-private-nopassphrase.pem diff --git a/bin/console b/bin/console index d6ff312..84b122c 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,9 @@ #!/usr/bin/env php logger = new ConsoleLogger($output); } - public function __invoke(string $className) + public function __invoke(Application $application, string $className) { - return function () use ($className) { - return new $className(null, $this->logger); + return function () use ($className, $application) { + /** @var Command $command */ + $command = new $className(null, $this->logger); + $command->setApplication($application); + return $command; }; } }; $application = new Application('Bisous', '1.0.0'); $application->setCommandLoader(new FactoryCommandLoader([ - 'init' => $factory(\App\Infrastructure\Console\Command\InitializeCommand::class), - 'magento' => $factory(\App\Infrastructure\Console\Command\MagentoCommand::class), - 'test' => $factory(\App\Infrastructure\Console\Command\TestCommand::class), -// 'categories' => function(){}, - 'products' => $factory(\App\Infrastructure\Console\Command\ProductCommand::class), + 'init' => $factory($application, \App\Infrastructure\Console\Command\InitializeCommand::class), + 'magento' => $factory($application, \App\Infrastructure\Console\Command\MagentoCommand::class), + 'self-update' => $factory($application, \App\Infrastructure\Console\Command\SelfUpdateCommand::class), + 'test' => $factory($application, \App\Infrastructure\Console\Command\TestCommand::class), + 'products' => $factory($application, \App\Infrastructure\Console\Command\ProductCommand::class), ])); $application->run($input, $output); diff --git a/bin/sql-to-csv b/bin/sql-to-csv deleted file mode 100755 index 04a8b90..0000000 --- a/bin/sql-to-csv +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env php - \PDO::ERRMODE_EXCEPTION, - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - ]); - - for ($i = 4; $i < $argc; ++$i) { - $requests = array_filter(explode(';', file_get_contents($argv[$i])), function($request) { - return !empty(trim($request)); - }); - - foreach ($requests as $request) { - $pdo->exec($request); - } - } - - $statement = $pdo->prepare($request = file_get_contents('php://stdin')); - $statement->setFetchMode(PDO::FETCH_ASSOC); - $statement->execute(); -} catch (\PDOException $e) { - file_put_contents('php://stderr', var_export($request, true) . PHP_EOL); - file_put_contents('php://stderr', var_export($e->errorInfo, true) . PHP_EOL); - file_put_contents('php://stderr', $e); - return -1; -} catch (\Error $e) { - file_put_contents('php://stderr', $e); - return -1; -} - -$file = new SplFileObject('php://stdout', 'w'); -$file->setCsvControl(';'); - -$i = 0; -foreach ($statement as $index => $row) { - if ($index === 0) { - $file->fputcsv(array_keys($row)); - } - - $file->fputcsv($row); - (++$i % 100) || ob_flush(); -} diff --git a/box.json.dist b/box.json.dist new file mode 100644 index 0000000..b114c95 --- /dev/null +++ b/box.json.dist @@ -0,0 +1,20 @@ +{ + "chmod": "0755", + "main": "bin/console", + "output": "bin/bisous.phar", + "directories": ["src"], + "finder": [ + { + "name": "*.php", + "in": "config" + }, + { + "name": "*.php", + "exclude": ["test", "tests"], + "in": "vendor" + } + ], + "algorithm": "OPENSSL", + "key": "bisous-phar-private-nopassphrase.pem", + "stub": true +} \ No newline at end of file diff --git a/composer.json b/composer.json index 72afa35..67070b5 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "ext-PDO": "^7.2", "ext-ctype": "*", "ext-iconv": "*", + "padraic/phar-updater": "^1.0", "psr/log": "^1.1", "symfony/cache": "4.3.*", "symfony/config": "4.3.*", diff --git a/composer.lock b/composer.lock index 1cd2e40..f6d93d9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,185 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cc6e0c02b8535ac4782bab309e71b5e2", + "content-hash": "e2d49e6d682ffd9e82a7fc44f6d6187c", "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2019-08-30T08:44:50+00:00" + }, + { + "name": "padraic/humbug_get_contents", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/humbug/file_get_contents.git", + "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/humbug/file_get_contents/zipball/dcb086060c9dd6b2f51d8f7a895500307110b7a7", + "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "ext-openssl": "*", + "php": "^5.3 || ^7.0 || ^7.1 || ^7.2" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.1", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false + }, + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Humbug\\": "src/" + }, + "files": [ + "src/function.php", + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Secure wrapper for accessing HTTPS resources with file_get_contents for PHP 5.3+", + "homepage": "https://github.com/padraic/file_get_contents", + "keywords": [ + "download", + "file_get_contents", + "http", + "https", + "ssl", + "tls" + ], + "time": "2018-02-12T18:47:17+00:00" + }, + { + "name": "padraic/phar-updater", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/humbug/phar-updater.git", + "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/humbug/phar-updater/zipball/d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", + "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", + "shasum": "" + }, + "require": { + "padraic/humbug_get_contents": "^1.0", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Humbug\\SelfUpdate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + } + ], + "description": "A thing to make PHAR self-updating easy and secure.", + "keywords": [ + "humbug", + "phar", + "self-update", + "update" + ], + "time": "2018-03-30T12:52:15+00:00" + }, { "name": "psr/cache", "version": "1.0.1", diff --git a/config/bootstrap.php b/config/bootstrap.php index 62e5f76..3f3397b 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -6,7 +6,7 @@ // Load cached env vars if the .env.local.php file exists // Run "composer dump-env prod" to create it (requires symfony/flex >=1.2) -if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { +if (is_array($env = @include getcwd().'/.env.local.php')) { foreach ($env as $k => $v) { $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); } @@ -14,7 +14,7 @@ throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); } else { // load all the .env files - (new Dotenv(false))->loadEnv(dirname(__DIR__) . '/.env'); + (new Dotenv(false))->loadEnv(getcwd() . '/.env'); } $_SERVER += $_ENV; diff --git a/src/App/Infrastructure/Console/Command/SelfUpdateCommand.php b/src/App/Infrastructure/Console/Command/SelfUpdateCommand.php new file mode 100644 index 0000000..c29c507 --- /dev/null +++ b/src/App/Infrastructure/Console/Command/SelfUpdateCommand.php @@ -0,0 +1,70 @@ +setDescription(sprintf( + 'Update %s to most recent stable build.', + $this->getLocalPharName() + )); + } + + protected function initialize(InputInterface $input, OutputInterface $output) + { + $this->updater = new Updater('bin/bisous.phar'); + $this->updater->setStrategy(Updater::STRATEGY_GITHUB); + $this->updater->getStrategy()->setPackageName('kiboko/bisous'); + $this->updater->getStrategy()->setPharName('bisous.phar'); + $this->updater->getStrategy()->setCurrentLocalVersion( + $this->getApplication()->getVersion() + ); + } + + /** + * @inheritdoc + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $result = $this->updater->update(); + + if ($result) { + $io->success( + sprintf( + 'Your PHAR has been updated from "%s" to "%s".', + $this->updater->getOldVersion(), + $this->updater->getNewVersion() + ) + ); + } else { + $io->success('Your PHAR is already up to date.'); + } + + return 0; + } + + private function getLocalPharName(): string + { + return basename(PHAR::running()); + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 7353b28..f2e8d22 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,4 +1,13 @@ { + "composer/ca-bundle": { + "version": "1.2.4" + }, + "padraic/humbug_get_contents": { + "version": "1.1.2" + }, + "padraic/phar-updater": { + "version": "v1.0.6" + }, "psr/cache": { "version": "1.0.1" },