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..d843d31 --- /dev/null +++ b/.docker/php@7.2/cli/Dockerfile @@ -0,0 +1,70 @@ +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/* + +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/.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 new file mode 100644 index 0000000..88f9762 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.env +/bin/bisous.phar +/bisous-phar-private.pem +/bisous-phar-private-nopassphrase.pem diff --git a/README.md b/README.md index 90152ca..4c9d52a 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,228 @@ Akeneo Fixtures Generation Toolbox ================================== -This classes and templates toolbox helps generating CSV fixtues from Magento 1.9CE or 1.14EE catalog. - -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); +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 +--- + +| 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 | ❌ | ❌ | ❌ | ❌ | + +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 new file mode 100755 index 0000000..84b122c --- /dev/null +++ b/bin/console @@ -0,0 +1,74 @@ +#!/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(Application $application, string $className) + { + 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($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 4203311..67070b5 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,69 @@ { - "name": "kiboko/akeneo-initialize-from-magento", + "name": "kiboko/bisous", "type": "project", "require": { "php": "^7.2", + "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.*", + "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.*", "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..f6d93d9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,40 +4,794 @@ "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": "e2d49e6d682ffd9e82a7fc44f6d6187c", "packages": [ { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "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", + "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/filesystem.git", + "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b9896d034463ad6fd2bf17e2bf9418caecd6313d", + "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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 Filesystem Component", + "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/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "url": "https://github.com/symfony/finder.git", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/finder/zipball/86c1c929f0a4b24812e1eb109262fc3372c8e9f2", + "reference": "86c1c929f0a4b24812e1eb109262fc3372c8e9f2", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Component\\Finder\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -46,23 +800,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 Finder Component", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "time": "2019-08-14T12:26:46+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 +920,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 +1310,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..3f3397b --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,23 @@ +=1.2) +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); + } +} 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(getcwd() . '/.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/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: 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..7fca365 --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractAttributeOptions.php @@ -0,0 +1,54 @@ +pdo = $pdo; + $this->twig = $twig; + $this->logger = $logger ?? new NullLogger(); + } + + 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, $this->logger)) + ( + $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..9d04f14 --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractAttributes.php @@ -0,0 +1,52 @@ +pdo = $pdo; + $this->twig = $twig; + $this->logger = $logger ?? new NullLogger(); + } + + 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, $this->logger)) + ( + $view->render([ + 'attributes' => $attributes, + 'locales' => $locales, + 'mapping' => $mapping, + ]), + $output + ); + } +} \ No newline at end of file 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/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..a53b2dd --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractFamilyVariants.php @@ -0,0 +1,58 @@ + $family) { + foreach ($family['variations'] as $variation) { + yield array_merge( + [ + '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 ($variation) { + return [ + 'label-' . $locale => sprintf( + '%s [%s] (%s)', + $variation['code'], + implode(', ', array_merge($variation['level_1']['axis'] ?? [], $variation['level_2']['axis'] ?? [])), + $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/ExtractProducts.php b/src/App/Domain/Fixture/Command/ExtractProducts.php new file mode 100644 index 0000000..820b41d --- /dev/null +++ b/src/App/Domain/Fixture/Command/ExtractProducts.php @@ -0,0 +1,166 @@ +pdo = $pdo; + $this->twig = $twig; + $this->logger = $logger ?? new NullLogger(); + } + + /** + * @param AttributeRenderer[] $attributes + * @param Family[] $families + * @param FamilyVariant[] $familyVariants + */ + public function __invoke( + \SplFileObject $productOutput, + \SplFileObject $productModelOutput, + array $attributes, + array $families, + array $familyVariants, + array $mapping + ): void { + $bus = new CommandBus(); + try { + $bus->add( + new TwigCommand( + $this->twig, + 'product/00-sku.sql.twig', + [], + $this->logger + ), + new TwigCommand( + $this->twig, + 'product/01-attribute-options.sql.twig', + [ + 'mapping' => $mapping, + 'attributes' => array_merge( + (new AttributeAggregator())(...$families), + (new FamilyVariantAxisAttributeAggregator())(...$familyVariants) + ), + ], + $this->logger + ), + new TwigCommand( + $this->twig, + 'product/02-prefill-axis-attributes.sql.twig', + [ + 'mapping' => $mapping, + 'variants' => $familyVariants, + ], + $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), + ] + )); + } + + if ($attribute->attribute() instanceof ExNihilo) { + continue; + } + + try { + $bus->add( + new TwigCommand( + $this->twig, + $attribute->template(), + [ + 'renderer' => $attribute, + ], + $this->logger + ) + ); + } catch (\RuntimeException $e) { + continue; + } + } + + $bus->add( + new TwigCommand( + $this->twig, + 'product/03-generate-intermediate-product-models.sql.twig', + [ + 'variants' => $familyVariants, + 'attributes' => (new FamilyVariantAxisAttributeAggregator())(...$familyVariants), + ], + $this->logger + ) + ); + + $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'); + + (new SqlToCsv($this->pdo, $this->logger)) + ( + $view->render([ + '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 FamilyVariantAxisAttributeAggregator())(...$familyVariants), + 'variants' => $familyVariants, + ]), + $productModelOutput + ); + } catch (\RuntimeException|LoaderError|RuntimeError|SyntaxError|FatalThrowableError $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..96845c2 --- /dev/null +++ b/src/App/Domain/Fixture/SqlToCsv.php @@ -0,0 +1,39 @@ +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(); + $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/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; + $this->group = $group; + } + + public function __toString() + { + return 'ad-hoc'; + } + + public function code(): string + { + return $this->code; + } + + public function source(): string + { + return $this->code; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/Attribute/Aliased.php b/src/App/Domain/Magento/Attribute/Aliased.php new file mode 100644 index 0000000..d9bf1d0 --- /dev/null +++ b/src/App/Domain/Magento/Attribute/Aliased.php @@ -0,0 +1,37 @@ +code = $code; + $this->source = $source; + $this->group = $group; + } + + public function __toString() + { + return 'aliased'; + } + + public function code(): string + { + return $this->code; + } + + public function source(): string + { + return $this->source; + } +} \ No newline at end of file diff --git a/src/App/Domain/Magento/Attribute/ExNihilo.php b/src/App/Domain/Magento/Attribute/ExNihilo.php new file mode 100644 index 0000000..feafd64 --- /dev/null +++ b/src/App/Domain/Magento/Attribute/ExNihilo.php @@ -0,0 +1,34 @@ +code = $code; + $this->group = $group; + } + + public function __toString() + { + return 'ex-nihilo'; + } + + public function code(): string + { + return $this->code; + } + + public function source(): string + { + return $this->code; + } +} \ No newline at end of file diff --git a/src/AttributeRenderer.php b/src/App/Domain/Magento/AttributeRenderer.php similarity index 74% rename from src/AttributeRenderer.php rename to src/App/Domain/Magento/AttributeRenderer.php index a388b4d..da45ec9 100644 --- a/src/AttributeRenderer.php +++ b/src/App/Domain/Magento/AttributeRenderer.php @@ -1,6 +1,6 @@ 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..23d3384 --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/Identifier.php @@ -0,0 +1,66 @@ +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/AttributeRenderer/Image.php b/src/App/Domain/Magento/AttributeRenderer/Image.php similarity index 81% rename from src/AttributeRenderer/Image.php rename to src/App/Domain/Magento/AttributeRenderer/Image.php index 1ddd693..0aad105 100644 --- a/src/AttributeRenderer/Image.php +++ b/src/App/Domain/Magento/AttributeRenderer/Image.php @@ -1,15 +1,18 @@ 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/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/AttributeRenderer/Varchar.php b/src/App/Domain/Magento/AttributeRenderer/Metric.php similarity index 76% rename from src/AttributeRenderer/Varchar.php rename to src/App/Domain/Magento/AttributeRenderer/Metric.php index 6754318..97f777f 100644 --- a/src/AttributeRenderer/Varchar.php +++ b/src/App/Domain/Magento/AttributeRenderer/Metric.php @@ -1,15 +1,18 @@ 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 +33,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..1dce15c --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/RichText.php @@ -0,0 +1,70 @@ +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/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/AttributeRenderer/SimpleSelect.php b/src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php similarity index 80% rename from src/AttributeRenderer/SimpleSelect.php rename to src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php index eb583c4..550e649 100644 --- a/src/AttributeRenderer/SimpleSelect.php +++ b/src/App/Domain/Magento/AttributeRenderer/SimpleSelect.php @@ -1,15 +1,18 @@ 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/AttributeRenderer/Text.php b/src/App/Domain/Magento/AttributeRenderer/Text.php similarity index 81% rename from src/AttributeRenderer/Text.php rename to src/App/Domain/Magento/AttributeRenderer/Text.php index adac2d5..6e15a9c 100644 --- a/src/AttributeRenderer/Text.php +++ b/src/App/Domain/Magento/AttributeRenderer/Text.php @@ -1,15 +1,18 @@ 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..297868c --- /dev/null +++ b/src/App/Domain/Magento/AttributeRenderer/TextArea.php @@ -0,0 +1,70 @@ +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/AttributeRenderer/Visibility.php b/src/App/Domain/Magento/AttributeRenderer/Visibility.php similarity index 81% rename from src/AttributeRenderer/Visibility.php rename to src/App/Domain/Magento/AttributeRenderer/Visibility.php index e828c28..74dc6ec 100644 --- a/src/AttributeRenderer/Visibility.php +++ b/src/App/Domain/Magento/AttributeRenderer/Visibility.php @@ -1,15 +1,18 @@ 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/AttributeResolver.php b/src/App/Domain/Magento/AttributeResolver.php similarity index 79% rename from src/AttributeResolver.php rename to src/App/Domain/Magento/AttributeResolver.php index bb05538..4546031 100644 --- a/src/AttributeResolver.php +++ b/src/App/Domain/Magento/AttributeResolver.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, ] ); } public function default(): string { - return 'attr_default'; + return strtr( + 'attr_default_{{ attribute }}', + [ + '{{ attribute }}' => $this->attribute->code, + ] + ); } public function alias(): string @@ -38,6 +48,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 59% rename from src/CodeGenerator/Localized.php rename to src/App/Domain/Magento/CodeGenerator/Localized.php index 7dcbb96..387e379 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(), ] ); @@ -34,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, ] ); } @@ -52,7 +63,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 58% rename from src/CodeGenerator/Scoped.php rename to src/App/Domain/Magento/CodeGenerator/Scoped.php index 3e81025..17e89e3 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(), ] ); @@ -35,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, ] ); } @@ -53,7 +63,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 64% rename from src/CodeGenerator/ScopedAndLocalized.php rename to src/App/Domain/Magento/CodeGenerator/ScopedAndLocalized.php index dd144a6..7929764 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(), ] @@ -40,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, ] ); } @@ -59,7 +70,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/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 new file mode 100644 index 0000000..2a0cdd2 --- /dev/null +++ b/src/App/Domain/Magento/FamilyVariant.php @@ -0,0 +1,43 @@ + 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; + } + + 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/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 @@ default = $default; } + public function __toString() + { + return $this->code; + } + public function code(): string { return $this->code; diff --git a/src/Locale/LocaleMapping.php b/src/App/Domain/Magento/Locale/LocaleMapping.php similarity index 69% rename from src/Locale/LocaleMapping.php rename to src/App/Domain/Magento/Locale/LocaleMapping.php index b1814ca..2c06ebf 100644 --- a/src/Locale/LocaleMapping.php +++ b/src/App/Domain/Magento/Locale/LocaleMapping.php @@ -1,9 +1,9 @@ store = $store; } + public function __toString() + { + return (string) $this->locale; + } + public function code(): string { return $this->locale->code(); diff --git a/src/MagentoStore.php b/src/App/Domain/Magento/MagentoStore.php similarity index 78% rename from src/MagentoStore.php rename to src/App/Domain/Magento/MagentoStore.php index ac73da5..36bf358 100644 --- a/src/MagentoStore.php +++ b/src/App/Domain/Magento/MagentoStore.php @@ -1,6 +1,6 @@ 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/Scope.php b/src/App/Domain/Magento/Scope.php similarity index 82% rename from src/Scope.php rename to src/App/Domain/Magento/Scope.php index de99cc9..11c62ad 100644 --- a/src/Scope.php +++ b/src/App/Domain/Magento/Scope.php @@ -1,6 +1,6 @@ locales = $locales; } + public function __toString() + { + return $this->code; + } + public function locales(): iterable { return $this->locales; diff --git a/src/Scope/ScopeMapping.php b/src/App/Domain/Magento/Scope/ScopeMapping.php similarity index 72% rename from src/Scope/ScopeMapping.php rename to src/App/Domain/Magento/Scope/ScopeMapping.php index d3e0147..cd0d3fb 100644 --- a/src/Scope/ScopeMapping.php +++ b/src/App/Domain/Magento/Scope/ScopeMapping.php @@ -1,9 +1,9 @@ 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 new file mode 100644 index 0000000..56ac3c3 --- /dev/null +++ b/src/App/Domain/Magento/SqlExportTwigExtension.php @@ -0,0 +1,123 @@ +codeGenerator->alias(); + }), + new TwigFunction('as_field_column', function(Field $field) { + return $field->codeGenerator->column(); + }), + new TwigFunction('as_field_table', function(Field $field) { + return $field->codeGenerator->table(); + }), + + new TwigFunction('as_default_alias', function() { + return 'attr_default'; + }), + + new TwigFunction('as_attribute_axis_table', function(FamilyVariant $variant, Attribute $attribute) { + return strtr( + 'tmp_axis_{{ variant }}__{{ attribute }}', + [ + '{{ variant }}' => $variant->code, + '{{ 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 }}', + [ + '{{ attribute }}' => $attribute->code(), + ] + ); + }), + new TwigFunction('as_attribute_default_table', function(Attribute $attribute) { + return strtr( + 'tmp_{{ attribute }}_default', + [ + '{{ attribute }}' => $attribute->code(), + ] + ); + }), + new TwigFunction('as_attribute_localized_table', function(Attribute $attribute, Locale $locale) { + return strtr( + 'tmp_{{ attribute }}_{{ locale }}', + [ + '{{ attribute }}' => $attribute->code(), + '{{ locale }}' => $locale->code(), + ] + ); + }), + new TwigFunction('as_attribute_scoped_table', function(Attribute $attribute, Scope $scope) { + return strtr( + 'tmp_{{ attribute }}_{{ scope }}', + [ + '{{ attribute }}' => $attribute->code(), + '{{ scope }}' => $scope->code(), + ] + ); + }), + new TwigFunction('as_attribute_scoped_and_localized_table', function(Attribute $attribute, Scope $scope, Locale $locale) { + return strtr( + 'tmp_{{ attribute }}_{{ scope }}_{{ locale }}', + [ + '{{ attribute }}' => $attribute->code(), + '{{ scope }}' => $scope->code(), + '{{ locale }}' => $locale->code(), + ] + ); + }), + + 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 }}__short', + [ + '{{ 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/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(...$families)), + SORT_REGULAR + ); + } + + 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/AttributeNotFoundException.php b/src/App/Infrastructure/AttributeNotFoundException.php new file mode 100644 index 0000000..3a0e1d8 --- /dev/null +++ b/src/App/Infrastructure/AttributeNotFoundException.php @@ -0,0 +1,7 @@ +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, + iterable $scopes, + iterable $locales, + array $config + ): \Iterator { + foreach ($attributes as $attribute) { + if (!isset($config[$attribute->code()])) { + continue; + } + + $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, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false, + $scopes, + $locales + ) + ); + break; + + case 'simple-select': + yield new AttributeRenderer\SimpleSelect( + $attribute, + $this->buildFieldResolver( + in_array($attribute, $axisAttributes), + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false, + $scopes, + $locales + ) + ); + break; + + case 'image': + yield new AttributeRenderer\Image( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false, + $scopes, + $locales + ) + ); + break; + + 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, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false, + $scopes, + $locales + ) + ); + break; + + case 'rich-text': + yield new AttributeRenderer\RichText( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false, + $scopes, + $locales + ) + ); + break; + + case 'text': + yield new AttributeRenderer\Text( + $attribute, + $this->buildFieldResolver( + false, + $attributeSpec['scoped'] ?? false, + $attributeSpec['localised'] ?? false, + $scopes, + $locales + ) + ); + break; + + case 'visibility': + yield new AttributeRenderer\Visibility( + $attribute, + $this->buildFieldResolver( + 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; + } + } + } + + 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(...$scopes); + } + if ($localised !== true && $scoped === true) { + return new FieldResolver\Scoped(...$scopes); + } + if ($localised === true && $scoped !== true) { + return new FieldResolver\Localized(...$locales); + } + + 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); + $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: ' + . ($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 new file mode 100644 index 0000000..1c4006c --- /dev/null +++ b/src/App/Infrastructure/Configuration/Configuration.php @@ -0,0 +1,157 @@ +getRootNode() + ->children() + ->arrayNode('attributes')->cannotBeEmpty()->useAttributeAsKey('code', false) + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->booleanNode('scoped')->end() + ->booleanNode('localised')->end() + ->arrayNode('label') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->enumNode('type') + ->isRequired() + ->values([ + 'identifier', + 'text-area', + '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', false) + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->arrayNode('label') + ->cannotBeEmpty() + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('families')->cannotBeEmpty()->useAttributeAsKey('code', false) + ->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('skuPattern')->end() + ->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', false) + ->arrayPrototype() + ->children() + ->scalarNode('code')->end() + ->scalarNode('currency')->end() + ->scalarNode('store')->end() + ->end() + ->end() + ->end() + ->arrayNode('scopes')->cannotBeEmpty()->useAttributeAsKey('code', false) + ->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/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/MagentoCommand.php b/src/App/Infrastructure/Console/Command/MagentoCommand.php new file mode 100644 index 0000000..426755b --- /dev/null +++ b/src/App/Infrastructure/Console/Command/MagentoCommand.php @@ -0,0 +1,256 @@ +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; + } + + 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([ + __DIR__ . '/../../../Resources/templates' + ]), + [ + 'autoescape' => false, + 'debug' => true, + ] + ); + + $twig->addExtension(new DebugExtension()); + $twig->addExtension(new SqlExportTwigExtension()); + + /** @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['locales'], 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; + } + } + } + + return false; + }); + + $attributeRenderersFactory = new AttributeRendererFactory(); + /** @var AttributeRenderer[] $attributeRenderers */ + $attributeRenderers = $attributeRenderersFactory( + $attributes, + $axisAttributes, + $scopes, + $locales, + $config['attributes'] + ); + + /** @var Family[] $families */ + $families = (new FamiliesFactory(...$attributeRenderers))($config); + /** @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', 'x'), + new \SplFileObject(($input->getArgument('output') ?? getcwd()) . '/product_models.csv', 'x'), + $attributeRenderers, + $families, + $axises, + $config['codes_mapping'] + ); + + $style->writeln('products and product models ok', SymfonyStyle::OUTPUT_PLAIN); + + return 0; + } +} \ 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/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/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/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..7ecf6bf --- /dev/null +++ b/src/App/Infrastructure/FamilyVariantAxisAttributeAggregator.php @@ -0,0 +1,26 @@ +walk(...$variants)), + SORT_REGULAR + ); + } + + 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/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/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 @@ +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 new file mode 100644 index 0000000..09d20ba --- /dev/null +++ b/src/App/Infrastructure/Normalizer/ListNormalizer.php @@ -0,0 +1,34 @@ +normalizer = $normalizer; + } + + public function normalize($object, $format = null, array $context = []) + { + return iterator_to_array($this->normalizeChild($object, substr($format, 0, -2), $context)); + } + + private function normalizeChild(iterable $object, $format, array $context): \Iterator + { + foreach ($object as $item) { + yield $this->normalizer->normalize($item, $format, $context); + } + } + + public function supportsNormalization($data, $format = null) + { + return is_iterable($data); + } +} \ 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..547fea3 --- /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 @@ +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/Infrastructure/VariantAxisesFactory.php b/src/App/Infrastructure/VariantAxisesFactory.php new file mode 100644 index 0000000..883e641 --- /dev/null +++ b/src/App/Infrastructure/VariantAxisesFactory.php @@ -0,0 +1,85 @@ +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) { + 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'])) + ); + } + } + } + } + + /** + * @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/Resources/templates/extract-attribute-options.sql.twig b/src/App/Resources/templates/extract-attribute-options.sql.twig new file mode 100644 index 0000000..15451f8 --- /dev/null +++ b/src/App/Resources/templates/extract-attribute-options.sql.twig @@ -0,0 +1,88 @@ +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\n') }} +) 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 -%} +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 new file mode 100644 index 0000000..af84dc9 --- /dev/null +++ b/src/App/Resources/templates/extract-attributes.sql.twig @@ -0,0 +1,231 @@ +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 -%} + codes.`group` 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", + type_mapping.metric_family AS "metric_family", + type_mapping.default_metric_unit 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", + 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 attribute in attributes -%} + {% set renderedAttribute %} + 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, + {% 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]) -%} +{%- endfor -%} + {{ renderedAttributes|join(' UNION ') }} +) AS codes +LEFT JOIN ( + SELECT + attribute.* + 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/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 new file mode 100644 index 0000000..4285ca3 --- /dev/null +++ b/src/App/Resources/templates/extract-products.sql.twig @@ -0,0 +1,27 @@ +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 parent +FROM tmp_sku AS product +{% for attribute in attributes %} + {% 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'); 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..5ed8218 --- /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) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +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..a86c4ca --- /dev/null +++ b/src/App/Resources/templates/product/01-attribute-options.sql.twig @@ -0,0 +1,43 @@ +CREATE TEMPORARY TABLE tmp_options ( + option_id INTEGER NOT NULL, + 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), + INDEX (code, attribute), + INDEX (attribute) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +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 attribute in attributes -%} + {% set renderedAttribute %} + SELECT '{{ attribute.attribute.code }}' AS code + {% endset %} + + {%- set renderedAttributes = renderedAttributes|merge([renderedAttribute]) -%} +{%- endfor -%} + {{ renderedAttributes|join('UNION\n') }} +) 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..7bf3afa --- /dev/null +++ b/src/App/Resources/templates/product/02-prefill-axis-attributes.sql.twig @@ -0,0 +1,27 @@ +{% for variant in variants %} + {% for axis in variant.all %} + {% for attribute in axis.attributes %} +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, + 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, + {{ 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.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..a923f00 --- /dev/null +++ b/src/App/Resources/templates/product/03-generate-intermediate-product-models.sql.twig @@ -0,0 +1,63 @@ +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) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci + +{% set variantRequests = [] %} +{% for variant in variants %} + {% if variant.isTwoLevels == true %} + {% 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 + 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/04-consolidate-product-models.sql.twig b/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig new file mode 100644 index 0000000..20b5c79 --- /dev/null +++ b/src/App/Resources/templates/product/04-consolidate-product-models.sql.twig @@ -0,0 +1,62 @@ +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, + hierarchy.family_variant AS family_variant, + 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, hierarchy.family_variant + FROM tmp_hierarchy as hierarchy + 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 +SELECT +{% for attribute in attributes %} + {% for field in attribute.fields %} + 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 %} +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 + {% 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 + {% 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/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-datetime-scopable-localizable.sql.twig similarity index 78% 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 index b836eee..0e64f69 100644 --- a/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/templates/product/attribute/extract-image-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig similarity index 74% rename from templates/product/attribute/extract-image-scopable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig index bc0e9c9..899e7a2 100644 --- a/templates/product/attribute/extract-image-scopable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-image-scopable-localizable.sql.twig @@ -1,29 +1,28 @@ - -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.* 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 -%} -LEFT JOIN catalog_product_entity_datetime AS {{ as_field_alias(field) }} +{% 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 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 new file mode 100644 index 0000000..68c3fee --- /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 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 -%} + {{ 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 +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-metric-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-metric-scopable-localizable.sql.twig new file mode 100644 index 0000000..f6a2156 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-metric-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) }}` DECIMAL(12, 4) 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_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_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 }} +{% endfor -%}; diff --git a/templates/product/attribute/extract-text-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig similarity index 78% rename from templates/product/attribute/extract-text-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-rich-text-scopable-localizable.sql.twig index c869198..9e16243 100644 --- a/templates/product/attribute/extract-text-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-rich-text-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) }}` 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.* 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 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/templates/product/attribute/extract-text.sql.twig b/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig similarity index 73% rename from templates/product/attribute/extract-text.sql.twig rename to src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig index 3578728..0324ed5 100644 --- a/templates/product/attribute/extract-text.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-rich-text.sql.twig @@ -1,22 +1,22 @@ -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.* 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-simple-select-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable-localizable.sql.twig new file mode 100644 index 0000000..f077aa0 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable-localizable.sql.twig @@ -0,0 +1,97 @@ +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, + {%- 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_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 + 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 +LEFT JOIN ( + SELECT * + FROM tmp_options + WHERE attribute LIKE '{{ renderer.attribute.code }}' +) AS {{ as_default_alias() }}_option + ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; + +{% for field in renderer.fields %} +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT 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() }}.`{{ 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 + 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 + AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id + AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} +LEFT JOIN ( + SELECT * + FROM tmp_options + 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(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, + {% 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) }}.`{{ 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 +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 +{% endfor %}; diff --git a/src/App/Resources/templates/product/attribute/extract-simple-select-scopable.sql.twig b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable.sql.twig new file mode 100644 index 0000000..7ad9f9b --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-simple-select-scopable.sql.twig @@ -0,0 +1,97 @@ +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, + {%- 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_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 + 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 +LEFT JOIN ( + SELECT * + FROM tmp_options + WHERE attribute LIKE '{{ renderer.attribute.code }}' +) AS {{ as_default_alias() }}_option + ON {{ as_default_alias() }}.value={{ as_default_alias() }}_option.option_id; + +{% for field in renderer.fields %} +CREATE TEMPORARY TABLE {{ as_field_table(field) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT 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() }}.`{{ 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 + 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 + AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id + AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} +LEFT JOIN ( + SELECT * + FROM tmp_options + 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(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, + {% 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) }}.`{{ 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 +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 +{% endfor %}; diff --git a/src/App/Resources/templates/product/attribute/extract-simple-select.sql.twig b/src/App/Resources/templates/product/attribute/extract-simple-select.sql.twig new file mode 100644 index 0000000..3b7993d --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-simple-select.sql.twig @@ -0,0 +1,30 @@ +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_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 + 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 +LEFT JOIN ( + SELECT * + FROM tmp_options + WHERE attribute LIKE '{{ renderer.attribute.code }}' +) AS {{ as_default_alias() }}_option + 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-scopable-localizable.sql.twig new file mode 100644 index 0000000..d0d5b4a --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-status-scopable-localizable.sql.twig @@ -0,0 +1,82 @@ +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, + {%- 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 `{{ 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 +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) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + COALESCE( + CASE {{ as_field_alias(field) }}.value + WHEN 1 THEN 'enabled' + WHEN 2 THEN 'disabled' + ELSE 'disabled' + END, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, + 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 +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 + AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id + AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; +{% endfor %} + +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) 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) }}.`{{ as_field_column(field) }}`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) 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 +{% endfor %}; 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..9e16243 --- /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='{{ 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 + 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..0324ed5 --- /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='{{ 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 + AND {{ as_default_alias() }}.store_id=0 \ No newline at end of file diff --git a/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig similarity index 78% rename from templates/product/attribute/extract-varchar-scopable-localizable.sql.twig rename to src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig index 331861b..31add71 100644 --- a/templates/product/attribute/extract-varchar-scopable-localizable.sql.twig +++ b/src/App/Resources/templates/product/attribute/extract-text-scopable-localizable.sql.twig @@ -1,27 +1,27 @@ -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.* 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 -{% 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-scopable-localizable.sql.twig new file mode 100644 index 0000000..5220177 --- /dev/null +++ b/src/App/Resources/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig @@ -0,0 +1,85 @@ +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, + {%- 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 `{{ 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 +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) }} ( + sku VARCHAR(128) NOT NULL, + entity_id INTEGER NOT NULL, + type_id VARCHAR(32) NOT NULL, + `{{ as_field_column(field) }}` VARCHAR(255) NULL, + PRIMARY KEY (sku), + UNIQUE INDEX (entity_id) +) CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci +SELECT + 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() }}.`{{ as_field_column(field) }}` + ) AS `{{ as_field_column(field) }}`, + 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 +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 + AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id + AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; +{% endfor %} + +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) 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) }}.`{{ as_field_column(field) }}`, + {{ as_default_alias() }}.`{{ as_field_column(field) }}` + ) 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 +{% 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; - } - - public function codeInSource(): string - { - return $this->code; - } - - public function codeInDestination(): string - { - return $this->code; - } -} \ No newline at end of file diff --git a/src/Attribute/Aliased.php b/src/Attribute/Aliased.php deleted file mode 100644 index 6a3aaea..0000000 --- a/src/Attribute/Aliased.php +++ /dev/null @@ -1,29 +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/Attribute/ExNihilo.php b/src/Attribute/ExNihilo.php deleted file mode 100644 index 04a98b5..0000000 --- a/src/Attribute/ExNihilo.php +++ /dev/null @@ -1,26 +0,0 @@ -code = $code; - } - - public function codeInSource(): string - { - return $this->code; - } - - public function codeInDestination(): string - { - return $this->code; - } -} \ 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 @@ -codeGenerator->alias(); - }), - new TwigFunction('as_field_column', function(Field $field) { - return $field->codeGenerator->column(); - }), - new TwigFunction('as_field_table', function(Field $field) { - return $field->codeGenerator->table(); - }), - - new TwigFunction('as_default_alias', function() { - return 'attr_default'; - }), - - new TwigFunction('as_attribute_table', function(Attribute $attribute) { - return strtr( - 'tmp_{{ attribute }}', - [ - '{{ attribute }}' => $attribute->codeInDestination(), - ] - ); - }), - new TwigFunction('as_attribute_default_table', function(Attribute $attribute) { - return strtr( - 'tmp_{{ attribute }}_default', - [ - '{{ attribute }}' => $attribute->codeInDestination(), - ] - ); - }), - new TwigFunction('as_attribute_localized_table', function(Attribute $attribute, Locale $locale) { - return strtr( - 'tmp_{{ attribute }}_{{ locale }}', - [ - '{{ attribute }}' => $attribute->codeInDestination(), - '{{ locale }}' => $locale->code(), - ] - ); - }), - new TwigFunction('as_attribute_scoped_table', function(Attribute $attribute, Scope $scope) { - return strtr( - 'tmp_{{ attribute }}_{{ scope }}', - [ - '{{ attribute }}' => $attribute->codeInDestination(), - '{{ scope }}' => $scope->code(), - ] - ); - }), - new TwigFunction('as_attribute_scoped_and_localized_table', function(Attribute $attribute, Scope $scope, Locale $locale) { - return strtr( - 'tmp_{{ attribute }}_{{ scope }}_{{ locale }}', - [ - '{{ attribute }}' => $attribute->codeInDestination(), - '{{ scope }}' => $scope->code(), - '{{ locale }}' => $locale->code(), - ] - ); - }), - ]; - } -} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock new file mode 100644 index 0000000..f2e8d22 --- /dev/null +++ b/symfony.lock @@ -0,0 +1,84 @@ +{ + "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" + }, + "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/finder": { + "version": "v4.3.4" + }, + "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/finalize-product-children.sql.twig b/templates/finalize-product-children.sql.twig deleted file mode 100644 index c7de07e..0000000 --- a/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/templates/finalize-product-parents.sql.twig b/templates/finalize-product-parents.sql.twig deleted file mode 100644 index 2489621..0000000 --- a/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/templates/finalize-products.sql.twig b/templates/finalize-products.sql.twig deleted file mode 100644 index 8ab509d..0000000 --- a/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/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 diff --git a/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig b/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig deleted file mode 100644 index 66ca736..0000000 --- a/templates/product/attribute/extract-simpleselect-scopable-localizable.sql.twig +++ /dev/null @@ -1,78 +0,0 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(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) -) -SELECT - product.*, - {{ as_default_alias() }}.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 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 -LEFT JOIN ( - SELECT * - FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' -) AS {{ as_default_alias() }}_option - ON attr_default.value=option_default.option_id; - -{% for field in fields %} -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, - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - product.*, - COALESCE( - {{ as_field_alias(field) }}.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 {{ 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 - AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id - AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} -LEFT JOIN ( - SELECT * - FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' -) 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) }} ( - 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, - {% endfor %} - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - {% for field in fields -%} - {{ as_field_alias(field) }}.value AS `{{ as_field_column(field) }}`, - {% endfor -%} - product.* -FROM tmp_sku AS product -{% for field in 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/templates/product/attribute/extract-simpleselect-scopable.sql.twig b/templates/product/attribute/extract-simpleselect-scopable.sql.twig deleted file mode 100644 index 188cd08..0000000 --- a/templates/product/attribute/extract-simpleselect-scopable.sql.twig +++ /dev/null @@ -1,78 +0,0 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(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) -) -SELECT - product.*, - {{ as_default_alias() }}.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 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 -LEFT JOIN ( - SELECT * - FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' -) AS {{ as_default_alias() }}_option - ON attr_default.value=option_default.option_id; - -{% for field in fields %} -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, - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - product.*, - COALESCE( - {{ as_field_alias(field) }}.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 {{ 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 - AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id - AND {{ as_field_alias(field) }}.store_id={{ field.store.id }} -LEFT JOIN ( - SELECT * - FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' -) 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) }} ( - 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, - {% endfor %} - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - {% for field in fields -%} - {{ as_field_alias(field) }}.value AS `{{ as_field_column(field) }}`, - {% endfor -%} - product.* -FROM tmp_sku AS product -{% for field in 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/templates/product/attribute/extract-simpleselect.sql.twig b/templates/product/attribute/extract-simpleselect.sql.twig deleted file mode 100644 index a96f718..0000000 --- a/templates/product/attribute/extract-simpleselect.sql.twig +++ /dev/null @@ -1,25 +0,0 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_table(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) -) -SELECT - product.*, - {{ as_default_alias() }}.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 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 -LEFT JOIN ( - SELECT * - FROM tmp_options - WHERE attribute LIKE '{{ attribute.codeInSource }}' -) AS {{ as_default_alias() }}_option - ON attr_default.value=option_default.option_id; diff --git a/templates/product/attribute/extract-status-scopable-localizable.sql.twig b/templates/product/attribute/extract-status-scopable-localizable.sql.twig deleted file mode 100644 index aa210e4..0000000 --- a/templates/product/attribute/extract-status-scopable-localizable.sql.twig +++ /dev/null @@ -1,73 +0,0 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(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) -) -SELECT - product.*, - CASE {{ as_default_alias() }}.value - WHEN 1 THEN 'enabled' - WHEN 2 THEN 'disabled' - ELSE 'disabled' - 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 -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 %} -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, - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - product.*, - COALESCE(CASE {{ as_field_alias(field) }}.value - WHEN 1 THEN 'enabled' - WHEN 2 THEN 'disabled' - ELSE 'disabled' - END, - {{ 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 {{ 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 - AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id - AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; -{% endfor %} - -CREATE TEMPORARY TABLE {{ as_attribute_table(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, - {% endfor %} - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - {% for field in 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 %} -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/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig b/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig deleted file mode 100644 index 04a05df..0000000 --- a/templates/product/attribute/extract-visibility-scopable-localizable.sql.twig +++ /dev/null @@ -1,76 +0,0 @@ - -CREATE TEMPORARY TABLE {{ as_attribute_default_table(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) -) -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 -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_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 %} -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, - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -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' - END, - attr_default.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 {{ 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 - AND {{ as_field_alias(field) }}.attribute_id=attribute.attribute_id - AND {{ as_field_alias(field) }}.store_id={{ field.store.id }}; -{% endfor %} - -CREATE TEMPORARY TABLE {{ as_attribute_table(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, - {% endfor %} - PRIMARY KEY (sku), - UNIQUE INDEX (entity_id) -) -SELECT - {% for field in 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 %} -LEFT JOIN {{ as_field_table(field) }} AS {{ as_field_alias(field) }} - ON {{ as_field_alias(field) }}.entity_id=product.entity_id -{% endfor %};