Skip to content

Commit

Permalink
Merge pull request #111 from php-etl/feature/custom-action
Browse files Browse the repository at this point in the history
Integration of custom actions
  • Loading branch information
sebprt authored Jul 5, 2023
2 parents fe53fd3 + 436885f commit 26c4f31
Show file tree
Hide file tree
Showing 23 changed files with 818 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/phpstan-5.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: PHPStan level 5
on: push
jobs:
phpstan:
phpstan5:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpstan-6.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: PHPStan level 6
on: push
jobs:
phpstan:
phpstan6:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpstan-7.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: PHPStan level 7
on: push
jobs:
phpstan:
phpstan7:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpstan-8.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: PHPStan level 8
on: push
jobs:
phpstan:
phpstan8:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
"Kiboko\\Component\\Satellite\\Plugin\\FTP\\Service",
"Kiboko\\Component\\Satellite\\Plugin\\Batching\\Service",
"Kiboko\\Component\\Satellite\\Plugin\\Filtering\\Service"
],
"actions": [
"Kiboko\\Component\\Satellite\\Action\\SFTP\\Service",
"Kiboko\\Component\\Satellite\\Action\\Custom\\Service"
]
}
},
Expand Down
47 changes: 47 additions & 0 deletions src/Action/Custom/Builder/Action.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Satellite\Action\Custom\Builder;

use Kiboko\Component\Satellite\Action\SFTP\Builder\ActionBuilderInterface;
use PhpParser\Node;

final class Action implements ActionBuilderInterface
{
private ?Node\Expr $logger = null;
private ?Node\Expr $state = null;

public function __construct(private readonly Node\Expr $service, private readonly string $containerNamespace)
{
}

public function withLogger(Node\Expr $logger): self
{
$this->logger = $logger;

return $this;
}

public function withState(Node\Expr $state): self
{
$this->state = $state;

return $this;
}

public function getNode(): Node
{
return new Node\Expr\MethodCall(
var: new Node\Expr\New_(
class: new Node\Name\FullyQualified($this->containerNamespace)
),
name: new Node\Identifier('get'),
args: [
new Node\Arg(
$this->service
),
]
);
}
}
44 changes: 44 additions & 0 deletions src/Action/Custom/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Satellite\Action\Custom;

use Kiboko\Component\Satellite\DependencyInjection\Configuration\ServicesConfiguration;
use Kiboko\Contract\Configurator\ActionConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;

use function Kiboko\Component\SatelliteToolbox\Configuration\asExpression;
use function Kiboko\Component\SatelliteToolbox\Configuration\isExpression;

final class Configuration implements ActionConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$builder = new TreeBuilder('custom');
/* @phpstan-ignore-next-line */
$builder->getRootNode()
->children()
->arrayNode('expression_language')
->scalarPrototype()->end()
->end()
->append((new ServicesConfiguration())->getConfigTreeBuilder()->getRootNode())
->scalarNode('use')
->isRequired()
->end()
->arrayNode('parameters')
->useAttributeAsKey('keyparam')
->scalarPrototype()
->cannotBeEmpty()
->validate()
->ifTrue(isExpression())
->then(asExpression())
->end()
->end()
->end()
->end()
;

return $builder;
}
}
91 changes: 91 additions & 0 deletions src/Action/Custom/Factory/Action.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Satellite\Action\Custom\Factory;

use Kiboko\Component\Packaging;
use Kiboko\Component\Satellite\Action\Custom;
use Kiboko\Component\Satellite\Action\Custom\Configuration;
use Kiboko\Component\Satellite\DependencyInjection\SatelliteDependencyInjection;
use Kiboko\Component\Satellite\ExpressionLanguage as Satellite;
use Kiboko\Contract\Configurator;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception as Symfony;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\String\ByteString;

use function Kiboko\Component\SatelliteToolbox\Configuration\compileValueWhenExpression;

class Action implements Configurator\FactoryInterface
{
private readonly Processor $processor;
private readonly ConfigurationInterface $configuration;

public function __construct(
private readonly ExpressionLanguage $interpreter = new Satellite\ExpressionLanguage(),
private readonly array $providers = [],
) {
$this->processor = new Processor();
$this->configuration = new Configuration();
}

public function configuration(): ConfigurationInterface
{
return $this->configuration;
}

/**
* @throws Configurator\ConfigurationExceptionInterface
*/
public function normalize(array $config): array
{
try {
return $this->processor->processConfiguration($this->configuration, $config);
} catch (Symfony\InvalidTypeException|Symfony\InvalidConfigurationException $exception) {
throw new Configurator\InvalidConfigurationException($exception->getMessage(), 0, $exception);
}
}

public function validate(array $config): bool
{
try {
$this->processor->processConfiguration($this->configuration, $config);

return true;
} catch (Symfony\InvalidTypeException|Symfony\InvalidConfigurationException) {
return false;
}
}

/**
* @throws Configurator\ConfigurationExceptionInterface
*/
public function compile(array $config): Repository\Action
{
$containerName = sprintf('ProjectServiceContainer%s', ByteString::fromRandom(8)->toString());

$builder = new Custom\Builder\Action(
compileValueWhenExpression($this->interpreter, $config['use']),
sprintf('GyroscopsGenerated\\%s', $containerName),
);

$container = (new SatelliteDependencyInjection(...$this->providers))($config);

$repository = new Repository\Action($builder);

$dumper = new PhpDumper($container);
$repository->addFiles(
new Packaging\File(
sprintf('%s.php', $containerName),
new Packaging\Asset\InMemory(
$dumper->dump(['class' => $containerName, 'namespace' => 'GyroscopsGenerated'])
)
),
);

return $repository;
}
}
32 changes: 32 additions & 0 deletions src/Action/Custom/Factory/Repository/Action.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Satellite\Action\Custom\Factory\Repository;

