diff --git a/composer.json b/composer.json index bc633db..b4d905b 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ }, "extra": { "branch-alias": { - "dev-main": "0.4.x-dev" + "dev-main": "0.5.x-dev" }, "gyroscops": { "plugins": ["Kiboko\\Plugin\\SQL\\Service"] diff --git a/src/Builder/AlternativeLoader.php b/src/Builder/AlternativeLoader.php index 0a9336e..7b5986e 100644 --- a/src/Builder/AlternativeLoader.php +++ b/src/Builder/AlternativeLoader.php @@ -11,6 +11,8 @@ final class AlternativeLoader implements StepBuilderInterface { /** @var array */ private array $parameters = []; + /** @var array */ + private array $parametersLists = []; public function __construct(private readonly Node\Expr $query) { @@ -101,6 +103,76 @@ public function addBinaryParam(int|string $key, Node\Expr $param): self return $this; } + public function addStringParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'string', + ]; + + return $this; + } + + public function addIntegerParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'integer', + ]; + + return $this; + } + + public function addBooleanParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'boolean', + ]; + + return $this; + } + + public function addDateParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'date', + ]; + + return $this; + } + + public function addDateTimeParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'datetime', + ]; + + return $this; + } + + public function addJSONParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'json', + ]; + + return $this; + } + + public function addBinaryParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'binary', + ]; + + return $this; + } + public function getNode(): Node { return new Node\Stmt\Expression( @@ -126,7 +198,7 @@ public function getNode(): Node ) ) ), - ...$this->compileParameters(), + ...$this->walkParameters(), new Node\Stmt\Expression( expr: new Node\Expr\MethodCall( var: new Node\Expr\Variable('statement'), @@ -147,115 +219,135 @@ public function getNode(): Node ); } - public function compileParameters(): iterable + public function walkParameters(): iterable { foreach ($this->parameters as $key => $parameter) { - yield match ($parameter['type']) { - 'datetime' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ + yield $this->compileParameter($key, $parameter); + } + + foreach ($this->parametersLists as $key => $parameter) { + yield new Node\Stmt\Foreach_( + expr: $parameter['value'], + valueVar: new Node\Expr\Variable('value'), + subNodes: [ + 'keyVar' => new Node\Expr\Variable('key'), + 'stmts' => [ + $this->compileParameter( new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) + new Node\Expr\BinaryOp\Concat( + new Node\Scalar\String_($key.'_'), + new Node\Expr\Variable('key'), + ) ), - new Node\Arg( - value: new Node\Expr\MethodCall( - var: $parameter['value'], - name: new Node\Name('format'), - args: [ - new Node\Arg( - value: new Node\Scalar\String_('YYYY-MM-DD HH:MI:SS') - ), - ], - ), + [ + 'type' => $parameter['type'], + 'value' => new Node\Expr\Variable('value'), + ] + ), + ], + ] + ); + } + } + + public function compileParameter(int|string|Node\Arg $key, array $parameter): Node\Stmt\Expression + { + return match ($parameter['type']) { + 'datetime' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + value: new Node\Expr\MethodCall( + var: $parameter['value'], + name: new Node\Name('format'), + args: [ + new Node\Arg( + value: new Node\Scalar\String_('YYYY-MM-DD HH:MI:SS') + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - 'date' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - value: new Node\Expr\MethodCall( - var: $parameter['value'], - name: new Node\Name('format'), - args: [ - new Node\Arg( - value: new Node\Scalar\String_('YYYY-MM-DD') - ), - ], - ), + ), + 'date' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + value: new Node\Expr\MethodCall( + var: $parameter['value'], + name: new Node\Name('format'), + args: [ + new Node\Arg( + value: new Node\Scalar\String_('YYYY-MM-DD') + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - 'json' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - new Node\Expr\FuncCall( - name: new Node\Name('json_encode'), - args: [ - new Node\Arg( - value: $parameter['value'] - ), - ], - ), + ), + 'json' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + new Node\Expr\FuncCall( + name: new Node\Name('json_encode'), + args: [ + new Node\Arg( + value: $parameter['value'] + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - default => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - $parameter['value'] - ), - $this->compileParameterType($parameter), - ], - ), + ), + default => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + $parameter['value'] + ), + $this->compileParameterType($parameter), + ], ), - }; + ), + }; + } + + private function compileParameterKey(int|string|Node\Arg $key): Node\Arg + { + if (\is_string($key)) { + return new Node\Arg( + new Node\Scalar\Encapsed([ + new Node\Scalar\EncapsedStringPart(':'), + new Node\Scalar\EncapsedStringPart($key), + ]) + ); } + if ($key instanceof Node\Arg) { + return $key; + } + + return new Node\Arg( + new Node\Scalar\LNumber($key) + ); } private function compileParameterType(array $parameter): Node\Arg diff --git a/src/Builder/AlternativeLookup.php b/src/Builder/AlternativeLookup.php index e094a00..a7fc111 100644 --- a/src/Builder/AlternativeLookup.php +++ b/src/Builder/AlternativeLookup.php @@ -13,6 +13,8 @@ final class AlternativeLookup implements StepBuilderInterface { /** @var array */ private array $parameters = []; + /** @var array */ + private array $parametersLists = []; private ?Builder $merge = null; public function __construct(private readonly Node\Expr $query) @@ -111,6 +113,76 @@ public function withMerge(Builder $merge): self return $this; } + public function addStringParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'string', + ]; + + return $this; + } + + public function addIntegerParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'integer', + ]; + + return $this; + } + + public function addBooleanParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'boolean', + ]; + + return $this; + } + + public function addDateParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'date', + ]; + + return $this; + } + + public function addDateTimeParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'datetime', + ]; + + return $this; + } + + public function addJSONParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'json', + ]; + + return $this; + } + + public function addBinaryParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'binary', + ]; + + return $this; + } + public function getNode(): Node { return (new IsolatedValueAppendingBuilder( @@ -120,7 +192,14 @@ public function getNode(): Node ...array_filter( [ $this->getAlternativeLookupNode(), - $this->merge?->getNode(), + new Node\Stmt\If_( + cond: new Node\Expr\Variable('lookup'), + subNodes: [ + 'stmts' => [ + $this->merge?->getNode(), + ], + ] + ), new Node\Stmt\Return_( new Node\Expr\Variable('output') ), @@ -151,7 +230,7 @@ public function getAlternativeLookupNode(): Node ), ), ), - ...$this->compileParameters(), + ...$this->walkParameters(), new Node\Stmt\Expression( expr: new Node\Expr\MethodCall( var: new Node\Expr\Variable('stmt'), @@ -233,121 +312,141 @@ class: new Node\Name\FullyQualified('PDO'), ))->getNode(); } - public function compileParameters(): iterable + public function walkParameters(): iterable { foreach ($this->parameters as $key => $parameter) { - yield match ($parameter['type']) { - 'datetime' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('stmt'), - name: new Node\Identifier('bindValue'), - args: [ + yield $this->compileParameter($key, $parameter); + } + + foreach ($this->parametersLists as $key => $parameter) { + yield new Node\Stmt\Foreach_( + expr: $parameter['value'], + valueVar: new Node\Expr\Variable('value'), + subNodes: [ + 'keyVar' => new Node\Expr\Variable('key'), + 'stmts' => [ + $this->compileParameter( new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) + new Node\Expr\BinaryOp\Concat( + new Node\Scalar\String_($key.'_'), + new Node\Expr\Variable('key'), + ) ), - new Node\Arg( - value: new Node\Expr\StaticCall( - class: new Node\Name('DateTimeImmutable'), - name: new Node\Name('createFromFormat'), - args: [ - new Node\Arg( - value: new Node\Scalar\String_('YYYY-MM-DD HH:MI:SS') - ), - new Node\Arg( - value: $parameter['value'] - ), - ], - ), + [ + 'type' => $parameter['type'], + 'value' => new Node\Expr\Variable('value'), + ] + ), + ], + ] + ); + } + } + + private function compileParameter(int|string|Node\Arg $key, array $parameter): Node\Stmt\Expression + { + return match ($parameter['type']) { + 'datetime' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('stmt'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + value: new Node\Expr\StaticCall( + class: new Node\Name('DateTimeImmutable'), + name: new Node\Name('createFromFormat'), + args: [ + new Node\Arg( + value: new Node\Scalar\String_('YYYY-MM-DD HH:MI:SS') + ), + new Node\Arg( + value: $parameter['value'] + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - 'date' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('stmt'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - value: new Node\Expr\StaticCall( - class: new Node\Name('DateTimeImmutable'), - name: new Node\Name('createFromFormat'), - args: [ - new Node\Arg( - value: new Node\Scalar\String_('YYYY-MM-DD') - ), - new Node\Arg( - value: $parameter['value'] - ), - ], - ), + ), + 'date' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('stmt'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + value: new Node\Expr\StaticCall( + class: new Node\Name('DateTimeImmutable'), + name: new Node\Name('createFromFormat'), + args: [ + new Node\Arg( + value: new Node\Scalar\String_('YYYY-MM-DD') + ), + new Node\Arg( + value: $parameter['value'] + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - 'json' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('stmt'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - new Node\Expr\FuncCall( - name: new Node\Name('json_decode'), - args: [ - new Node\Arg( - value: $parameter['value'] - ), - ], - ), + ), + 'json' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('stmt'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + new Node\Expr\FuncCall( + name: new Node\Name('json_decode'), + args: [ + new Node\Arg( + value: $parameter['value'] + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - default => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('stmt'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - $parameter['value'] - ), - $this->compileParameterType($parameter), - ], - ), + ), + default => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('stmt'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + $parameter['value'] + ), + $this->compileParameterType($parameter), + ], ), - }; + ), + }; + } + + private function compileParameterKey(int|string|Node\Arg $key): Node\Arg + { + if (\is_string($key)) { + return new Node\Arg( + new Node\Scalar\Encapsed([ + new Node\Scalar\EncapsedStringPart(':'), + new Node\Scalar\EncapsedStringPart($key), + ]) + ); + } + if ($key instanceof Node\Arg) { + return $key; } + + return new Node\Arg( + new Node\Scalar\LNumber($key) + ); } private function compileParameterType(array $parameter): Node\Arg diff --git a/src/Builder/ConditionalLookup.php b/src/Builder/ConditionalLookup.php index 2d62d27..8f749c8 100644 --- a/src/Builder/ConditionalLookup.php +++ b/src/Builder/ConditionalLookup.php @@ -7,7 +7,6 @@ use Kiboko\Contract\Configurator\StepBuilderInterface; use Kiboko\Contract\Mapping\CompiledMapperInterface; use PhpParser\Node; -use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; final class ConditionalLookup implements StepBuilderInterface @@ -188,7 +187,8 @@ class: new Node\Name\FullyQualified(NullLogger::class) default: new Node\Expr\ConstFetch( name: new Node\Name(name: 'null'), ), - type: new Node\Name\FullyQualified(LoggerInterface::class) + type: new Node\NullableType(\Psr\Log\LoggerInterface::class), + flags: Node\Stmt\Class_::MODIFIER_PRIVATE ), ], ], diff --git a/src/Builder/Loader.php b/src/Builder/Loader.php index 39b642a..42f7488 100644 --- a/src/Builder/Loader.php +++ b/src/Builder/Loader.php @@ -14,7 +14,10 @@ final class Loader implements StepBuilderInterface private array $beforeQueries = []; /** @var array */ private array $afterQueries = []; + /** @var array */ private array $parameters = []; + /** @var array */ + private array $parametersLists = []; public function __construct( private readonly Node\Expr $query, @@ -144,6 +147,76 @@ public function addBinaryParam(int|string $key, Node\Expr $param): self return $this; } + public function addStringParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'string', + ]; + + return $this; + } + + public function addIntegerParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'integer', + ]; + + return $this; + } + + public function addBooleanParamList(int|string $key, Node\Expr $param): StepBuilderInterface + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'boolean', + ]; + + return $this; + } + + public function addDateParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'date', + ]; + + return $this; + } + + public function addDateTimeParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'datetime', + ]; + + return $this; + } + + public function addJSONParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'json', + ]; + + return $this; + } + + public function addBinaryParamList(int|string $key, Node\Expr $param): self + { + $this->parametersLists[$key] = [ + 'value' => $param, + 'type' => 'binary', + ]; + + return $this; + } + public function getNode(): Node { return new Node\Expr\New_( @@ -168,7 +241,7 @@ class: new Node\Name\FullyQualified(\Kiboko\Component\Flow\SQL\Loader::class), ), ], 'stmts' => [ - ...$this->compileParameters(), + ...$this->walkParameters(), ], ], )) @@ -234,115 +307,135 @@ public function compileAfterQueries(): Node\Expr ); } - public function compileParameters(): iterable + public function walkParameters(): iterable { foreach ($this->parameters as $key => $parameter) { - yield match ($parameter['type']) { - 'datetime' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ + yield $this->compileParameter($key, $parameter); + } + + foreach ($this->parametersLists as $key => $parameter) { + yield new Node\Stmt\Foreach_( + expr: $parameter['value'], + valueVar: new Node\Expr\Variable('value'), + subNodes: [ + 'keyVar' => new Node\Expr\Variable('key'), + 'stmts' => [ + $this->compileParameter( new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) + new Node\Expr\BinaryOp\Concat( + new Node\Scalar\String_($key.'_'), + new Node\Expr\Variable('key'), + ) ), - new Node\Arg( - value: new Node\Expr\MethodCall( - var: $parameter['value'], - name: new Node\Name('format'), - args: [ - new Node\Arg( - value: new Node\Scalar\String_('YYYY-MM-DD HH:MI:SS') - ), - ], - ), + [ + 'type' => $parameter['type'], + 'value' => new Node\Expr\Variable('value'), + ] + ), + ], + ] + ); + } + } + + public function compileParameter(int|string|Node\Arg $key, array $parameter): Node\Stmt\Expression + { + return match ($parameter['type']) { + 'datetime' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + value: new Node\Expr\MethodCall( + var: $parameter['value'], + name: new Node\Name('format'), + args: [ + new Node\Arg( + value: new Node\Scalar\String_('YYYY-MM-DD HH:MI:SS') + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - 'date' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - value: new Node\Expr\MethodCall( - var: $parameter['value'], - name: new Node\Name('format'), - args: [ - new Node\Arg( - value: new Node\Scalar\String_('YYYY-MM-DD') - ), - ], - ), + ), + 'date' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + value: new Node\Expr\MethodCall( + var: $parameter['value'], + name: new Node\Name('format'), + args: [ + new Node\Arg( + value: new Node\Scalar\String_('YYYY-MM-DD') + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - 'json' => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - new Node\Expr\FuncCall( - name: new Node\Name('json_encode'), - args: [ - new Node\Arg( - value: $parameter['value'] - ), - ], - ), + ), + 'json' => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + new Node\Expr\FuncCall( + name: new Node\Name('json_encode'), + args: [ + new Node\Arg( + value: $parameter['value'] + ), + ], ), - $this->compileParameterType($parameter), - ], - ), + ), + $this->compileParameterType($parameter), + ], ), - default => new Node\Stmt\Expression( - new Node\Expr\MethodCall( - var: new Node\Expr\Variable('statement'), - name: new Node\Identifier('bindValue'), - args: [ - new Node\Arg( - \is_string($key) ? new Node\Scalar\Encapsed( - [ - new Node\Scalar\EncapsedStringPart(':'), - new Node\Scalar\EncapsedStringPart($key), - ] - ) : new Node\Scalar\LNumber($key) - ), - new Node\Arg( - $parameter['value'] - ), - $this->compileParameterType($parameter), - ], - ), + ), + default => new Node\Stmt\Expression( + new Node\Expr\MethodCall( + var: new Node\Expr\Variable('statement'), + name: new Node\Identifier('bindValue'), + args: [ + $this->compileParameterKey($key), + new Node\Arg( + $parameter['value'] + ), + $this->compileParameterType($parameter), + ], ), - }; + ), + }; + } + + private function compileParameterKey(int|string|Node\Arg $key): Node\Arg + { + if (\is_string($key)) { + return new Node\Arg( + new Node\Scalar\Encapsed([ + new Node\Scalar\EncapsedStringPart(':'), + new Node\Scalar\EncapsedStringPart($key), + ]) + ); + } + if ($key instanceof Node\Arg) { + return $key; } + + return new Node\Arg( + new Node\Scalar\LNumber($key) + ); } private function compileParameterType(array $parameter): Node\Arg diff --git a/src/Configuration/Extractor.php b/src/Configuration/Extractor.php index 3255b1f..4a31f9a 100644 --- a/src/Configuration/Extractor.php +++ b/src/Configuration/Extractor.php @@ -7,6 +7,9 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use function Kiboko\Component\SatelliteToolbox\Configuration\asExpression; +use function Kiboko\Component\SatelliteToolbox\Configuration\isExpression; + final class Extractor implements ConfigurationInterface { public function getConfigTreeBuilder(): TreeBuilder @@ -15,7 +18,12 @@ public function getConfigTreeBuilder(): TreeBuilder /* @phpstan-ignore-next-line */ $builder->getRootNode() - ->append((new Query())->getConfigTreeBuilder()->getRootNode()) + ->append((new Query())->getConfigTreeBuilder()->getRootNode() + ->validate() + ->ifTrue(isExpression()) + ->then(asExpression()) + ->end() + ) ->append((new Parameters())->getConfigTreeBuilder()->getRootNode()) ; diff --git a/src/Configuration/Loader.php b/src/Configuration/Loader.php index 79c60e3..52b7538 100644 --- a/src/Configuration/Loader.php +++ b/src/Configuration/Loader.php @@ -35,7 +35,12 @@ public function getConfigTreeBuilder(): TreeBuilder return $data; }) ->end() - ->append((new Query())->getConfigTreeBuilder()->getRootNode()) + ->append((new Query())->getConfigTreeBuilder()->getRootNode() + ->validate() + ->ifTrue(isExpression()) + ->then(asExpression()) + ->end() + ) ->append((new Parameters())->getConfigTreeBuilder()->getRootNode()) ->children() ->append((new FastMap\Configuration('merge'))->getConfigTreeBuilder()->getRootNode()) @@ -66,7 +71,12 @@ private function getConditionalTreeBuilder(): TreeBuilder ->then(asExpression()) ->end() ->end() - ->append((new Query())->getConfigTreeBuilder()->getRootNode()) + ->append((new Query())->getConfigTreeBuilder()->getRootNode() + ->validate() + ->ifTrue(isExpression()) + ->then(asExpression()) + ->end() + ) ->append((new Parameters())->getConfigTreeBuilder()->getRootNode()) ->append((new FastMap\Configuration('merge'))->getConfigTreeBuilder()->getRootNode()) ->end() diff --git a/src/Configuration/Lookup.php b/src/Configuration/Lookup.php index 531cb4a..7e7bb39 100644 --- a/src/Configuration/Lookup.php +++ b/src/Configuration/Lookup.php @@ -35,7 +35,12 @@ public function getConfigTreeBuilder(): TreeBuilder return $data; }) ->end() - ->append((new Query())->getConfigTreeBuilder()->getRootNode()) + ->append((new Query())->getConfigTreeBuilder()->getRootNode() + ->validate() + ->ifTrue(isExpression()) + ->then(asExpression()) + ->end() + ) ->append((new Parameters())->getConfigTreeBuilder()->getRootNode()) ->children() ->append((new FastMap\Configuration('merge'))->getConfigTreeBuilder()->getRootNode()) @@ -66,7 +71,12 @@ private function getConditionalTreeBuilder(): TreeBuilder ->then(asExpression()) ->end() ->end() - ->append((new Query())->getConfigTreeBuilder()->getRootNode()) + ->append((new Query())->getConfigTreeBuilder()->getRootNode() + ->validate() + ->ifTrue(isExpression()) + ->then(asExpression()) + ->end() + ) ->append((new Parameters())->getConfigTreeBuilder()->getRootNode()) ->append((new FastMap\Configuration('merge'))->getConfigTreeBuilder()->getRootNode()) ->end() diff --git a/src/Configuration/Parameters.php b/src/Configuration/Parameters.php index 80d6b27..e9f271e 100644 --- a/src/Configuration/Parameters.php +++ b/src/Configuration/Parameters.php @@ -9,6 +9,7 @@ use function Kiboko\Component\SatelliteToolbox\Configuration\asExpression; use function Kiboko\Component\SatelliteToolbox\Configuration\isExpression; +use function Kiboko\Component\SatelliteToolbox\Configuration\mutuallyExclusiveFields; class Parameters implements ConfigurationInterface { @@ -24,9 +25,21 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->useAttributeAsKey('key', false) ->arrayPrototype() + ->validate() + ->always(mutuallyExclusiveFields('value', 'from')) + ->end() + ->validate() + ->ifTrue(fn (array $data) => !\array_key_exists('value', $data) && !\array_key_exists('from', $data)) + ->thenInvalid('Your configuration should either contain the "value" or the "from" key.') + ->end() ->children() ->scalarNode('value') - ->isRequired() + ->validate() + ->ifTrue(isExpression()) + ->then(asExpression()) + ->end() + ->end() + ->scalarNode('from') ->validate() ->ifTrue(isExpression()) ->then(asExpression()) diff --git a/src/Factory/Loader.php b/src/Factory/Loader.php index 2dce588..d36cfd6 100644 --- a/src/Factory/Loader.php +++ b/src/Factory/Loader.php @@ -59,80 +59,146 @@ public function compile(array $config): SQL\Factory\Repository\Loader if (\array_key_exists('parameters', $config)) { foreach ($config['parameters'] as $key => $parameter) { - match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { - 'integer' => $loader->addIntegerParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'boolean' => $loader->addBooleanParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'date' => $loader->addDateParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'datetime' => $loader->addDateTimeParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'json' => $loader->addJSONParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'binary' => $loader->addBinaryParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - default => $loader->addStringParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - }; - } - } - } else { - $loader = new SQL\Builder\ConditionalLoader(); - - foreach ($config['conditional'] as $alternative) { - $alternativeLoaderBuilder = new SQL\Builder\AlternativeLoader( - compileValueWhenExpression($this->interpreter, $alternative['query']) - ); - - if (\array_key_exists('parameters', $alternative)) { - foreach ($alternative['parameters'] as $key => $parameter) { + if (\array_key_exists('from', $parameter) && isset($parameter['from'])) { match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { - 'integer' => $alternativeLoaderBuilder->addIntegerParam( + 'integer' => $loader->addIntegerParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'boolean' => $loader->addBooleanParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'date' => $loader->addDateParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'datetime' => $loader->addDateTimeParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'json' => $loader->addJSONParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'binary' => $loader->addBinaryParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + default => $loader->addStringParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + }; + } else { + match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { + 'integer' => $loader->addIntegerParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), - 'boolean' => $alternativeLoaderBuilder->addBooleanParam( + 'boolean' => $loader->addBooleanParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), - 'date' => $alternativeLoaderBuilder->addDateParam( + 'date' => $loader->addDateParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), - 'datetime' => $alternativeLoaderBuilder->addDateTimeParam( + 'datetime' => $loader->addDateTimeParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), - 'json' => $alternativeLoaderBuilder->addJSONParam( + 'json' => $loader->addJSONParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), - 'binary' => $alternativeLoaderBuilder->addBinaryParam( + 'binary' => $loader->addBinaryParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), - default => $alternativeLoaderBuilder->addStringParam( + default => $loader->addStringParam( $key, compileValueWhenExpression($this->interpreter, $parameter['value']), ), }; } } + } + } else { + $loader = new SQL\Builder\ConditionalLoader(); + + foreach ($config['conditional'] as $alternative) { + $alternativeLoaderBuilder = new SQL\Builder\AlternativeLoader( + compileValueWhenExpression($this->interpreter, $alternative['query']) + ); + + if (\array_key_exists('parameters', $alternative)) { + foreach ($alternative['parameters'] as $key => $parameter) { + if (\array_key_exists('from', $parameter) && isset($parameter['from'])) { + match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { + 'integer' => $alternativeLoaderBuilder->addIntegerParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + 'boolean' => $alternativeLoaderBuilder->addBooleanParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + 'date' => $alternativeLoaderBuilder->addDateParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + 'datetime' => $alternativeLoaderBuilder->addDateTimeParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + 'json' => $alternativeLoaderBuilder->addJSONParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + 'binary' => $alternativeLoaderBuilder->addBinaryParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + default => $alternativeLoaderBuilder->addStringParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']) + ), + }; + } else { + match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { + 'integer' => $alternativeLoaderBuilder->addIntegerParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + 'boolean' => $alternativeLoaderBuilder->addBooleanParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + 'date' => $alternativeLoaderBuilder->addDateParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + 'datetime' => $alternativeLoaderBuilder->addDateTimeParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + 'json' => $alternativeLoaderBuilder->addJSONParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + 'binary' => $alternativeLoaderBuilder->addBinaryParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + default => $alternativeLoaderBuilder->addStringParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']) + ), + }; + } + } + } $loader->addAlternative( compileValueWhenExpression($this->interpreter, $alternative['condition']), diff --git a/src/Factory/Lookup.php b/src/Factory/Lookup.php index f30159a..3ac6f19 100644 --- a/src/Factory/Lookup.php +++ b/src/Factory/Lookup.php @@ -83,50 +83,38 @@ public function compile(array $config): SQL\Factory\Repository\Lookup if (\array_key_exists('parameters', $config)) { foreach ($config['parameters'] as $key => $parameter) { - match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { - 'integer' => $alternativeBuilder->addIntegerParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'boolean' => $alternativeBuilder->addBooleanParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'date' => $alternativeBuilder->addDateParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'datetime' => $alternativeBuilder->addDateTimeParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'json' => $alternativeBuilder->addJSONParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - 'binary' => $alternativeBuilder->addBinaryParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - default => $alternativeBuilder->addStringParam( - $key, - compileValueWhenExpression($this->interpreter, $parameter['value']), - ), - }; - } - } - - $this->merge($alternativeBuilder, $config); - } else { - $lookup = new SQL\Builder\ConditionalLookup(); - - foreach ($config['conditional'] as $alternative) { - $alternativeBuilder = new SQL\Builder\AlternativeLookup( - compileValueWhenExpression($this->interpreter, $alternative['query']) - ); - - if (\array_key_exists('parameters', $alternative)) { - foreach ($alternative['parameters'] as $key => $parameter) { + if (\array_key_exists('from', $parameter) && isset($parameter['from'])) { + match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { + 'integer' => $alternativeBuilder->addIntegerParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'boolean' => $alternativeBuilder->addBooleanParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'date' => $alternativeBuilder->addDateParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'datetime' => $alternativeBuilder->addDateTimeParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'json' => $alternativeBuilder->addJSONParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'binary' => $alternativeBuilder->addBinaryParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + default => $alternativeBuilder->addStringParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + }; + } else { match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { 'integer' => $alternativeBuilder->addIntegerParam( $key, @@ -159,6 +147,84 @@ public function compile(array $config): SQL\Factory\Repository\Lookup }; } } + } + + $this->merge($alternativeBuilder, $config); + } else { + $lookup = new SQL\Builder\ConditionalLookup(); + + foreach ($config['conditional'] as $alternative) { + $alternativeBuilder = new SQL\Builder\AlternativeLookup( + compileValueWhenExpression($this->interpreter, $alternative['query']) + ); + + if (\array_key_exists('parameters', $alternative)) { + foreach ($alternative['parameters'] as $key => $parameter) { + if (\array_key_exists('from', $parameter) && isset($parameter['from'])) { + match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { + 'integer' => $alternativeBuilder->addIntegerParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'boolean' => $alternativeBuilder->addBooleanParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'date' => $alternativeBuilder->addDateParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'datetime' => $alternativeBuilder->addDateTimeParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'json' => $alternativeBuilder->addJSONParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + 'binary' => $alternativeBuilder->addBinaryParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + default => $alternativeBuilder->addStringParamList( + $key, + compileValueWhenExpression($this->interpreter, $parameter['from']), + ), + }; + } else { + match (\array_key_exists('type', $parameter) ? $parameter['type'] : null) { + 'integer' => $alternativeBuilder->addIntegerParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + 'boolean' => $alternativeBuilder->addBooleanParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + 'date' => $alternativeBuilder->addDateParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + 'datetime' => $alternativeBuilder->addDateTimeParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + 'json' => $alternativeBuilder->addJSONParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + 'binary' => $alternativeBuilder->addBinaryParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + default => $alternativeBuilder->addStringParam( + $key, + compileValueWhenExpression($this->interpreter, $parameter['value']), + ), + }; + } + } + } $lookup->addAlternative( compileValueWhenExpression($this->interpreter, $alternative['condition']),