Skip to content

Commit 92a9a86

Browse files
committed
Introduce new feed generation system
1 parent 5f39542 commit 92a9a86

27 files changed

+945
-8
lines changed

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
],
1010
"license": "MIT",
1111
"require": {
12-
"php": "^8.1",
12+
"php": "^8.2",
1313
"ext-json": "*",
1414
"sylius/sylius": "^1.12",
15-
"symfony/serializer": "^5.0 || ^6.0",
1615
"symfony/lock": "^5.4 || ^6.0",
16+
"symfony/serializer": "^5.0 || ^6.0",
1717
"symfony/webpack-encore-bundle": "^1.15"
1818
},
1919
"require-dev": {
@@ -49,7 +49,8 @@
4949
"symfony/flex": "^2.2.2",
5050
"symfony/intl": "^5.4 || ^6.0",
5151
"symfony/web-profiler-bundle": "^5.4 || ^6.0",
52-
"vimeo/psalm": "^4.27"
52+
"theofidry/alice-data-fixtures": "^1.7",
53+
"vimeo/psalm": "^5.26"
5354
},
5455
"config": {
5556
"sort-packages": true,

config/services/command.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
66

7+
use Webgriffe\SyliusClerkPlugin\Command\V2FeedGeneratorCommand;
78
use Webgriffe\SyliusClerkPlugin\Command\FeedGeneratorCommand;
89
use Webgriffe\SyliusClerkPlugin\Service\FeedGenerator;
910

@@ -19,4 +20,13 @@
1920
])
2021
->tag('console.command')
2122
;
23+
24+
$services->set('webgriffe_sylius_clerk_plugin.command.feed_generator', V2FeedGeneratorCommand::class)
25+
->args([
26+
'$channelRepository' => service('sylius.repository.channel'),
27+
'$productsFeedGenerator' => service('webgriffe_sylius_clerk_plugin.feed_generator.products'),
28+
'$filesystem' => service('filesystem'),
29+
])
30+
->tag('console.command')
31+
;
2232
};

config/services/generator.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
66

7+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Enum\Resource;
8+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Generator\ResourceFeedGenerator;
79
use Webgriffe\SyliusClerkPlugin\QueryBuilder\CustomersQueryBuilderFactory;
810
use Webgriffe\SyliusClerkPlugin\QueryBuilder\ProductsQueryBuilderFactory;
911
use Webgriffe\SyliusClerkPlugin\QueryBuilder\TaxonsQueryBuilderFactory;
@@ -24,4 +26,44 @@
2426
service('serializer'),
2527
])
2628
;
29+
30+
$services->set('webgriffe_sylius_clerk_plugin.feed_generator.products', ResourceFeedGenerator::class)
31+
->args([
32+
service('webgriffe_sylius_clerk_plugin.provider.products'),
33+
service('serializer'),
34+
Resource::PRODUCTS,
35+
])
36+
;
37+
38+
$services->set('webgriffe_sylius_clerk_plugin.feed_generator.categories', ResourceFeedGenerator::class)
39+
->args([
40+
service('webgriffe_sylius_clerk_plugin.provider.categories'),
41+
service('serializer'),
42+
Resource::CATEGORIES,
43+
])
44+
;
45+
46+
$services->set('webgriffe_sylius_clerk_plugin.feed_generator.orders', ResourceFeedGenerator::class)
47+
->args([
48+
service('webgriffe_sylius_clerk_plugin.provider.orders'),
49+
service('serializer'),
50+
Resource::ORDERS,
51+
])
52+
;
53+
54+
$services->set('webgriffe_sylius_clerk_plugin.feed_generator.customers', ResourceFeedGenerator::class)
55+
->args([
56+
service('webgriffe_sylius_clerk_plugin.provider.customers'),
57+
service('serializer'),
58+
Resource::CUSTOMERS,
59+
])
60+
;
61+
62+
$services->set('webgriffe_sylius_clerk_plugin.feed_generator.pages', ResourceFeedGenerator::class)
63+
->args([
64+
service('webgriffe_sylius_clerk_plugin.provider.pages'),
65+
service('serializer'),
66+
Resource::PAGES,
67+
])
68+
;
2769
};

config/services/provider.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
66

7+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Provider\QueryBuilderResourceProvider;
78
use Webgriffe\SyliusClerkPlugin\Service\PrivateApiKeyProvider;
89
use Webgriffe\SyliusClerkPlugin\Service\PublicApiKeyProvider;
910

@@ -13,4 +14,34 @@
1314
$services->set('webgriffe_sylius_clerk.provider.private_api_key', PrivateApiKeyProvider::class);
1415