use Kiboko\Component\Satellite\Action\Custom;
use Kiboko\Contract\Configurator;

final class Action implements Configurator\RepositoryInterface
{
use RepositoryTrait;

public function __construct(private readonly Custom\Builder\Action $builder)
{
$this->files = [];
$this->packages = [];
}

public function getBuilder(): Custom\Builder\Action
{
return $this->builder;
}

public function merge(Configurator\RepositoryInterface $friend): self
{
array_push($this->files, ...$friend->getFiles());
array_push($this->packages, ...$friend->getPackages());

return $this;
}
}
42 changes: 42 additions & 0 deletions src/Action/Custom/Factory/Repository/RepositoryTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Satellite\Action\Custom\Factory\Repository;

use Kiboko\Contract\Configurator;
use Kiboko\Contract\Packaging;

trait RepositoryTrait
{
/** @var array<Packaging\FileInterface|Packaging\DirectoryInterface> */
private array $files;
/** @var string[] */
private array $packages;

public function addFiles(Packaging\FileInterface|Packaging\DirectoryInterface ...$files): Configurator\RepositoryInterface
{
array_push($this->files, ...$files);

return $this;
}

/** @return iterable<Packaging\FileInterface|Packaging\DirectoryInterface> */
public function getFiles(): iterable
{
return $this->files;
}

public function addPackages(string ...$packages): Configurator\RepositoryInterface
{
array_push($this->packages, ...$packages);

return $this;
}

/** @return iterable<string> */
public function getPackages(): iterable
{
return $this->packages;
}
}
82 changes: 82 additions & 0 deletions src/Action/Custom/Service.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Satellite\Action\Custom;

use Kiboko\Component\Satellite\Action\Custom;
use Kiboko\Component\Satellite\ExpressionLanguage as Satellite;
use Kiboko\Contract\Configurator;
use Symfony\Component\Config\Definition\Exception as Symfony;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

#[Configurator\Action(
name: 'custom',
)]
final readonly class Service implements Configurator\ActionInterface
{
private Processor $processor;
private Configurator\ActionConfigurationInterface $configuration;

public function __construct(
private ExpressionLanguage $interpreter = new Satellite\ExpressionLanguage()
) {
$this->processor = new Processor();
$this->configuration = new Custom\Configuration();
}

public function interpreter(): ExpressionLanguage
{
return $this->interpreter;
}

public function configuration(): Configurator\ActionConfigurationInterface
{
return $this->configuration;
}

/**
* @throws Configurator\ConfigurationExceptionInterface
*/
public function normalize(array $config): array
{
try {
return $this->processor->processConfiguration($this->configuration, $config);
} catch (Symfony\InvalidTypeException|Symfony\InvalidConfigurationException $exception) {
throw new Configurator\InvalidConfigurationException($exception->getMessage(), 0, $exception);
}
}

public function validate(array $config): bool
{
try {
$this->processor->processConfiguration($this->configuration, $config);

return true;
} catch (Symfony\InvalidTypeException|Symfony\InvalidConfigurationException) {
return false;
}
}

/**
* @throws Configurator\ConfigurationExceptionInterface
*/
public function compile(array $config): Configurator\RepositoryInterface
{
$interpreter = clone $this->interpreter;

if (\array_key_exists('expression_language', $config)
&& \is_array($config['expression_language'])
&& \count($config['expression_language'])
) {
foreach ($config['expression_language'] as $provider) {
$interpreter->registerProvider(new $provider());
}
}

$actionFactory = new Custom\Factory\Action($this->interpreter);

return $actionFactory->compile($config);
}
}
2 changes: 1 addition & 1 deletion src/Action/SFTP/Builder/Action.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function withState(Node\Expr $state): self
public function getNode(): Node
{
return new Node\Expr\New_(
class: new Node\Name\FullyQualified('Kiboko\Component\Action\Flow\SFTP\Action'),
class: new Node\Name\FullyQualified('Kiboko\Component\Action\Flow\SFTP\UploadFile'),
args: [
new Node\Arg(
value: $this->host,
Expand Down
Loading

0 comments on commit 26c4f31

Please sign in to comment.