Add initial config and pipeline source files
This commit is contained in:
15
src/ArdentPipelineBundle.php
Normal file
15
src/ArdentPipelineBundle.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
|
||||
|
||||
class ArdentPipelineBundle extends AbstractBundle
|
||||
{
|
||||
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
|
||||
{
|
||||
$container->import('../config/services.yaml');
|
||||
}
|
||||
}
|
34
src/Pipeline/OptionsProcessor/AbstractOptionsStage.php
Normal file
34
src/Pipeline/OptionsProcessor/AbstractOptionsStage.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
abstract class AbstractOptionsStage implements OptionsStageInterface
|
||||
{
|
||||
protected Options $options;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function setOptions(Options $options): void
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
#[Required]
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function before(ItemBag $items): void
|
||||
{
|
||||
$this->logger->notice(sprintf('Begin %s with %d item(s)', static::class, $items->count()));
|
||||
}
|
||||
|
||||
public function after(ItemBag $items): void
|
||||
{
|
||||
$this->logger->notice(sprintf('End %s with %d item(s)', static::class, $items->count()));
|
||||
}
|
||||
}
|
88
src/Pipeline/OptionsProcessor/Item.php
Normal file
88
src/Pipeline/OptionsProcessor/Item.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
class Item
|
||||
{
|
||||
private string|int $id;
|
||||
|
||||
private bool $delete = false;
|
||||
|
||||
private ?ItemBag $children = null;
|
||||
private ?Item $parent = null;
|
||||
|
||||
private array $data = [];
|
||||
|
||||
public function __construct(
|
||||
string|int|null $id = null
|
||||
)
|
||||
{
|
||||
$id ??= sha1(microtime());
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function isDelete(): bool
|
||||
{
|
||||
return $this->delete;
|
||||
}
|
||||
|
||||
public function setDelete(): void
|
||||
{
|
||||
$this->delete = true;
|
||||
}
|
||||
|
||||
// Data manipulation
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->has($key) ? $this->data[$key] : $default;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): self
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->data);
|
||||
}
|
||||
|
||||
public function getChildren(): ?ItemBag
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function setChildren(ItemBag $children): void
|
||||
{
|
||||
$this->children = $children;
|
||||
}
|
||||
|
||||
public function addChild(Item $item): self
|
||||
{
|
||||
if (!$this->children) {
|
||||
$this->children = new ItemBag();
|
||||
}
|
||||
$item->setParent($this);
|
||||
$this->children->add($item);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): ?Item
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(?Item $parent): Item
|
||||
{
|
||||
$this->parent = $parent;
|
||||
return $this;
|
||||
}
|
||||
}
|
45
src/Pipeline/OptionsProcessor/ItemBag.php
Normal file
45
src/Pipeline/OptionsProcessor/ItemBag.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
class ItemBag
|
||||
{
|
||||
/**
|
||||
* @var Item[]
|
||||
*/
|
||||
private array $items = [];
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
public function add(Item $item): self
|
||||
{
|
||||
$this->items[$item->getId()] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function remove(Item $item): self
|
||||
{
|
||||
if ($this->has($item)) {
|
||||
unset($this->items[$item->getId()]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function has(Item $item): bool
|
||||
{
|
||||
return array_key_exists($item->getId(), $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
}
|
43
src/Pipeline/OptionsProcessor/ItemBagPipeline.php
Normal file
43
src/Pipeline/OptionsProcessor/ItemBagPipeline.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class ItemBagPipeline
|
||||
{
|
||||
/**
|
||||
* @var OptionsStageInterface[]
|
||||
*/
|
||||
private array $stages;
|
||||
|
||||
public function __construct(
|
||||
private readonly OptionsProcessor $processor,
|
||||
OptionsStageInterface ...$stages)
|
||||
{
|
||||
$this->stages = $stages;
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
foreach ($this->stages as $stage) {
|
||||
$stage->setLogger($logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[]|ItemBag $items
|
||||
*/
|
||||
public function process(array|ItemBag $items): ItemBag
|
||||
{
|
||||
if (is_array($items)) {
|
||||
$itemBag = new ItemBag();
|
||||
foreach ($items as $item) {
|
||||
$itemBag->add($item);
|
||||
}
|
||||
} else {
|
||||
$itemBag = $items;
|
||||
}
|
||||
return $this->processor->process($itemBag, ...$this->stages);
|
||||
}
|
||||
}
|
19
src/Pipeline/OptionsProcessor/Options.php
Normal file
19
src/Pipeline/OptionsProcessor/Options.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
class Options
|
||||
{
|
||||
public function __construct(private readonly array $options)
|
||||
{
|
||||
}
|
||||
|
||||
public function get(string $key, $default = null): mixed
|
||||
{
|
||||
if (array_key_exists($key, $this->options) && $this->options[$key] !== null) {
|
||||
return $this->options[$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
}
|
38
src/Pipeline/OptionsProcessor/OptionsProcessor.php
Normal file
38
src/Pipeline/OptionsProcessor/OptionsProcessor.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
class OptionsProcessor
|
||||
{
|
||||
private Options $options;
|
||||
|
||||
public function __construct(
|
||||
?Options $options = null,
|
||||
)
|
||||
{
|
||||
$this->options = $options ?? new Options([]);
|
||||
}
|
||||
|
||||
public function process(ItemBag $items, OptionsStageInterface ...$stages): ItemBag
|
||||
{
|
||||
foreach ($stages as $stage) {
|
||||
$stage->setOptions($this->options);
|
||||
|
||||
$stage->before($items);
|
||||
foreach ($items->all() as $item) {
|
||||
$newItems = $stage($item);
|
||||
|
||||
if ($item->isDelete()) {
|
||||
$items->remove($item);
|
||||
}
|
||||
|
||||
foreach ($newItems as $newItem) {
|
||||
$items->add($newItem);
|
||||
}
|
||||
}
|
||||
$stage->after($items);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
43
src/Pipeline/OptionsProcessor/OptionsStageBag.php
Normal file
43
src/Pipeline/OptionsProcessor/OptionsStageBag.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class OptionsStageBag implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var OptionsStageInterface[]
|
||||
*/
|
||||
private array $stages = [];
|
||||
|
||||
public function __construct(
|
||||
iterable $optionProcessors
|
||||
)
|
||||
{
|
||||
foreach ($optionProcessors as $optionProcessor) {
|
||||
/** @var OptionsStageInterface $optionProcessor */
|
||||
$this->stages[get_class($optionProcessor)] = $optionProcessor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of OptionsStageInterface
|
||||
* @param class-string<T> $id
|
||||
* @return T
|
||||
*/
|
||||
public function get(string $id): OptionsStageInterface
|
||||
{
|
||||
if (!$this->has($id)) {
|
||||
throw new Exception(sprintf('Class "%s" in not an OptionsStageInterface, it is not in the OptionsStageBag', $id));
|
||||
}
|
||||
|
||||
return $this->stages[$id];
|
||||
}
|
||||
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return array_key_exists($id, $this->stages);
|
||||
}
|
||||
}
|
44
src/Pipeline/OptionsProcessor/OptionsStageBuilder.php
Normal file
44
src/Pipeline/OptionsProcessor/OptionsStageBuilder.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
class OptionsStageBuilder
|
||||
{
|
||||
/**
|
||||
* @var OptionsStageInterface[]
|
||||
*/
|
||||
private array $stages = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly OptionsStageBag $bag,
|
||||
private readonly ?Options $options = null,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|OptionsStageInterface $stageClass Can either be the classFQN of an OptionsStageInterface or one directly
|
||||
*/
|
||||
public function add(string|OptionsStageInterface $stageClass): self
|
||||
{
|
||||
if (is_string($stageClass)) {
|
||||
$this->stages[] = $this->bag->get($stageClass);
|
||||
} else {
|
||||
$this->stages[] = $stageClass;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function each(ItemBagPipeline $pipeline): self
|
||||
{
|
||||
$this->stages[] = $this->bag->get(SubItemStage::class)->setSubPipeline($pipeline);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): ItemBagPipeline
|
||||
{
|
||||
return new ItemBagPipeline(new OptionsProcessor($this->options), ...$this->stages);
|
||||
}
|
||||
}
|
21
src/Pipeline/OptionsProcessor/OptionsStageInterface.php
Normal file
21
src/Pipeline/OptionsProcessor/OptionsStageInterface.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
interface OptionsStageInterface
|
||||
{
|
||||
/**
|
||||
* @return Item[] New items
|
||||
*/
|
||||
public function __invoke(Item $item): array;
|
||||
|
||||
public function setLogger(LoggerInterface $logger): void;
|
||||
|
||||
public function before(ItemBag $items): void;
|
||||
|
||||
public function after(ItemBag $items): void;
|
||||
|
||||
public function setOptions(Options $options): void;
|
||||
}
|
32
src/Pipeline/OptionsProcessor/SubItemStage.php
Normal file
32
src/Pipeline/OptionsProcessor/SubItemStage.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Ardent\PipelineBundle\Pipeline\OptionsProcessor;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SubItemStage extends AbstractOptionsStage
|
||||
{
|
||||
private ?ItemBagPipeline $subPipeline = null;
|
||||
|
||||
public function getSubPipeline(): ItemBagPipeline
|
||||
{
|
||||
if (!$this->subPipeline) {
|
||||
throw new Exception('subPipeline not set');
|
||||
}
|
||||
return $this->subPipeline;
|
||||
}
|
||||
|
||||
public function setSubPipeline(ItemBagPipeline $subPipeline): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->subPipeline = $subPipeline;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function __invoke(Item $item): array
|
||||
{
|
||||
$this->getSubPipeline()->process($item->getChildren());
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user