diff --git a/composer.json b/composer.json index 62babfe..bb31a26 100644 --- a/composer.json +++ b/composer.json @@ -27,5 +27,8 @@ }, "bin": [ "bin/slotcli" - ] + ], + "require-dev": { + "phpstan/phpstan": "^2.1" + } } diff --git a/composer.lock b/composer.lock index bdccf0f..1c7bdbe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c7b8d782317f7c0945290d495e1b425e", + "content-hash": "9dc3a3ca30d0b0d1698b2e4e26577e67", "packages": [ { "name": "guzzlehttp/guzzle", @@ -594,13 +594,14 @@ "source": { "type": "git", "url": "../slotdb-client-php", - "reference": "3b929684f9ceb58ea1e23328b12c2c3333e092a0" + "reference": "43d5db56cfc22486b00415079664b5fbc2392b4e" }, "require": { "psr/http-client": "^1.0" }, "require-dev": { - "guzzlehttp/guzzle": "^7.9" + "guzzlehttp/guzzle": "^7.9", + "phpstan/phpstan": "^2.1" }, "default-branch": true, "type": "library", @@ -619,7 +620,7 @@ } ], "description": "SlotDB PHP Client", - "time": "2025-03-10T15:43:56+00:00" + "time": "2025-03-13T16:14:01+00:00" }, { "name": "symfony/console", @@ -1270,7 +1271,66 @@ "time": "2024-11-13T13:31:26+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-03-09T09:30:48+00:00" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..a720b54 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 5 + + # Paths to include in the analysis + paths: + - src diff --git a/src/Application.php b/src/Application.php index 4bbfa01..960896e 100644 --- a/src/Application.php +++ b/src/Application.php @@ -2,13 +2,24 @@ namespace SlotDb\Cli; +use GuzzleHttp\Client; +use SlotDb\Client\SlotDbClient; + class Application extends \Symfony\Component\Console\Application { public function __construct() { parent::__construct("slotcli", "0.0.0"); + $psr = new Client(); + $client = new SlotDbClient($psr); + $this->add(new Command\ImportCommand()); $this->add(new Command\ExportCommand()); + + $this->add(new Command\Slot\SlotQueryCommand($client)); + $this->add(new Command\Slot\SlotFindCommand($client)); + $this->add(new Command\Slot\SlotSetCommand($client)); } + } diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 1cd6ee5..730d0b0 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -2,9 +2,14 @@ namespace SlotDb\Cli\Command; +use SlotDb\Cli\Application; use Symfony\Component\Console\Command\Command; abstract class BaseCommand extends Command { + public function getApplication(): Application + { + return parent::getApplication(); // @phpstan-ignore return.type + } } diff --git a/src/Command/Slot/SlotFindCommand.php b/src/Command/Slot/SlotFindCommand.php new file mode 100644 index 0000000..84ae00e --- /dev/null +++ b/src/Command/Slot/SlotFindCommand.php @@ -0,0 +1,67 @@ +addArgument("where", InputArgument::IS_ARRAY, "Conditions that are ANDed") + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + + $wheres = $input->getArgument("where"); + + $whereConds = []; + foreach ($wheres as $where) { + if (preg_match('<^(.+?)(=|\<=|\>=|\<|\>)(.+?)$>', $where, $match)) { + $c = $match[2]; + if ($c == '=') $c = ''; + $whereConds[$match[1]] = sprintf("%s%s", $c, $match[3]); + } + } + + $slots = $this->client->findSlots($whereConds); + + $keys = []; + foreach ($slots as $slot) { + $keys = array_merge($keys, array_keys($slot)); + } + $keys = array_unique($keys); + + $table = new Table($output); + $table->setHeaders($keys); + $table->setStyle('box'); + foreach ($slots as $slot) { + $row = []; + foreach ($keys as $key) { + $row[] = isset($slot[$key]) ? json_encode($slot[$key]) : null; + } + $table->addRow($row); + } + + $table->render(); + + return self::SUCCESS; + } +} diff --git a/src/Command/Slot/SlotQueryCommand.php b/src/Command/Slot/SlotQueryCommand.php new file mode 100644 index 0000000..00c8f0e --- /dev/null +++ b/src/Command/Slot/SlotQueryCommand.php @@ -0,0 +1,49 @@ +addArgument("slot", InputArgument::REQUIRED, "Slot ID") + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + + $slotId = $input->getArgument("slot"); + $slot = $this->client->querySlot($slotId); + + $len = 2 + max(array_map(mb_strlen(...), array_keys($slot))); + foreach ($slot as $k=>$v) { + $output->writeln( + sprintf( + str_starts_with($k,"_") + ? "%{$len}s: %s" + : "%{$len}s: %s", + $k, json_encode($v) + ) + ); + } + + return self::SUCCESS; + } +} diff --git a/src/Command/Slot/SlotSetCommand.php b/src/Command/Slot/SlotSetCommand.php new file mode 100644 index 0000000..7bc4639 --- /dev/null +++ b/src/Command/Slot/SlotSetCommand.php @@ -0,0 +1,62 @@ +addArgument("slot", InputArgument::REQUIRED, "Slot ID") + ->addArgument("update", InputArgument::IS_ARRAY, "key=value pairs to update") + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + + $slotId = $input->getArgument("slot"); + $updates = $input->getArgument("update"); + $props = []; + foreach ($updates as $update) { + if (str_contains($update, "=")) { + [$k,$v] = explode("=", $update, 2); + $v = json_decode($v) ?? $v; + $props[$k] = $v; + } else { + $props[$update] = null; + } + } + + $slot = $this->client->updateSlot($slotId, $props); + + $len = 2 + max(array_map(mb_strlen(...), array_keys($slot))); + foreach ($slot as $k=>$v) { + $output->writeln( + sprintf( + str_starts_with($k,"_") + ? "%{$len}s: %s" + : "%{$len}s: %s", + $k, json_encode($v) + ) + ); + } + + return self::SUCCESS; + } +}