Skip to content

Commit

Permalink
Implement SourceField bulk apis
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreGauthier committed Jan 22, 2024
1 parent 2b303a3 commit 8ad6263
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 336 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: php-actions/composer@v6
with:
php_version: "8.1"
php_extensions: ctype curl dom hash iconv intl gd json mbstring openssl session simplexml xml zip zlib pdo_mysql
php_extensions: ctype curl dom hash iconv intl gd json mbstring openssl session simplexml xml zip zlib pdo_mysql exif
- name: Php cs fixer
run: php ./vendor/bin/php-cs-fixer fix src
- name: Phpstan
Expand Down
14 changes: 10 additions & 4 deletions src/Indexer/CategoryIndexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@
namespace Gally\SyliusPlugin\Indexer;

use Gally\SyliusPlugin\Service\IndexOperation;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\ResourceRepositoryTrait;
use Sylius\Bundle\TaxonomyBundle\Doctrine\ORM\TaxonRepository;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Taxonomy\Model\TaxonTranslationInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;

/**
* Class CategoryIndexer
*
* @package Gally
* @author Stephan Hochdörfer <[email protected]>, Gally Team <[email protected]>
* @copyright 2022-present Smile
* @license Open Software License v. 3.0 (OSL-3.0)
*/
class CategoryIndexer extends AbstractIndexer
{
private TaxonRepositoryInterface $taxonRepository;
Expand Down Expand Up @@ -100,11 +106,11 @@ private function formatTaxon(TaxonInterface $taxon, TaxonTranslationInterface $t
{
$parentId = '';
if (null !== $taxon->getParent()) {
$parentId = (string) $taxon->getParent()->getCode();
$parentId = str_replace('/', '_', (string) $taxon->getParent()->getCode());
}

return [
'id' => (string) $taxon->getCode(),
'id' => str_replace('/', '_', (string) $taxon->getCode()),
'parentId' => $parentId,
'level' => $taxon->getLevel() + 1,
'path' => $this->pathCache[$taxon->getCode()],
Expand Down
8 changes: 8 additions & 0 deletions src/Indexer/ProductIndexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
use Sylius\Component\Product\Model\ProductOptionValueInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;

/**
* Class ProductIndexer
*
* @package Gally
* @author Stephan Hochdörfer <[email protected]>, Gally Team <[email protected]>
* @copyright 2022-present Smile
* @license Open Software License v. 3.0 (OSL-3.0)
*/
class ProductIndexer extends AbstractIndexer
{
public function __construct(
Expand Down
22 changes: 8 additions & 14 deletions src/Resources/config/services/synchronizers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
<argument key="$getCollectionMethod">getMetadataCollection</argument>
<argument key="$createEntityMethod">postMetadataCollection</argument>
<argument key="$putEntityMethod">putMetadataItem</argument>
<argument key="$deleteEntityMethod">deleteMetadataItem</argument>
</service>

<service id="Gally\SyliusPlugin\Synchronizer\CatalogSynchronizer" parent="Gally\SyliusPlugin\Synchronizer\AbstractSynchronizer" autowire="true" autoconfigure="true">
<argument key="$entityClass">\Gally\Rest\Api\CatalogApi</argument>
<argument key="$getCollectionMethod">getCatalogCollection</argument>
<argument key="$createEntityMethod">postCatalogCollection</argument>
<argument key="$putEntityMethod">putCatalogItem</argument>
<argument key="$deleteEntityMethod">deleteCatalogItem</argument>
<tag name="gally.entity.synchronizer" priority="100"/>
</service>

Expand All @@ -29,35 +31,27 @@
<argument key="$getCollectionMethod">getLocalizedCatalogCollection</argument>
<argument key="$createEntityMethod">postLocalizedCatalogCollection</argument>
<argument key="$putEntityMethod">putLocalizedCatalogItem</argument>
<argument key="$deleteEntityMethod">deleteLocalizedCatalogItem</argument>
</service>

<service id="Gally\SyliusPlugin\Synchronizer\SourceFieldSynchronizer" parent="Gally\SyliusPlugin\Synchronizer\AbstractSynchronizer" autowire="true" autoconfigure="true">
<argument key="$entityClass">\Gally\Rest\Api\SourceFieldApi</argument>
<argument key="$getCollectionMethod">getSourceFieldCollection</argument>
<argument key="$createEntityMethod">postSourceFieldCollection</argument>
<argument key="$putEntityMethod">putSourceFieldItem</argument>
<argument key="$deleteEntityMethod">deleteSourceFieldItem</argument>
<argument key="$bulkEntityMethod">bulkSourceFieldItem</argument>
<tag name="gally.entity.synchronizer" priority="80"/>
</service>

<service id="Gally\SyliusPlugin\Synchronizer\SourceFieldLabelSynchronizer" parent="Gally\SyliusPlugin\Synchronizer\AbstractSynchronizer" autowire="true" autoconfigure="true">
<argument key="$entityClass">\Gally\Rest\Api\SourceFieldLabelApi</argument>
<argument key="$getCollectionMethod">getSourceFieldLabelCollection</argument>
<argument key="$createEntityMethod">postSourceFieldLabelCollection</argument>
<argument key="$putEntityMethod">putSourceFieldLabelItem</argument>
</service>

<service id="Gally\SyliusPlugin\Synchronizer\SourceFieldOptionSynchronizer" parent="Gally\SyliusPlugin\Synchronizer\AbstractSynchronizer" autowire="true" autoconfigure="true">
<argument key="$entityClass">\Gally\Rest\Api\SourceFieldOptionApi</argument>
<argument key="$getCollectionMethod">getSourceFieldOptionCollection</argument>
<argument key="$createEntityMethod">postSourceFieldOptionCollection</argument>
<argument key="$putEntityMethod">putSourceFieldOptionItem</argument>
</service>

<service id="Gally\SyliusPlugin\Synchronizer\SourceFieldOptionLabelSynchronizer" parent="Gally\SyliusPlugin\Synchronizer\AbstractSynchronizer" autowire="true" autoconfigure="true">
<argument key="$entityClass">\Gally\Rest\Api\SourceFieldOptionLabelApi</argument>
<argument key="$getCollectionMethod">getSourceFieldOptionLabelCollection</argument>
<argument key="$createEntityMethod">postSourceFieldOptionLabelCollection</argument>
<argument key="$putEntityMethod">putSourceFieldOptionLabelItem</argument>
<argument key="$deleteEntityMethod">deleteSourceFieldOptionItem</argument>
<argument key="$bulkEntityMethod">bulkSourceFieldOptionItem</argument>
<tag name="gally.entity.synchronizer" priority="60"/>
</service>

<service id="Gally\SyliusPlugin\Synchronizer\Subscriber\ProductAttributeSubscriber">
Expand Down
69 changes: 56 additions & 13 deletions src/Synchronizer/AbstractSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
abstract class AbstractSynchronizer
{
protected const FETCH_PAGE_SIZE = 50;
protected const BATCH_SIZE = 100;

protected array $entityByCode = [];
private array $currentBatch = [];
private int $currentBatchSize = 0;
protected bool $allEntityHasBeenFetch = false;

protected GallyConfiguration $configuration;
Expand All @@ -37,7 +40,9 @@ public function __construct(
protected string $entityClass,
protected string $getCollectionMethod,
protected string $createEntityMethod,
protected string $putEntityMethod
protected string $putEntityMethod,
protected string $deleteEntityMethod,
protected ?string $bulkEntityMethod = null
) {
$this->configuration = $this->configurationRepository->getConfiguration();
}
Expand All @@ -53,16 +58,18 @@ public function getEntityClass(): string

public function fetchEntities(): void
{
$currentPage = 1;
do {
$entities = $this->client->query(...$this->buildFetchAllParams($currentPage));

foreach ($entities as $entity) {
$this->addEntityByIdentity($entity);
}
++$currentPage;
} while (\count($entities) >= self::FETCH_PAGE_SIZE);
$this->allEntityHasBeenFetch = true;
if (!$this->allEntityHasBeenFetch) {
$currentPage = 1;
do {
$entities = $this->client->query(...$this->buildFetchAllParams($currentPage));

foreach ($entities as $entity) {
$this->addEntityByIdentity($entity);
}
++$currentPage;
} while (\count($entities) >= self::FETCH_PAGE_SIZE);
$this->allEntityHasBeenFetch = true;
}
}

public function fetchEntity(ModelInterface $entity): ?ModelInterface
Expand Down Expand Up @@ -122,10 +129,10 @@ protected function createOrUpdateEntity(ModelInterface $entity): ModelInterface
return $this->entityByCode[$this->getIdentity($entity)];
}

protected function getEntityFromApi(ModelInterface $entity): ?ModelInterface
protected function getEntityFromApi(ModelInterface|string $entity): ?ModelInterface
{
if ($this->allEntityHasBeenFetch) {
return $this->entityByCode[$this->getIdentity($entity)] ?? null;
return $this->entityByCode[is_string($entity) ? $entity : $this->getIdentity($entity)] ?? null;
}

return $this->fetchEntity($entity);
Expand All @@ -142,4 +149,40 @@ protected function validateEntity(ModelInterface $entity): void
throw new \LogicException('Missing properties for ' . \get_class($entity) . ' : ' . implode(',', $entity->listInvalidProperties()));
}
}

protected function addEntityToBulk(ModelInterface $entity): void
{
if ($this->bulkEntityMethod === null) {
throw new \Exception(sprintf('The entity %s doesn\'t have a bulk method.', $this->getEntityClass()));
}

$this->currentBatch[] = $entity;
$this->currentBatchSize++;
if ($this->currentBatchSize >= self::BATCH_SIZE) {
$this->runBulk();
}
}

protected function runBulk(): void
{
if ($this->currentBatchSize) {
$entities = $this->client->query($this->entityClass, $this->bulkEntityMethod, 'fakeId', $this->currentBatch);
foreach ($entities as $entity) {
$this->addEntityByIdentity($entity);
}
$this->currentBatch = [];
$this->currentBatchSize = 0;
}
}

protected function getAllEntityCodes(): array
{
$this->fetchEntities();
return array_keys($this->entityByCode);
}

protected function deleteEntity(int|string $entityId)
{
$this->client->query($this->entityClass, $this->deleteEntityMethod, $entityId);
}
}
31 changes: 28 additions & 3 deletions src/Synchronizer/CatalogSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
namespace Gally\SyliusPlugin\Synchronizer;

use Gally\Rest\Model\Catalog;
use Gally\Rest\Model\CatalogCatalogRead;
use Gally\Rest\Model\LocalizedCatalogCatalogRead;
use Gally\Rest\Model\ModelInterface;
use Gally\SyliusPlugin\Api\RestClient;
use Gally\SyliusPlugin\Repository\GallyConfigurationRepository;
Expand All @@ -27,13 +29,17 @@
*/
class CatalogSynchronizer extends AbstractSynchronizer
{
private array $catalogCodes = [];
private array $localizedCatalogCodes = [];

public function __construct(
GallyConfigurationRepository $configurationRepository,
RestClient $client,
string $entityClass,
string $getCollectionMethod,
string $createEntityMethod,
string $putEntityMethod,
string $deleteEntityMethod,
private RepositoryInterface $channelRepository,
private LocalizedCatalogSynchronizer $localizedCatalogSynchronizer
) {
Expand All @@ -43,20 +49,23 @@ public function __construct(
$entityClass,
$getCollectionMethod,
$createEntityMethod,
$putEntityMethod
$putEntityMethod,
$deleteEntityMethod
);
}

public function getIdentity(ModelInterface $entity): string
{
/** @var Catalog $entity */
return $entity->getCode();
return "catalog" . $entity->getCode();
}

public function synchronizeAll(): void
{
$this->fetchEntities();
$this->catalogCodes = array_flip($this->getAllEntityCodes());
$this->localizedCatalogSynchronizer->fetchEntities();
$this->localizedCatalogCodes = array_flip($this->localizedCatalogSynchronizer->getAllEntityCodes());

// synchronize all channels where the Gally integration is active
$channels = $this->channelRepository->findBy(['gallyActive' => 1]);
Expand All @@ -65,6 +74,18 @@ public function synchronizeAll(): void
foreach ($channels as $channel) {
$this->synchronizeItem(['channel' => $channel]);
}

foreach (array_flip($this->localizedCatalogCodes) as $localizedCatalogCode) {
/** @var LocalizedCatalogCatalogRead $localizedCatalog */
$localizedCatalog = $this->localizedCatalogSynchronizer->getEntityFromApi($localizedCatalogCode);
$this->localizedCatalogSynchronizer->deleteEntity($localizedCatalog->getId() );
}

foreach (array_flip($this->catalogCodes) as $catalogCode) {
/** @var CatalogCatalogRead $catalog */
$catalog = $this->getEntityFromApi($catalogCode);
$this->deleteEntity($catalog->getId());
}
}

public function synchronizeItem(array $params): ?ModelInterface
Expand All @@ -81,13 +102,17 @@ public function synchronizeItem(array $params): ?ModelInterface

/** @var LocaleInterface $locale */
foreach ($channel->getLocales() as $locale) {
$this->localizedCatalogSynchronizer->synchronizeItem([
$localizedCatalog = $this->localizedCatalogSynchronizer->synchronizeItem([
'channel' => $channel,
'locale' => $locale,
'catalog' => $catalog,
]);

unset($this->localizedCatalogCodes[$this->localizedCatalogSynchronizer->getIdentity($localizedCatalog)]);
}

unset($this->catalogCodes[$this->getIdentity($catalog)]);

return $catalog;
}
}
7 changes: 1 addition & 6 deletions src/Synchronizer/LocalizedCatalogSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ public function synchronizeItem(array $params): ?ModelInterface
{
/** @var Channel $channel */
$channel = $params['channel'];

/** @var Locale $locale */
$locale = $params['locale'];

/** @var Catalog $catalog */
$catalog = $params['catalog'];

Expand Down Expand Up @@ -85,10 +83,7 @@ public function getLocalizedCatalogByLocale(string $localeCode): array

public function getByIdentity(string $identifier): ?ModelInterface
{
if (!$this->allEntityHasBeenFetch) {
$this->fetchEntities();
}

$this->fetchEntities();
return $this->entityByCode[$identifier] ?? null;
}
}
16 changes: 5 additions & 11 deletions src/Synchronizer/MetadataSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
namespace Gally\SyliusPlugin\Synchronizer;

use Gally\Rest\Model\Metadata;
use Gally\Rest\Model\MetadataMetadataRead;
use Gally\Rest\Model\MetadataMetadataWrite;
use Gally\Rest\Model\ModelInterface;

final class MetadataSynchronizer extends AbstractSynchronizer
Expand All @@ -25,21 +27,13 @@ public function synchronizeAll(): void

public function synchronizeItem(array $params): ?ModelInterface
{
return $this->createOrUpdateEntity(new Metadata(['entity' => $params['entity']]));
$this->fetchEntities();
return $this->createOrUpdateEntity(new MetadataMetadataWrite(['entity' => $params['entity']]));
}

public function getIdentity(ModelInterface $entity): string
{
/** @var Metadata $entity */
/** @var MetadataMetadataRead $entity */
return $entity->getEntity();
}

protected function getEntityFromApi(ModelInterface $entity): ?ModelInterface
{
if (!$this->allEntityHasBeenFetch) {
$this->fetchEntities();
}

return $this->entityByCode[$this->getIdentity($entity)] ?? null;
}
}
Loading

0 comments on commit 8ad6263

Please sign in to comment.