1516
$services->set('webgriffe_sylius_clerk.provider.public_api_key', PublicApiKeyProvider::class);
17+
18+
$services->set('webgriffe_sylius_clerk_plugin.provider.products', QueryBuilderResourceProvider::class)
19+
->args([
20+
service('webgriffe_sylius_clerk_plugin.query_builder.products'),
21+
])
22+
;
23+
24+
$services->set('webgriffe_sylius_clerk_plugin.provider.categories', QueryBuilderResourceProvider::class)
25+
->args([
26+
service('webgriffe_sylius_clerk_plugin.query_builder.categories'),
27+
])
28+
;
29+
30+
$services->set('webgriffe_sylius_clerk_plugin.provider.orders', QueryBuilderResourceProvider::class)
31+
->args([
32+
service('webgriffe_sylius_clerk_plugin.query_builder.orders'),
33+
])
34+
;
35+
36+
$services->set('webgriffe_sylius_clerk_plugin.provider.customers', QueryBuilderResourceProvider::class)
37+
->args([
38+
service('webgriffe_sylius_clerk_plugin.query_builder.customers'),
39+
])
40+
;
41+
42+
$services->set('webgriffe_sylius_clerk_plugin.provider.pages', QueryBuilderResourceProvider::class)
43+
->args([
44+
service('webgriffe_sylius_clerk_plugin.query_builder.pages'),
45+
])
46+
;
1647
};

config/services/query_builder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
66

7+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Doctrine\ORM\ProductsQueryBuilder;
78
use Webgriffe\SyliusClerkPlugin\QueryBuilder\CustomersQueryBuilderFactory;
89
use Webgriffe\SyliusClerkPlugin\QueryBuilder\OrdersQueryBuilderFactory;
910
use Webgriffe\SyliusClerkPlugin\QueryBuilder\ProductsQueryBuilderFactory;
@@ -35,4 +36,11 @@
3536
service('sylius.repository.customer'),
3637
])
3738
;
39+
40+
$services->set('webgriffe_sylius_clerk_plugin.query_builder.products', ProductsQueryBuilder::class)
41+
->args([
42+
service('sylius.repository.product'),
43+
service('event_dispatcher'),
44+
])
45+
;
3846
};

src/Command/FeedGeneratorCommand.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
use Webgriffe\SyliusClerkPlugin\Service\FeedGeneratorInterface;
1818
use Webmozart\Assert\Assert;
1919

20+
/**
21+
* @deprecated
22+
*/
2023
final class FeedGeneratorCommand extends Command
2124
{
2225
use LockableTrait;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webgriffe\SyliusClerkPlugin\Command;
6+
7+
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
8+
use Sylius\Component\Core\Model\ChannelInterface;
9+
use Symfony\Component\Console\Attribute\AsCommand;
10+
use Symfony\Component\Console\Command\Command;
11+
use Symfony\Component\Console\Command\LockableTrait;
12+
use Symfony\Component\Console\Input\InputInterface;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
use Symfony\Component\Filesystem\Filesystem;
16+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Generator\FeedGeneratorInterface;
17+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\ValueObject\Feed;
18+
19+
/**
20+
* @psalm-suppress PropertyNotSetInConstructor
21+
*/
22+
#[AsCommand(name: 'webgriffe:clerk:feed:generate', description: 'Generate feeds for Clerk.io data sync')]
23+
class V2FeedGeneratorCommand extends Command
24+
{
25+
use LockableTrait;
26+
27+
private SymfonyStyle $io;
28+
29+
/**
30+
* @param ChannelRepositoryInterface<ChannelInterface> $channelRepository
31+
*/
32+
public function __construct(
33+
private readonly ChannelRepositoryInterface $channelRepository,
34+
private readonly FeedGeneratorInterface $productsFeedGenerator,
35+
private readonly Filesystem $filesystem,
36+
private readonly string $feedsStorageDirectory,
37+
) {
38+
parent::__construct();
39+
}
40+
41+
protected function configure(): void
42+
{
43+
}
44+
45+
protected function initialize(InputInterface $input, OutputInterface $output): void
46+
{
47+
$this->io = new SymfonyStyle($input, $output);
48+
}
49+
50+
protected function execute(InputInterface $input, OutputInterface $output): int
51+
{
52+
$this->io->writeln('Starting Clerk.io feed generation...');
53+
if (!$this->lock()) {
54+
$this->io->error('The command is already running in another process, quitting.');
55+
56+
return Command::FAILURE;
57+
}
58+
59+
$this->filesystem->mkdir($this->feedsStorageDirectory);
60+
/** @var ChannelInterface[] $channels */
61+
$channels = $this->channelRepository->findAll();
62+
foreach ($channels as $channel) {
63+
foreach ($channel->getLocales() as $locale) {
64+
$productsFeed = $this->productsFeedGenerator->generate($channel, (string) $locale->getCode());
65+
$feedFilePath = $this->getFeedFilePath($productsFeed);
66+
67+
$this->io->writeln(sprintf('Writing feed to file: %s', $feedFilePath));
68+
$this->filesystem->dumpFile($feedFilePath, $productsFeed->getContent());
69+
}
70+
}
71+
72+
$this->io->success('Clerk.io feed generation completed successfully.');
73+
74+
return Command::SUCCESS;
75+
}
76+
77+
private function getFeedFilePath(Feed $productsFeed): string
78+
{
79+
return sprintf(
80+
'%s/%s',
81+
rtrim($this->feedsStorageDirectory, '/'),
82+
ltrim($productsFeed->getFileName(), '/'),
83+
);
84+
}
85+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Doctrine\ORM\Event;
6+
7+
use Doctrine\ORM\QueryBuilder;
8+
use Sylius\Component\Core\Model\ChannelInterface;
9+
10+
final readonly class QueryBuilderEvent
11+
{
12+
public function __construct(
13+
private QueryBuilder $queryBuilder,
14+
private ChannelInterface $channel,
15+
private string $localeCode,
16+
private ?\DateTimeInterface $modifiedAfter = null,
17+
private ?int $limit = null,
18+
private ?int $offset = null,
19+
) {
20+
}
21+
22+
public function getQueryBuilder(): QueryBuilder
23+
{
24+
return $this->queryBuilder;
25+
}
26+
27+
public function getChannel(): ChannelInterface
28+
{
29+
return $this->channel;
30+
}
31+
32+
public function getLocaleCode(): string
33+
{
34+
return $this->localeCode;
35+
}
36+
37+
public function getModifiedAfter(): ?\DateTimeInterface
38+
{
39+
return $this->modifiedAfter;
40+
}
41+
42+
public function getLimit(): ?int
43+
{
44+
return $this->limit;
45+
}
46+
47+
public function getOffset(): ?int
48+
{
49+
return $this->offset;
50+
}
51+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Doctrine\ORM;
6+
7+
use Psr\EventDispatcher\EventDispatcherInterface;
8+
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository;
9+
use Sylius\Component\Core\Model\ChannelInterface;
10+
use Sylius\Component\Core\Model\ProductInterface;
11+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Doctrine\ORM\Event\QueryBuilderEvent;
12+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Enum\Resource;
13+
use Webgriffe\SyliusClerkPlugin\DataSyncInfrastructure\Model\QueryBuilderInterface;
14+
15+
/**
16+
* @implements QueryBuilderInterface<ProductInterface>
17+
*/
18+
final readonly class ProductsQueryBuilder implements QueryBuilderInterface
19+
{
20+
public function __construct(
21+
private ProductRepository $productRepository,
22+
private EventDispatcherInterface $eventDispatcher,
23+
) {
24+
}
25+
26+
public function getResource(): Resource
27+
{
28+
return Resource::PRODUCTS;
29+
}
30+
31+
public function getResult(
32+
ChannelInterface $channel,
33+
string $localeCode,
34+
?\DateTimeInterface $modifiedAfter = null,
35+
?int $limit = null,
36+
?int $offset = null,
37+
): array {
38+
$queryBuilder = $this->productRepository->createQueryBuilder('p');
39+
40+
$queryBuilder
41+
->andWhere(':channel MEMBER OF p.channels')
42+
->setParameter('channel', $channel)
43+
;
44+
$queryBuilder
45+
->leftJoin('p.translations', 't', 'WITH', 't.locale = :localeCode')
46+
->setParameter('localeCode', $localeCode)
47+
;
48+
49+
if ($modifiedAfter !== null) {
50+
$queryBuilder
51+
->andWhere('p.updatedAt > :modifiedAfter')
52+
->setParameter('modifiedAfter', $modifiedAfter)
53+
;
54+
}
55+
56+
if ($limit !== null) {
57+
$queryBuilder->setMaxResults($limit);
58+
}
59+
60+
if ($offset !== null) {
61+
$queryBuilder->setFirstResult($offset);
62+
}
63+
64+
$this->eventDispatcher->dispatch(new QueryBuilderEvent(
65+
$queryBuilder,
66+
$channel,
67+
$localeCode,
68+
$modifiedAfter,
69+
$limit,
70+
$offset,
71+
));
72+
73+
/** @var ProductInterface[] $result */
74+
$result = $queryBuilder->getQuery()->getResult();
75+
76+
return $result;
77+
}
78+
}

0 commit comments

Comments
 (0)