diff --git a/.editorconfig b/.editorconfig index 9866c39..d2e3f8e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,6 @@ insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true + +[*.json] +indent_size = 2 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 14ecdf0..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# Changelog - -All notable changes to `temporal-bridge` will be documented in this file. - -## 1.0.0 - 202X-XX-XX - -- initial release diff --git a/README.md b/README.md index 2a6af50..f55d6dc 100644 --- a/README.md +++ b/README.md @@ -9,531 +9,24 @@ [Temporal](https://temporal.io/) is the simple, scalable open source way to write and run reliable cloud applications. -## Temporal screencasts -[](https://youtu.be/goulj2CRNOY) - ## Requirements Make sure that your server is configured with following PHP version and extensions: - PHP 8.1+ -- Spiral framework 3.0+ - -## Installation - -You can install the package via composer: - -```bash -composer require spiral/temporal-bridge -``` - -After package install you need to register bootloader from the package. - -```php -protected const LOAD = [ - // ... - \Spiral\TemporalBridge\Bootloader\TemporalBridgeBootloader::class, -]; -``` - -> Note: if you are using [`spiral-packages/discoverer`](https://github.com/spiral-packages/discoverer), -> you don't need to register bootloader by yourself. - -#### Configuration - -The package is already configured by default, use these features only if you need to change the default configuration. -The package provides the ability to configure `address`, `namespace`, `defaultWorker`, `workers` parameters. -Create file `app/config/temporal.php` and configure options. For example: - -```php -declare(strict_types=1); - -use Temporal\Worker\WorkerFactoryInterface; -use Temporal\Worker\WorkerOptions; - -return [ - 'address' => env('TEMPORAL_ADDRESS', 'localhost:7233'), - 'namespace' => 'App\\Workflow', - 'defaultWorker' => WorkerFactoryInterface::DEFAULT_TASK_QUEUE, - 'workers' => [ - 'workerName' => WorkerOptions::new() - ], -]; -``` - -#### RoadRunner configuration - -Add `temporal` plugin section in your RoadRunner `rr.yaml` config: - -```yaml -temporal: - address: localhost:7233 - activities: - num_workers: 10 -``` - -#### Temporal - -You can run temporal server via docker by using the example below: - -> You can find official docker compose files here https://github.com/temporalio/docker-compose - -```yaml -version: '3.5' - -services: - postgresql: - container_name: temporal-postgresql - image: postgres:13 - environment: - POSTGRES_PASSWORD: temporal - POSTGRES_USER: temporal - ports: - - 5432:5432 - - temporal: - container_name: temporal - image: temporalio/auto-setup:1.14.2 - depends_on: - - postgresql - environment: - - DB=postgresql - - DB_PORT=5432 - - POSTGRES_USER=temporal - - POSTGRES_PWD=temporal - - POSTGRES_SEEDS=postgresql - - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml - ports: - - 7233:7233 - volumes: - - ./temporal:/etc/temporal/config/dynamicconfig - - temporal-web: - container_name: temporal-web - image: temporalio/web:1.13.0 - depends_on: - - temporal - environment: - - TEMPORAL_GRPC_ENDPOINT=temporal:7233 - - TEMPORAL_PERMIT_WRITE_API=true - ports: - - 8088:8088 -``` - -> Please make sure that a configuration file for temporal server exists. -> `mkdir temporal && touch temporal/development.yaml` - -## Creating workflow - -You are able to create a new workflow via console command: - -```bash -php app.php temporal:make-workflow MySuperWorkflow -``` - -The command will generate the following files with default namespace `App\Workflow`: - -``` -project/ - src/ - Workflow/ - MySuperWorkflow/ - MySuperWorkflowInterface - MySuperWorkflow -``` - -> You can redefine default namespace via `app/config/temporal.php` config file. - -#### Workflow with activity classes - -```bash -php app.php temporal:make-workflow MySuperWorkflow --with-activity -``` - -``` -project/ - src/ - Workflow/ - MySuperWorkflow/ - ... - MySuperWorkflowHandlerInterface - MySuperWorkflowHandler -``` - -#### Workflow with handler classes - -```bash -php app.php temporal:make-workflow MySuperWorkflow --with-handler -``` - -``` -project/ - src/ - Workflow/ - MySuperWorkflow/ - ... - MySuperWorkflowActivityInterface - MySuperWorkflowActivity -``` - -> You can mixin options `--with-activity --with-handler` - -#### Workflow method name definition - -```bash -temporal:make-workflow PingSite -m ping -``` - -```php -#[WorkflowInterface] -interface PingSiteWorkflowInterface -{ - #[WorkflowMethod] - public function ping(string $name): \Generator; -} -``` - -#### Workflow method parameters definition - -```bash -temporal:make-workflow PingSite ... -p url:string -p name:string -``` - -```php -#[WorkflowInterface] -interface PingSiteWorkflowInterface -{ - #[WorkflowMethod] - public function ping(string $url, string $name): \Generator; -} -``` - -#### Workflow query methods definition - -```bash -temporal:make-workflow PingSite ... -r getStatusCode -r getHeaders:array -``` - -```php -#[WorkflowInterface] -interface PingSiteWorkflowInterface -{ - #[WorkflowMethod] - public function ping(...): \Generator; - - #[QueryMethod] - function getStatusCode(): string; - - #[QueryMethod] - function getHeaders(): array; -} -``` - -#### Workflow with namespace definition - -```bash -temporal:make-workflow Domain\\MyPackage\\MoneyTransfer ... -s withdraw -s deposit -``` - -## Creating workflow from presets - -The package provides the ability to create predefined Workflows. Presets for the package can be provided via third-party -packages. - -**Example of usage** - -```bash -php app.php temporal:make-preset subscribtion-trial CustomerTrialSubscription -``` - -A preset will create all necessary classes. - -> You can show list of available presets using the console command `php app.php temporal:presets` - -#### Creating a preset - -A preset class should implement `Spiral\TemporalBridge\Preset\PresetInterface` and should have an -attribute `Spiral\TemporalBridge\Preset\WorkflowPreset` - -```php -use Spiral\TemporalBridge\Generator\WorkflowInterfaceGenerator; -use Spiral\TemporalBridge\Generator\SignalWorkflowGenerator; -use Spiral\TemporalBridge\Generator\ActivityInterfaceGenerator; -use Spiral\TemporalBridge\Generator\ActivityGenerator; -use Spiral\TemporalBridge\Generator\HandlerInterfaceGenerator; -use Spiral\TemporalBridge\Generator\HandlerGenerator; -use Spiral\TemporalBridge\Preset\PresetInterface; -use Spiral\TemporalBridge\Preset\WorkflowPreset; - -#[WorkflowPreset('signal')] -final class SignalWorkflow implements PresetInterface -{ - public function getDescription(): ?string - { - return 'Workflow with signals'; - } - - public function generators(Context $context): array - { - $generators = [ - 'WorkflowInterface' => new WorkflowInterfaceGenerator(), - 'Workflow' => new SignalWorkflowGenerator(), - ]; - - if ($context->hasActivity()) { - $generators = \array_merge($generators, [ - 'ActivityInterface' => new ActivityInterfaceGenerator(), - 'Activity' => new ActivityGenerator(), - ]); - } - - if ($context->hasHandler()) { - $generators = \array_merge($generators, [ - 'HandlerInterface' => new HandlerInterfaceGenerator(), - 'Handler' => new HandlerGenerator(), - ]); - } - - return $generators; - } -} -``` - -**Please note: If you are using `WorkflowPreset` you have to add a directory with presets to tokenizer.** - -```php -use Spiral\Tokenizer\Bootloader\TokenizerBootloader; - -class MyBootloader extends \Spiral\Boot\Bootloader\Bootloader -{ - protected const DEPENDENCIES = [ - TokenizerBootloader::class - ]; - - public function start(TokenizerBootloader $tokenizer) - { - $tokenizer->addDirectory(__DIR__..'/presets'); - } -} -``` - -You can omit `WorkflowPreset` attribute and register your preset via Bootloader - -```php -use Spiral\TemporalBridge\Preset\PresetRegistryInterface; - -class MyBootloader extends \Spiral\Boot\Bootloader\Bootloader -{ - public function start(PresetRegistryInterface $registry) - { - $registry->register('signal', new SignalWorkflow()); - } -} -``` - -#### Workflow signal methods definition - -```bash -temporal:make-workflow MoneyTransfer ... -s withdraw -s deposit -``` - -```php -#[WorkflowInterface] -interface MoneyTransferWorkflowInterface -{ - #[WorkflowMethod] - public function ping(...): \Generator; - - #[SignalMethod] - function withdraw(): void; - - #[SignalMethod] - function deposit(): void; -} -``` - -> You may discover available workflow samples [here](https://github.com/temporalio/samples-php) - -## Usage - -Configure temporal address via env variables `.env` - -``` -TEMPORAL_ADDRESS=127.0.0.1:7233 -``` - -### Running workflow - -```php -class PingController -{ - public function ping(StoreRequest $request, PingSiteHandler $handler): void - { - $this->handler->handle( - $request->url, - $request->name - ); - } -} -``` - -## Running workflows and activities with different task queue - -Add a `Spiral\TemporalBridge\Attribute\AssignWorker` attribute to your Workflow or Activity with the `name` of the worker. -This Workflow or Activity will be processed by the specified worker. - -**Workflow example:** - -```php - [ - 'worker1' => WorkerOptions::new() - ], -]; -``` - -Or via application bootloader - -```php -register( - 'worker1', - WorkerOptions::new()->... - ); - } -} -``` - -### Custom data converters - -Temporal SDK hsa an ability to define custom data converters - -By default it uses the following list of data converters: - - - `Temporal\DataConverter\NullConverter` - - `Temporal\DataConverter\BinaryConverter` - - `Temporal\DataConverter\ProtoJsonConverter` - - `Temporal\DataConverter\JsonConverter` - -If you want to specify custom list of data converters you need to bind your own implementation for -`Temporal\DataConverter\DataConverterInterface` via container. - -```php -use Spiral\Boot\Bootloader\Bootloader; -use Temporal\DataConverter\DataConverter; -use Temporal\DataConverter\DataConverterInterface; - -class AppBootloader extends Bootloader -{ - protected const SINGLETONS = [ - DataConverterInterface::class => [self::class, 'initDataConverter'], - ]; - - protected function initDataConverter(): DataConverterInterface - { - return new DataConverter( - new \Temporal\DataConverter\NullConverter(), - new \Temporal\DataConverter\BinaryConverter(), - new \App\DataConverter\JmsSerializerConverter(), - new \Temporal\DataConverter\ProtoJsonConverter(), - new \Temporal\DataConverter\JsonConverter(), - ); - } -} -``` - -## Testing - -```bash -composer test -``` - -## Changelog - -Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. - -## Contributing - -Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. +- Spiral Framework 3.0+ -## Security Vulnerabilities +## Documentation, Installation, and Usage Instructions -Please review [our security policy](../../security/policy) on how to report security vulnerabilities. +See the [documentation](https://spiral.dev/docs/temporal-configuration) for detailed installation and usage instructions. -## Credits +## Useful links -- [butschster](https://github.com/spiral) -- [All Contributors](../../contributors) +- [Temporal screencasts](https://youtu.be/goulj2CRNOY) +- [Fault tolerant workflow orchestration on PHP](https://www.youtube.com/watch?v=pdxHkIqX62A) +- [Fault tolerant workflow orchestration on PHP (rus)](https://www.youtube.com/watch?v=mNsjdTnanA4) +- [Temporal PHP SDK](https://github.com/temporalio/sdk-php/) ## License -The MIT License (MIT). Please see [License File](LICENSE) for more information. +MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information. Maintained by [Spiral Scout](https://spiralscout.com). diff --git a/composer.json b/composer.json index 393d0d9..1f49803 100644 --- a/composer.json +++ b/composer.json @@ -8,13 +8,31 @@ "workflow", "temporal" ], - "homepage": "https://github.com/spiral/temporal-bridge", + "homepage": "https://spiral.dev", + "support": { + "issues": "https://github.com/spiral/temporal-bridge/issues", + "source": "https://github.com/spiral/temporal-bridge", + "docs": "https://spiral.dev/docs", + "forum": "https://forum.spiral.dev", + "chat": "https://discord.gg/V6EK4he" + }, "license": "MIT", "authors": [ { - "name": "butschster", - "email": "butschster@gmail.com", - "role": "Developer" + "name": "Anton Titov (wolfy-j)", + "email": "wolfy-j@spiralscout.com" + }, + { + "name": "Pavel Butchnev (butschster)", + "email": "pavel.buchnev@spiralscout.com" + }, + { + "name": "Aleksei Gagarin (roxblnfk)", + "email": "alexey.gagarin@spiralscout.com" + }, + { + "name": "Maksim Smakouz (msmakouz)", + "email": "maksim.smakouz@spiralscout.com" } ], "require": { @@ -22,13 +40,13 @@ "spiral/boot": "^3.0", "spiral/attributes": "^2.8 || ^3.0", "spiral/tokenizer": "^3.0", + "spiral/scaffolder": "^3.0", "spiral/roadrunner-bridge": "^2.0 || ^3.0", - "nette/php-generator": "^4.0", "temporal/sdk": "^1.3 || ^2.0" }, "require-dev": { "spiral/framework": "^3.0", - "spiral/testing": "^2.0", + "spiral/testing": "^2.6", "vimeo/psalm": "^5.17" }, "autoload": { @@ -42,9 +60,15 @@ "Spiral\\TemporalBridge\\Tests\\": "tests/src" } }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/roadrunner-server" + } + ], "scripts": { "test": "vendor/bin/phpunit", - "psalm": "vendor/bin/psalm --config=psalm.xml ./src" + "psalm": "vendor/bin/psalm --no-cache --config=psalm.xml ./src" }, "config": { "sort-packages": true, diff --git a/psalm.xml b/psalm.xml index 0200c83..d4a2702 100644 --- a/psalm.xml +++ b/psalm.xml @@ -8,22 +8,17 @@ findUnusedCode="false" > - + - + - + - + + - - - - - - - + diff --git a/src/Bootloader/PrototypeBootloader.php b/src/Bootloader/PrototypeBootloader.php index 734ad93..29bbb0e 100644 --- a/src/Bootloader/PrototypeBootloader.php +++ b/src/Bootloader/PrototypeBootloader.php @@ -6,18 +6,19 @@ use Spiral\Boot\Bootloader\Bootloader; use Spiral\Prototype\Bootloader\PrototypeBootloader as BasePrototypeBootloader; -use Spiral\TemporalBridge\WorkflowManagerInterface; use Temporal\Client\WorkflowClientInterface; class PrototypeBootloader extends Bootloader { - protected const DEPENDENCIES = [ - BasePrototypeBootloader::class, - ]; + public function defineDependencies(): array + { + return [ + BasePrototypeBootloader::class, + ]; + } public function boot(BasePrototypeBootloader $prototype): void { $prototype->bindProperty('workflow', WorkflowClientInterface::class); - $prototype->bindProperty('workflowManager', WorkflowManagerInterface::class); } } diff --git a/src/Bootloader/ScaffolderBootloader.php b/src/Bootloader/ScaffolderBootloader.php new file mode 100644 index 0000000..678ac89 --- /dev/null +++ b/src/Bootloader/ScaffolderBootloader.php @@ -0,0 +1,51 @@ +configureCommands($console); + $this->configureDeclarations($scaffolder); + } + + private function configureCommands(ConsoleBootloader $console): void + { + $console->addCommand(WorkflowCommand::class); + $console->addCommand(ActivityCommand::class); + } + + private function configureDeclarations(BaseScaffolderBootloader $scaffolder): void + { + $scaffolder->addDeclaration(WorkflowDeclaration::TYPE, [ + 'namespace' => 'Endpoint\\Temporal\\Workflow', + 'postfix' => 'Workflow', + 'class' => WorkflowDeclaration::class, + ]); + + $scaffolder->addDeclaration(ActivityDeclaration::TYPE, [ + 'namespace' => 'Endpoint\\Temporal\\Activity', + 'postfix' => 'Activity', + 'class' => ActivityDeclaration::class, + ]); + } +} diff --git a/src/Bootloader/TemporalBridgeBootloader.php b/src/Bootloader/TemporalBridgeBootloader.php index 7c2fb86..1555eab 100644 --- a/src/Bootloader/TemporalBridgeBootloader.php +++ b/src/Bootloader/TemporalBridgeBootloader.php @@ -16,16 +16,11 @@ use Spiral\RoadRunnerBridge\Bootloader\RoadRunnerBootloader; use Spiral\TemporalBridge\Commands; use Spiral\TemporalBridge\Config\TemporalConfig; +use Spiral\TemporalBridge\DeclarationLocator; use Spiral\TemporalBridge\DeclarationLocatorInterface; use Spiral\TemporalBridge\Dispatcher; -use Spiral\TemporalBridge\Preset\PresetRegistry; -use Spiral\TemporalBridge\Preset\PresetRegistryInterface; use Spiral\TemporalBridge\WorkersRegistry; use Spiral\TemporalBridge\WorkersRegistryInterface; -use Spiral\TemporalBridge\Workflow\WorkflowManager; -use Spiral\TemporalBridge\WorkflowManagerInterface; -use Spiral\TemporalBridge\WorkflowPresetLocator; -use Spiral\TemporalBridge\WorkflowPresetLocatorInterface; use Spiral\Tokenizer\ClassesInterface; use Temporal\Client\ClientOptions; use Temporal\Client\GRPC\ServiceClient; @@ -40,40 +35,39 @@ class TemporalBridgeBootloader extends Bootloader { - protected const SINGLETONS = [ - WorkflowPresetLocatorInterface::class => [self::class, 'initWorkflowPresetLocator'], - WorkflowManagerInterface::class => WorkflowManager::class, - WorkerFactoryInterface::class => [self::class, 'initWorkerFactory'], - DeclarationLocatorInterface::class => [self::class, 'initDeclarationLocator'], - WorkflowClientInterface::class => [self::class, 'initWorkflowClient'], - WorkersRegistryInterface::class => [self::class, 'initWorkersRegistry'], - PresetRegistryInterface::class => PresetRegistry::class, - DataConverterInterface::class => [self::class, 'initDataConverter'], - ]; + public function defineDependencies(): array + { + return [ + ConsoleBootloader::class, + RoadRunnerBootloader::class, + ScaffolderBootloader::class, + ]; + } - protected const DEPENDENCIES = [ - ConsoleBootloader::class, - RoadRunnerBootloader::class, - ]; + public function defineSingletons(): array + { + return [ + WorkerFactoryInterface::class => [self::class, 'initWorkerFactory'], + DeclarationLocatorInterface::class => [self::class, 'initDeclarationLocator'], + WorkflowClientInterface::class => [self::class, 'initWorkflowClient'], + WorkersRegistryInterface::class => [self::class, 'initWorkersRegistry'], + DataConverterInterface::class => [self::class, 'initDataConverter'], + ]; + } public function __construct( - private readonly ConfiguratorInterface $config + private readonly ConfiguratorInterface $config, ) { } public function init( AbstractKernel $kernel, EnvironmentInterface $env, - ConsoleBootloader $console, - FactoryInterface $factory + FactoryInterface $factory, ): void { $this->initConfig($env); $kernel->addDispatcher($factory->make(Dispatcher::class)); - - $console->addCommand(Commands\MakeWorkflowCommand::class); - $console->addCommand(Commands\MakePresetCommand::class); - $console->addCommand(Commands\PresetListCommand::class); } public function addWorkerOptions(string $worker, WorkerOptions $options): void @@ -81,38 +75,27 @@ public function addWorkerOptions(string $worker, WorkerOptions $options): void $this->config->modify(TemporalConfig::CONFIG, new Append('workers', $worker, $options)); } - protected function initWorkflowPresetLocator( - FactoryInterface $factory, - ClassesInterface $classes - ): WorkflowPresetLocatorInterface { - return new WorkflowPresetLocator( - factory: $factory, - classes: $classes, - reader: new AttributeReader() - ); - } - protected function initConfig(EnvironmentInterface $env): void { $this->config->setDefaults( TemporalConfig::CONFIG, [ 'address' => $env->get('TEMPORAL_ADDRESS', '127.0.0.1:7233'), - 'namespace' => 'App\\Workflow', + 'namespace' => 'App\\Endpoint\\Temporal\\Workflow', 'defaultWorker' => (string)$env->get('TEMPORAL_TASK_QUEUE', WorkerFactoryInterface::DEFAULT_TASK_QUEUE), 'workers' => [], - ] + ], ); } protected function initWorkflowClient( TemporalConfig $config, - DataConverterInterface $dataConverter + DataConverterInterface $dataConverter, ): WorkflowClientInterface { return WorkflowClient::create( serviceClient: ServiceClient::create($config->getAddress()), options: (new ClientOptions())->withNamespace($config->getTemporalNamespace()), - converter: $dataConverter + converter: $dataConverter, ); } @@ -122,27 +105,27 @@ protected function initDataConverter(): DataConverterInterface } protected function initWorkerFactory( - DataConverterInterface $dataConverter + DataConverterInterface $dataConverter, ): WorkerFactoryInterface { return new WorkerFactory( dataConverter: $dataConverter, - rpc: Goridge::create() + rpc: Goridge::create(), ); } protected function initDeclarationLocator( - ClassesInterface $classes + ClassesInterface $classes, ): DeclarationLocatorInterface { - return new \Spiral\TemporalBridge\DeclarationLocator( + return new DeclarationLocator( classes: $classes, - reader: new AttributeReader() + reader: new AttributeReader(), ); } protected function initWorkersRegistry( WorkerFactoryInterface $workerFactory, FinalizerInterface $finalizer, - TemporalConfig $config + TemporalConfig $config, ): WorkersRegistryInterface { return new WorkersRegistry($workerFactory, $finalizer, $config); } diff --git a/src/Commands/MakePresetCommand.php b/src/Commands/MakePresetCommand.php deleted file mode 100644 index 7172b56..0000000 --- a/src/Commands/MakePresetCommand.php +++ /dev/null @@ -1,60 +0,0 @@ -getPresets() as $name => $preset) { - $registry->register($name, $preset); - } - - $context = $this->getContext(); - $presetName = $this->argument('preset'); - - $preset = $registry->findByName($presetName); - $preset->init($context); - $generators = $preset->generators($context); - - if ($generators === []) { - $this->error(\sprintf('Generators for preset [%s] are not found.', $presetName)); - return self::INVALID; - } - - if ($this->verifyExistsWorkflow($context)) { - return self::SUCCESS; - } - - \assert($this->output instanceof OutputInterface); - - $generator->generate( - $this->output, - $context, - $generators - ); - - return self::SUCCESS; - } -} diff --git a/src/Commands/MakeWorkflowCommand.php b/src/Commands/MakeWorkflowCommand.php deleted file mode 100644 index 5b97bfd..0000000 --- a/src/Commands/MakeWorkflowCommand.php +++ /dev/null @@ -1,71 +0,0 @@ -getContext(); - - if ($this->verifyExistsWorkflow($context)) { - return self::SUCCESS; - } - - \assert($this->output instanceof OutputInterface); - - $generator->generate( - $this->output, - $context, - $this->defineGenerators($context) - ); - - return self::SUCCESS; - } - - private function defineGenerators(Context $context): array - { - $generators = [ - 'WorkflowInterface' => new WorkflowInterfaceGenerator(), - 'Workflow' => new WorkflowGenerator(), - ]; - - if ($context->hasActivity()) { - $generators = \array_merge($generators, [ - 'ActivityInterface' => new ActivityInterfaceGenerator(), - 'Activity' => new ActivityGenerator(), - ]); - } - - if ($context->hasHandler()) { - $generators = \array_merge($generators, [ - 'HandlerInterface' => new HandlerInterfaceGenerator(), - 'Handler' => new HandlerGenerator(), - ]); - } - - return $generators; - } -} diff --git a/src/Commands/PresetListCommand.php b/src/Commands/PresetListCommand.php deleted file mode 100644 index 569a007..0000000 --- a/src/Commands/PresetListCommand.php +++ /dev/null @@ -1,55 +0,0 @@ -getPresets() as $name => $preset) { - $registry->register($name, $preset); - } - - $list = $registry->getList(); - if ($list === []) { - $this->info('No available Workflow presets found.'); - - return self::SUCCESS; - } - - \assert($this->output instanceof OutputInterface); - - $table = new Table($this->output); - - $table->setHeaders(['name', 'description']); - - foreach ($list as $name => $preset) { - $table->addRow([$name, \implode("\n", \str_split( - (string) $preset->getDescription(), self::DESCRIPTION_LENGTH) - )]); - } - - $table->render(); - - $this->newLine(); - $this->info('Use the command below to make a workflow: '); - $this->comment('php app.php temporal:make-preset preset-name MySuperWorkflow'); - $this->newLine(); - - return self::SUCCESS; - } -} diff --git a/src/Commands/Scaffolder/ActivityCommand.php b/src/Commands/Scaffolder/ActivityCommand.php new file mode 100644 index 0000000..6537e0b --- /dev/null +++ b/src/Commands/Scaffolder/ActivityCommand.php @@ -0,0 +1,57 @@ +createDeclaration(ActivityDeclaration::class, [ + 'activityName' => $this->activityName, + ]); + + if ($this->worker !== WorkerFactoryInterface::DEFAULT_TASK_QUEUE) { + $declaration->assignWorker($this->worker); + } + + foreach ($this->methods as $method) { + if (\str_contains($method, ':')) { + $array = \explode(separator: ':', string: $method, limit: 2); + \assert(\count($array) === 2); + [$method, $type] = $array; + } else { + $type = 'mixed'; + } + + $declaration->addMethod($method, $type); + } + + $this->writeDeclaration($declaration); + + return self::SUCCESS; + } +} diff --git a/src/Commands/Scaffolder/WorkflowCommand.php b/src/Commands/Scaffolder/WorkflowCommand.php new file mode 100644 index 0000000..ebf42bf --- /dev/null +++ b/src/Commands/Scaffolder/WorkflowCommand.php @@ -0,0 +1,64 @@ +createDeclaration(WorkflowDeclaration::class, [ + 'workflowName' => $this->workflowName, + ]); + + if ($this->worker !== WorkerFactoryInterface::DEFAULT_TASK_QUEUE) { + $declaration->assignWorker($this->worker); + } + + foreach ($this->queryMethods as $method) { + if (\str_contains($method, ':')) { + $array = \explode(separator: ':', string: $method, limit: 2); + \assert(\count($array) === 2); + [$method, $type] = $array; + } else { + $type = 'mixed'; + } + + $declaration->addQueryMethod($method, $type); + } + + foreach ($this->signalMethods as $name) { + $declaration->addSignalMethod($name); + } + + $this->writeDeclaration($declaration); + + return self::SUCCESS; + } +} diff --git a/src/Commands/WithContext.php b/src/Commands/WithContext.php deleted file mode 100644 index d03670c..0000000 --- a/src/Commands/WithContext.php +++ /dev/null @@ -1,149 +0,0 @@ -getPath()) || $this->option('force')) { - return false; - } - - $question = new QuestionHelper(); - - \assert($this->output instanceof OutputInterface); - \assert($this->input instanceof InputInterface); - - return ! $question->ask( - $this->input, - $this->output, - new ConfirmationQuestion( - \sprintf( - 'Workflow with given name [%s] exists. Some files can be overwritten. Would you like to continue?', - $context->getBaseClass() - ) - ) - ); - } - - public function getContext(): Context - { - \assert($this->container instanceof ContainerInterface); - - $config = $this->container->get(TemporalConfig::class); - \assert($config instanceof TemporalConfig); - - $dirs = $this->container->get(DirectoriesInterface::class); - \assert($dirs instanceof DirectoriesInterface); - - $name = $this->getNameInput(); - $namespace = $this->getNamespaceFromClass($name) ?? $config->getDefaultNamespace(); - $className = $this->qualifyClass($name, $namespace); - $namespace = $namespace.'\\'.$className; - - $context = (new Context( - $dirs->get('app').'src/Workflow/'.$className.'/', - $namespace, - $className, - )) - ->withActivityMethods(Utils::parseMethods((array)$this->option('activity'))) - ->withMethodParameters(Utils::parseParameters((array)$this->option('param'))) - ->withHandlerMethod($this->option('method') ?? 'handle') - ->withSignalMethods(Utils::parseMethods((array)$this->option('signal'))) - ->withQueryMethods(Utils::parseMethods((array)$this->option('query'))); - - if ($this->option('with-handler') ?? false) { - $context = $context->withHandler(); - } - - if ($this->option('with-activity') ?? false) { - $context = $context->withActivity(); - } - - if ($this->option('scheduled') ?? false) { - $context = $context->withCronSchedule(); - } - - return $context; - } - - protected function getPath(string $namespace, string $appDir): string - { - if (\str_starts_with($namespace, 'App')) { - $namespace = \str_replace('App', 'src', $namespace); - } - - return $appDir.\str_replace('\\', '/', $namespace).'/'; - } - - private function getNameInput(): string - { - return \trim($this->argument('name')); - } - - private function getNamespaceFromClass(string $name): ?string - { - $namespace = \trim(\implode('\\', \array_slice(\explode('\\', $name), 0, -1)), '\\'); - - return ! empty($namespace) ? $namespace : null; - } - - private function qualifyClass(string $name, string $namespace): string - { - $name = \str_replace('/', '\\', $name); - $name = \str_replace(['-', '_', '.'], ' ', $name); - $name = \str_replace(' ', '', $name); - if (\str_starts_with($name, $namespace)) { - $name = \str_replace($namespace, '', $name); - } - - $name = \ltrim($name, '\\/'); - - return \ucwords($name); - } -} diff --git a/src/Config/TemporalConfig.php b/src/Config/TemporalConfig.php index 3a84343..78a28b4 100644 --- a/src/Config/TemporalConfig.php +++ b/src/Config/TemporalConfig.php @@ -14,7 +14,7 @@ final class TemporalConfig extends InjectableConfig protected array $config = [ 'address' => 'localhost:7233', - 'namespace' => 'App\\Workflow', + 'namespace' => 'App\\Endpoint\\Temporal\\Workflow', 'temporalNamespace' => 'default', 'defaultWorker' => WorkerFactoryInterface::DEFAULT_TASK_QUEUE, 'workers' => [], diff --git a/src/Exception/PresetNotFoundException.php b/src/Exception/PresetNotFoundException.php deleted file mode 100644 index c6b9501..0000000 --- a/src/Exception/PresetNotFoundException.php +++ /dev/null @@ -1,10 +0,0 @@ -getClass()); - $class->addImplement($context->getClassInterfaceWithNamespace()); - - $method = $class->addMethod('__construct')->setPublic(); - - $method->addPromotedParameter('logger') - ->setPrivate() - ->setType(LoggerInterface::class); - - foreach ($context->getActivityMethods() as $method) { - $class->addMember($method); - $method - ->addBody( - \sprintf( - '$this->logger->info(\'%s\', func_get_args());', - 'Something special happens here.', - ) - ) - ->addBody("\nreturn 'Success';"); - } - - return new PhpCodePrinter( - $namespace - ->add($class) - ->addUse(LoggerInterface::class), - $context - ); - } -} diff --git a/src/Generator/ActivityInterfaceGenerator.php b/src/Generator/ActivityInterfaceGenerator.php deleted file mode 100644 index 50573a4..0000000 --- a/src/Generator/ActivityInterfaceGenerator.php +++ /dev/null @@ -1,34 +0,0 @@ -getClass(), $namespace); - $class->addAttribute(ActivityInterface::class, ['prefix' => $context->getBaseClass('.')]); - - foreach ($context->getActivityMethods() as $method) { - $class->addMember($method); - $method - ->setBody('') - ->addAttribute(ActivityMethod::class); - } - - return new PhpCodePrinter( - $namespace - ->add($class) - ->addUse(ActivityInterface::class) - ->addUse(ActivityMethod::class), - $context - ); - } -} diff --git a/src/Generator/Context.php b/src/Generator/Context.php deleted file mode 100644 index 63368eb..0000000 --- a/src/Generator/Context.php +++ /dev/null @@ -1,287 +0,0 @@ - */ - private array $activityMethods = []; - /** @var array */ - private array $signalMethods = []; - /** @var array */ - private array $queryMethods = []; - /** @var array */ - private array $handlerParameters = []; - private string $classPostfix = ''; - - public function __construct( - private readonly string $directory, - private readonly string $namespace, - private readonly string $baseClass, - ) { - } - - public function withClassPostfix(string $postfix): self - { - $this->classPostfix = \str_ends_with($this->baseClass, 'Workflow') - ? \str_replace('Workflow', '', $postfix) - : $postfix; - - return $this; - } - - public function withCronSchedule(): self - { - $this->scheduled = true; - - return $this; - } - - /** - * @param Parameter[] $parameters - */ - public function withMethodParameters(array $parameters): self - { - $this->handlerParameters = $parameters; - - return $this; - } - - /** - * @param Method[] $methods - */ - public function withSignalMethods(array $methods): self - { - $this->signalMethods = $methods; - - return $this; - } - - /** - * @param Method[] $methods - */ - public function withQueryMethods(array $methods): self - { - $this->queryMethods = $methods; - - return $this; - } - - /** - * @param non-empty-string $name - */ - public function withHandlerMethod(string $name): self - { - $this->handlerMethod = $name; - - return $this; - } - - public function withActivity(): self - { - $this->withActivity = true; - - return $this; - } - - /** - * @param Method[] $methods - */ - public function withActivityMethods(array $methods): self - { - $this->activityMethods = $methods; - - return $this; - } - - public function withHandler(): self - { - $this->withHandler = true; - - return $this; - } - - /** - * Get the namespace - */ - public function getNamespace(): string - { - return $this->namespace; - } - - /** - * Get base class name without the namespace - */ - public function getBaseClass(string $postfix = ''): string - { - return $this->baseClass.$postfix; - } - - /** - * Get base class interface without the namespace - */ - public function getBaseClassInterface(string $postfix = ''): string - { - return $this->getBaseClass($postfix).self::INTERFACE; - } - - /** - * Get base class interface with the namespace - */ - public function getBaseClassInterfaceWithNamespace(string $postfix = ''): string - { - return $this->getNamespace().'\\'.$this->getBaseClassInterface($postfix); - } - - /** - * Get current class name without the namespace - */ - public function getClass(string $postfix = ''): string - { - if ($this->classPostfix !== '' && str_ends_with($this->baseClass, $this->classPostfix)) { - return $this->getBaseClass($postfix); - } - - return $this->baseClass.$this->classPostfix.$postfix; - } - - /** - * Get current class name with the namespace - */ - public function getClassWithNamespace(string $postfix = ''): string - { - return $this->getNamespace().'\\'.$this->getClass($postfix); - } - - /** - * Get current class interface without the namespace - */ - public function getClassInterface(string $postfix = ''): string - { - return $this->getClass($postfix).self::INTERFACE; - } - - /** - * Get current class interface with the namespace - */ - public function getClassInterfaceWithNamespace(string $postfix = ''): string - { - return $this->getNamespace().'\\'.$this->getClassInterface($postfix); - } - - public function getPath(): string - { - return $this->directory; - } - - /** - * Get current class file full path - */ - public function getClassPath(): string - { - return $this->directory.$this->getClass().'.php'; - } - - /** - * Get required query methods - * @return Method[] - */ - public function getSignalMethods(): array - { - return \array_map(fn($method) => clone $method, $this->signalMethods); - } - - /** - * Get required query methods - * @return Method[] - */ - public function getQueryMethods(): array - { - return \array_map(fn($method) => clone $method, $this->queryMethods); - } - - /** - * Check if workflow should be scheduled by cron expression - */ - public function isScheduled(): bool - { - return $this->scheduled; - } - - /** - * Check if workflow should have activity classes - */ - public function hasActivity(): bool - { - return $this->withActivity; - } - - /** - * Check if activity has defined methods - */ - public function hasActivityMethods(): bool - { - return $this->activityMethods !== []; - } - - /** - * Get activity methods - * @return Method[] - */ - public function getActivityMethods(): array - { - if (! $this->hasActivityMethods()) { - return [$this->handlerMethod => $this->getHandlerMethod()]; - } - - return \array_map(fn($method) => clone $method, $this->activityMethods); - } - - /** - * Check if workflow should have handler classes - */ - public function hasHandler(): bool - { - return $this->withHandler; - } - - /** - * Check if default handler method is changed - */ - public function isHandlerMethodNameChanged(): bool - { - return $this->handlerMethod !== self::HANDLER_METHOD; - } - - /** - * Get workflow handler method - */ - public function getHandlerMethod(): Method - { - return (new Method($this->handlerMethod)) - ->setPublic() - ->setReturnType('\Generator') - ->setParameters($this->handlerParameters); - } - - - /** - * Get workflow handler method name - */ - public function getHandlerMethodName(): string - { - return $this->handlerMethod; - } -} diff --git a/src/Generator/FileGeneratorInterface.php b/src/Generator/FileGeneratorInterface.php deleted file mode 100644 index b97aa38..0000000 --- a/src/Generator/FileGeneratorInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -writeln('Generating workflow files...'); - - foreach ($generators as $name => $generator) { - $c = clone $context; - - $generator->generate( - $c->withClassPostfix($name), - new PhpNamespace($c->getNamespace()) - )->print($this->files); - - $output->writeln(\sprintf( - 'Class [%s] successfully generated.', - $c->getClassWithNamespace() - )); - } - } -} diff --git a/src/Generator/HandlerGenerator.php b/src/Generator/HandlerGenerator.php deleted file mode 100644 index 92b8024..0000000 --- a/src/Generator/HandlerGenerator.php +++ /dev/null @@ -1,132 +0,0 @@ -getClass()); - $class->addImplement($context->getClassInterfaceWithNamespace()); - - $constructor = $class - ->addMethod('__construct') - ->setPublic(); - - $constructor->addPromotedParameter('manager') - ->setPrivate() - ->setType(WorkflowManagerInterface::class); - - $constructor->addPromotedParameter('logger') - ->setPrivate() - ->setType(LoggerInterface::class); - - $class->addMember($handlerMethod = $context->getHandlerMethod()); - $handlerMethod->setReturnType(RunningWorkflow::class); - - $handlerMethod->addBody($this->generateWorkflowInitialization($context)); - $handlerMethod->addBody($this->generateWorkflowSettingBody($context)); - $handlerMethod->addBody($this->generateRunScriptBody($context)); - - return new PhpCodePrinter( - $namespace - ->add($class) - ->addUse(RunningWorkflow::class) - ->addUse(WorkflowExecutionAlreadyStartedException::class) - ->addUse(WorkflowIdReusePolicy::class) - ->addUse(LoggerInterface::class) - ->addUse(WorkflowManagerInterface::class), - $context - ); - } - - private function generateWorkflowInitialization(Context $context): string - { - $workflowClassName = $context->getBaseClass('WorkflowInterface'); - - if ($context->isScheduled()) { - return \sprintf( - <<<'BODY' -$workflow = $this->manager - ->createScheduled( - %s::class, - '%s' - ); -BODY - , - $workflowClassName, - '* * * * *' - ); - } - - return \sprintf( - <<<'BODY' -$workflow = $this->manager - ->create(%s::class); -BODY - , - $workflowClassName - ); - } - - private function generateWorkflowSettingBody(Context $context): string - { - return <<<'BODY' -// $workflow->assignId( -// 'operation-id', -// WorkflowIdReusePolicy::WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY -// ); - -// $workflow->withWorkflowRunTimeout(\Carbon\CarbonInterval::minutes(10)) -// ->withWorkflowTaskTimeout(\Carbon\CarbonInterval::minute()) -// ->withWorkflowExecutionTimeout(\Carbon\CarbonInterval::minutes(5)); - -// $workflow->maxRetryAttempts(5) -// ->backoffRetryCoefficient(1.5) -// ->initialRetryInterval(\Carbon\CarbonInterval::seconds(5)) -// ->maxRetryInterval(\Carbon\CarbonInterval::seconds(20)); -BODY - ; - } - - /** - * @param Context $context - * @return string - */ - private function generateRunScriptBody(Context $context): string - { - $runArgs = Utils::buildMethodArgs($context->getHandlerMethod()->getParameters()); - - return \sprintf( - <<<'BODY' -try { - $run = $workflow->run(%s); - - $this->logger->info('Workflow [%s] has been run', [ - 'id' => $run->getExecution()->getID() - ]); - - return $run; -} catch (WorkflowExecutionAlreadyStartedException $e) { - $this->logger->error('Workflow has been already started.', [ - 'name' => $workflow->getWorkflowType() - ]); - - throw $e; -} -BODY, - $runArgs, - $context->getBaseClass() - ); - } -} diff --git a/src/Generator/HandlerInterfaceGenerator.php b/src/Generator/HandlerInterfaceGenerator.php deleted file mode 100644 index c39eda6..0000000 --- a/src/Generator/HandlerInterfaceGenerator.php +++ /dev/null @@ -1,26 +0,0 @@ -getClass(), $namespace); - - $class->addMember($handler = $context->getHandlerMethod()); - $handler->setReturnType(RunningWorkflow::class); - - return new PhpCodePrinter( - $namespace - ->add($class) - ->addUse(RunningWorkflow::class), - $context - ); - } -} diff --git a/src/Generator/PhpCodePrinter.php b/src/Generator/PhpCodePrinter.php deleted file mode 100644 index a81be96..0000000 --- a/src/Generator/PhpCodePrinter.php +++ /dev/null @@ -1,34 +0,0 @@ -addNamespace($this->namespace); - $file->setStrictTypes(); - - $printer = new PsrPrinter; - - $files->write( - filename: $this->context->getClassPath(), - data: $printer->printFile($file), - ensureDirectory: true - ); - } -} diff --git a/src/Generator/Utils.php b/src/Generator/Utils.php deleted file mode 100644 index be1efc8..0000000 --- a/src/Generator/Utils.php +++ /dev/null @@ -1,159 +0,0 @@ - $type) { - $method->addParameter($parameter)->setType($type); - } - } - - public static function generateWorkflowSignalMethods(array $signalMethods, ClassType $class): void - { - foreach ($signalMethods as $method) { - $params = null; - if (\str_contains($method, ',')) { - /** @psalm-suppress PossiblyUndefinedArrayOffset */ - [$method, $params] = \explode(',', $method, 2); - } - - $method = $class->addMethod($method) - ->setPublic() - ->setReturnType('void'); - - if ($params) { - static::addParameters(static::parseParameters(explode(',', $params)), $method); - } - - if ($class->isInterface()) { - $method->addAttribute(SignalMethod::class); - } else { - $method->addBody('// Do something special.'); - } - } - } - - public static function generateWorkflowQueryMethods(array $queryMethods, ClassType $class): void - { - foreach ($queryMethods as $method => $type) { - $method = $class->addMethod($method) - ->setPublic() - ->setReturnType($type); - - if ($class->isInterface()) { - $method->addAttribute(QueryMethod::class); - } else { - $method->addBody('// Query something special.'); - } - } - } - - /** - * @param string[] $methods - * @return Method[] - */ - public static function parseMethods(array $methods): array - { - $result = []; - - foreach ($methods as $method) { - $params = ''; - if (\str_contains($method, ',')) { - /** @psalm-suppress PossiblyUndefinedArrayOffset */ - [$method, $params] = \explode(',', $method, 2); - } - - if (\str_contains($method, ':')) { - /** @psalm-suppress PossiblyUndefinedArrayOffset */ - [$method, $type] = \explode(':', $method, 2); - } - - $type ??= 'void'; - - $result[$method] = (new Method($method)) - ->setPublic() - ->setReturnType($type); - - $result[$method]->setParameters(self::parseParameters(\explode(',', $params))); - } - - return $result; - } - - /** - * @param string[] $parameters - * @return Parameter[] - */ - public static function parseParameters(array $parameters): array - { - $params = []; - - foreach ($parameters as $param) { - $type = null; - if (\str_contains($param, ':')) { - /** @psalm-suppress PossiblyUndefinedArrayOffset */ - [$param, $type] = \explode(':', $param, 2); - } - - if (empty($param)) { - continue; - } - - $type ??= 'string'; - $params[$param] = (new Parameter($param))->setType($type); - } - - return $params; - } - - public static function buildMethodArgs(array $args): string - { - return \implode(', ', \array_map(fn($param) => '$'.$param, \array_keys($args))); - } - - public static function initializeActivityProperty(ClassType $class, Context $context): void - { - $activityClass = $context->getBaseClassInterface('Activity'); - $activityName = $context->getBaseClass().'.handle'; - - $class->addProperty('activity') - ->setPrivate() - ->setType(ActivityProxy::class) - ->addComment( - $context->hasActivity() - ? \sprintf('@var %s|%s', 'ActivityProxy', $activityClass) - : \sprintf('@var %s', 'ActivityProxy') - ); - - if ($class->hasMethod('__construct')) { - $constructor = $class->getMethod('__construct'); - } else { - $constructor = $class->addMethod('__construct')->setPublic(); - } - - $constructor->addBody( - \sprintf( - <<<'BODY' -$this->activity = Workflow::newActivityStub( - %s, - ActivityOptions::new() - ->withScheduleToCloseTimeout(CarbonInterval::seconds(10)) -); -BODY, - $context->hasActivity() ? $activityClass.'::class' : "'$activityName'" - ) - ); - } -} diff --git a/src/Generator/WorkflowGenerator.php b/src/Generator/WorkflowGenerator.php deleted file mode 100644 index 9951f72..0000000 --- a/src/Generator/WorkflowGenerator.php +++ /dev/null @@ -1,69 +0,0 @@ -getClass()); - $class->addImplement($context->getClassInterfaceWithNamespace()); - - Utils::initializeActivityProperty($class, $context); - - $class->addMember($handlerMethod = $context->getHandlerMethod()); - - if (\count($context->getActivityMethods()) === 1) { - foreach ($context->getActivityMethods() as $method) { - $handlerMethod->addBody( - \sprintf( - 'return yield $this->activity->%s(%s);', - $method->getName(), - Utils::buildMethodArgs($method->getParameters()) - ) - ); - } - } else { - foreach ($context->getActivityMethods() as $method) { - $handlerMethod->addBody('$result = [];'); - - $handlerMethod->addBody( - \sprintf( - '$result[] = yield $this->activity->%s(%s);', - $method->getName(), - Utils::buildMethodArgs($method->getParameters()) - ) - ); - - $handlerMethod->addBody('return $result;'); - } - } - - foreach ($context->getSignalMethods() as $method) { - $class->addMember($method->addBody('// Signal about something special.')); - } - - foreach ($context->getQueryMethods() as $method) { - $class->addMember($method->addBody('// Query something special.')); - } - - return new PhpCodePrinter( - $namespace - ->add($class) - ->addUse(CarbonInterval::class) - ->addUse(ActivityOptions::class) - ->addUse(ActivityProxy::class) - ->addUse(Workflow::class), - $context - ); - } -} diff --git a/src/Generator/WorkflowInterfaceGenerator.php b/src/Generator/WorkflowInterfaceGenerator.php deleted file mode 100644 index cae4994..0000000 --- a/src/Generator/WorkflowInterfaceGenerator.php +++ /dev/null @@ -1,44 +0,0 @@ -getClass(); - - $class = new \Nette\PhpGenerator\InterfaceType($className, $namespace); - - $class->addAttribute(WorkflowInterface::class); - - $class->addMember($method = $context->getHandlerMethod()); - $method->setBody('')->addAttribute(WorkflowMethod::class); - - foreach ($context->getSignalMethods() as $method) { - $class->addMember($method->setBody('')->addAttribute(SignalMethod::class)); - } - - foreach ($context->getQueryMethods() as $method) { - $class->addMember($method->setBody('')->addAttribute(QueryMethod::class)); - } - - return new PhpCodePrinter( - $namespace - ->add($class) - ->addUse(QueryMethod::class) - ->addUse(SignalMethod::class) - ->addUse(WorkflowInterface::class) - ->addUse(WorkflowMethod::class), - $context - ); - } -} diff --git a/src/Preset/PresetInterface.php b/src/Preset/PresetInterface.php deleted file mode 100644 index 735bcc2..0000000 --- a/src/Preset/PresetInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -presets[$name] = $preset; - } - - public function findByName(string $name): PresetInterface - { - if (! isset($this->presets[$name])) { - throw new PresetNotFoundException( - \sprintf( - 'Preset with given name [%s] is not defined.', - $name - ) - ); - } - - return $this->presets[$name]; - } - - public function getList(): array - { - return $this->presets; - } -} diff --git a/src/Preset/PresetRegistryInterface.php b/src/Preset/PresetRegistryInterface.php deleted file mode 100644 index aa79412..0000000 --- a/src/Preset/PresetRegistryInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ - public function getList(): array; - - /** - * Find an exists preset by name - * @throws PresetNotFoundException - */ - public function findByName(string $name): PresetInterface; -} diff --git a/src/Preset/WorkflowPreset.php b/src/Preset/WorkflowPreset.php deleted file mode 100644 index 258d376..0000000 --- a/src/Preset/WorkflowPreset.php +++ /dev/null @@ -1,17 +0,0 @@ -namespace->addUse(ActivityInterface::class); + $this->namespace->addUse(ActivityMethod::class); + $this->namespace->addUse(PromiseInterface::class); + + $classAttributeArgs = []; + if ($this->activityName !== null) { + $classAttributeArgs['name'] = $this->activityName; + } + + $this->class->addAttribute(ActivityInterface::class, $classAttributeArgs); + } + + public function assignWorker(string $worker): void + { + $this->namespace->addUse(AssignWorker::class); + $this->class->addAttribute(AssignWorker::class, ['name' => $worker]); + } + + public function addMethod(string $name, string $returnType): void + { + $this->class + ->addMethod($name) + ->setPublic() + ->addAttribute(ActivityMethod::class, ['name' => $name]) + ->setReturnType($returnType) + ->addComment('@return PromiseInterface<' . $returnType . '>') + ->setBody('// TODO: Implement activity method'); + } +} diff --git a/src/Scaffolder/Declaration/WorkflowDeclaration.php b/src/Scaffolder/Declaration/WorkflowDeclaration.php new file mode 100644 index 0000000..93b7784 --- /dev/null +++ b/src/Scaffolder/Declaration/WorkflowDeclaration.php @@ -0,0 +1,76 @@ +namespace->addUse(WorkflowInterface::class); + $this->namespace->addUse(WorkflowMethod::class); + + $this->class->addAttribute(WorkflowInterface::class); + + $methodAttributeArgs = []; + if ($this->workflowName !== null) { + $methodAttributeArgs['name'] = $this->workflowName; + } + + $this->class + ->addMethod('handle') + ->setPublic() + ->setComment('Handle workflow') + ->addAttribute(WorkflowMethod::class, $methodAttributeArgs) + ->setBody('// TODO: Implement handle method'); + } + + public function assignWorker(string $worker): void + { + $this->namespace->addUse(AssignWorker::class); + $this->class->addAttribute(AssignWorker::class, ['name' => $worker]); + } + + public function addQueryMethod(string $name, string $returnType): void + { + $this->namespace->addUse(QueryMethod::class); + $this->class + ->addMethod($name) + ->setPublic() + ->addAttribute(QueryMethod::class) + ->setReturnType($returnType) + ->setBody('// TODO: Implement query method'); + } + + public function addSignalMethod(string $name): void + { + $this->namespace->addUse(SignalMethod::class); + $this->class + ->addMethod($name) + ->setPublic() + ->addAttribute(SignalMethod::class) + ->setReturnType('void') + ->setBody('// TODO: Implement signal method'); + } +} diff --git a/src/Workflow/RunningWorkflow.php b/src/Workflow/RunningWorkflow.php deleted file mode 100644 index 1323c2a..0000000 --- a/src/Workflow/RunningWorkflow.php +++ /dev/null @@ -1,20 +0,0 @@ -workflow, $name], $arguments); - } -} diff --git a/src/Workflow/Workflow.php b/src/Workflow/Workflow.php deleted file mode 100644 index 48ef8f7..0000000 --- a/src/Workflow/Workflow.php +++ /dev/null @@ -1,168 +0,0 @@ - $class - */ - public function __construct( - private readonly WorkflowClientInterface $client, - private WorkflowOptions $options, - private readonly string $class, - private readonly string $type, - ) { - } - - public function getId(): ?string - { - return $this->options->workflowId; - } - - public function backoffRetryCoefficient(float $coefficient): self - { - $this->retryOptions = $this->getRetryOptions() - ->withBackoffCoefficient($coefficient); - - return $this; - } - - public function maxRetryAttempts(int $attempts): self - { - $this->retryOptions = $this->getRetryOptions() - ->withMaximumAttempts($attempts); - - return $this; - } - - /** - * @param DateIntervalValue|null $interval - */ - public function maxRetryInterval($interval): self - { - $this->retryOptions = $this->getRetryOptions() - ->withMaximumInterval($interval); - - return $this; - } - - /** - * @param DateIntervalValue|null $interval - */ - public function initialRetryInterval($interval): self - { - $this->retryOptions = $this->getRetryOptions() - ->withInitialInterval($interval); - - return $this; - } - - /** - * Workflow id to use when starting. If not specified a UUID is generated. - * Note that it is dangerous as in case of client side retries no - * deduplication will happen based on the generated id. So prefer assigning - * business meaningful ids if possible. - */ - public function assignId(string $id, ?int $policy = null): self - { - if ($policy !== null) { - $this->withWorkflowIdReusePolicy($policy); - } - - return $this->withWorkflowId($id); - } - - /** - * Sends signal on start. - * @param mixed ...$args - */ - public function withSignal(string $name, ...$args): self - { - $this->signal = new WorkflowSignal($name, $args); - - return $this; - } - - /** - * Starts untyped and typed workflow stubs in async mode. - * @param mixed ...$args - */ - public function run(...$args): RunningWorkflow - { - if ($this->retryOptions && ! $this->options->retryOptions) { - $this->withRetryOptions($this->retryOptions); - } - - $workflow = $this->createStub(); - - if ($this->signal) { - $run = $this->client->startWithSignal( - workflow: $workflow, - signal: $this->signal->getName(), - signalArgs: $this->signal->getArgs(), - startArgs: $args - ); - } else { - $run = $this->client->start($workflow, ...$args); - } - - return new RunningWorkflow( - $this->client->newUntypedRunningWorkflowStub( - $run->getExecution()->getID(), - $run->getExecution()->getRunID(), - $this->type - ) - ); - } - - public function __call(string $name, array $arguments) - { - if (str_starts_with($name, 'with')) { - if (method_exists($this->options, $name)) { - $this->options = call_user_func_array([$this->options, $name], $arguments); - - return $this; - } - } - - if (method_exists($this->class, $name)) { - return call_user_func_array([$this->createStub(), $name], $arguments); - } - - throw new \BadMethodCallException(\sprintf('Method [%s] doesn\'t exist.', $name)); - } - - /** - * @return T - */ - private function createStub(): WorkflowProxy - { - return $this->client->newWorkflowStub($this->class, $this->options); - } - - private function getRetryOptions(): RetryOptions - { - if ($this->retryOptions) { - return $this->retryOptions; - } - - return $this->retryOptions = new RetryOptions(); - } -} diff --git a/src/Workflow/WorkflowManager.php b/src/Workflow/WorkflowManager.php deleted file mode 100644 index 8f03f77..0000000 --- a/src/Workflow/WorkflowManager.php +++ /dev/null @@ -1,93 +0,0 @@ -client, - $this->createOptions($id), - $class, - $this->getTypeFromWorkflowClass($class) - ); - } - - public function createScheduled(string $class, string $expression, ?string $id = null): Workflow - { - return $this->create($class, $id) - ->withCronSchedule($expression); - } - - public function getById( - string $id, - ?string $class = null, - ): RunningWorkflow { - $type = $class !== null ? $this->getTypeFromWorkflowClass($class) : null; - - return new RunningWorkflow( - $this->client->newUntypedRunningWorkflowStub( - workflowID: $id, - workflowType: $type - ) - ); - } - - private function createOptions(?string $id): WorkflowOptions - { - $options = new WorkflowOptions(); - - if ($id !== null) { - $options = $options->withWorkflowId($id); - } - - if ($this->defaultWorkflowExecutionTimeout !== null) { - $options = $options->withWorkflowExecutionTimeout( - $this->defaultWorkflowExecutionTimeout - ); - } - - if ($this->defaultWorkflowRunTimeout !== null) { - $options = $options->withWorkflowRunTimeout( - $this->defaultWorkflowRunTimeout - ); - } - - if ($this->defaultWorkflowTaskTimeout !== null) { - $options = $options->withWorkflowTaskTimeout( - $this->defaultWorkflowTaskTimeout - ); - } - - return $options; - } - - /** - * @param class-string $class - * @throws \ReflectionException - */ - private function getTypeFromWorkflowClass(string $class): string - { - return $this->reader->fromClass($class)->getID(); - } -} diff --git a/src/Workflow/WorkflowSignal.php b/src/Workflow/WorkflowSignal.php deleted file mode 100644 index 3cec02f..0000000 --- a/src/Workflow/WorkflowSignal.php +++ /dev/null @@ -1,24 +0,0 @@ -name; - } - - public function getArgs(): array - { - return $this->args; - } -} diff --git a/src/WorkflowManagerInterface.php b/src/WorkflowManagerInterface.php deleted file mode 100644 index d0702b8..0000000 --- a/src/WorkflowManagerInterface.php +++ /dev/null @@ -1,44 +0,0 @@ - $class - * @return RunningWorkflow|T|WorkflowStubInterface - */ - public function getById( - string $id, - ?string $class = null, - ): RunningWorkflow; - - /** - * @psalm-template T of object - * @param class-string $class - * @return T|Workflow|WorkflowOptions - */ - public function create( - string $class, - ?string $id = null - ): Workflow; - - /** - * @psalm-template T of object - * @param class-string $class - * @return T|Workflow|WorkflowOptions - */ - public function createScheduled( - string $class, - string $expression, - ?string $id = null - ): Workflow; -} diff --git a/src/WorkflowPresetLocator.php b/src/WorkflowPresetLocator.php deleted file mode 100644 index c2d2fbe..0000000 --- a/src/WorkflowPresetLocator.php +++ /dev/null @@ -1,41 +0,0 @@ -classes->getClasses() as $class) { - if ($attr = $this->reader->firstClassMetadata($class, WorkflowPreset::class)) { - if (! $class->implementsInterface(PresetInterface::class)) { - continue; - } - - $preset = $this->factory->make($class->getName()); - \assert($preset instanceof PresetInterface); - - $presets[$attr->name] = $preset; - } - } - - return $presets; - } -} diff --git a/src/WorkflowPresetLocatorInterface.php b/src/WorkflowPresetLocatorInterface.php deleted file mode 100644 index 1a77384..0000000 --- a/src/WorkflowPresetLocatorInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public function getPresets(): array; -} diff --git a/tests/src/Bootloader/PrototypeBootloaderTest.php b/tests/src/Bootloader/PrototypeBootloaderTest.php index 33fef54..0d082e7 100644 --- a/tests/src/Bootloader/PrototypeBootloaderTest.php +++ b/tests/src/Bootloader/PrototypeBootloaderTest.php @@ -25,6 +25,5 @@ public function testBindProperties(string $expected, string $property): void public function propertiesDataProvider(): \Traversable { yield [WorkflowClientInterface::class, 'workflow']; - yield [WorkflowManagerInterface::class, 'workflowManager']; } } diff --git a/tests/src/Bootloader/TemporalBridgeBootloaderTest.php b/tests/src/Bootloader/TemporalBridgeBootloaderTest.php index 9aa756e..f3e5604 100644 --- a/tests/src/Bootloader/TemporalBridgeBootloaderTest.php +++ b/tests/src/Bootloader/TemporalBridgeBootloaderTest.php @@ -4,22 +4,15 @@ namespace Spiral\TemporalBridge\Tests\Bootloader; -use Spiral\Attributes\ReaderInterface; use Spiral\TemporalBridge\Bootloader\TemporalBridgeBootloader; use Spiral\TemporalBridge\Config\TemporalConfig; use Spiral\Config\ConfigManager; use Spiral\Config\LoaderInterface; use Spiral\TemporalBridge\DeclarationLocator; use Spiral\TemporalBridge\DeclarationLocatorInterface; -use Spiral\TemporalBridge\Preset\PresetRegistry; -use Spiral\TemporalBridge\Preset\PresetRegistryInterface; use Spiral\TemporalBridge\Tests\TestCase; use Spiral\TemporalBridge\WorkersRegistry; use Spiral\TemporalBridge\WorkersRegistryInterface; -use Spiral\TemporalBridge\Workflow\WorkflowManager; -use Spiral\TemporalBridge\WorkflowManagerInterface; -use Spiral\TemporalBridge\WorkflowPresetLocator; -use Spiral\TemporalBridge\WorkflowPresetLocatorInterface; use Temporal\Client\WorkflowClient; use Temporal\Client\WorkflowClientInterface; use Temporal\DataConverter\DataConverter; @@ -30,24 +23,6 @@ class TemporalBridgeBootloaderTest extends TestCase { - public function testWorkflowPresetLocator() - { - $this->assertContainerBoundAsSingleton( - WorkflowPresetLocatorInterface::class, - WorkflowPresetLocator::class - ); - } - - public function testWorkflowManager() - { - $this->mockContainer(ReaderInterface::class); - - $this->assertContainerBoundAsSingleton( - WorkflowManagerInterface::class, - WorkflowManager::class - ); - } - public function testWorkerFactory() { $this->assertContainerBoundAsSingleton( @@ -80,14 +55,6 @@ public function testWorkflowClient() ); } - public function testPresetRegistry() - { - $this->assertContainerBoundAsSingleton( - PresetRegistryInterface::class, - PresetRegistry::class - ); - } - public function testWorkersRegistry(): void { $this->assertContainerBoundAsSingleton( diff --git a/tests/src/Commands/MakePresetCommandTest.php b/tests/src/Commands/MakePresetCommandTest.php deleted file mode 100644 index ffa10da..0000000 --- a/tests/src/Commands/MakePresetCommandTest.php +++ /dev/null @@ -1,64 +0,0 @@ -expectException(PresetNotFoundException::class); - $this->expectExceptionMessage('Preset with given name [foo] is not defined.'); - - $this->runCommand('temporal:make-preset', ['preset' => 'foo', 'name' => ' bar']); - } - - public function testPresetWithoutGeneratorsShouldShowErrorMessage(): void - { - $registry = $this->mockContainer(PresetRegistryInterface::class); - $preset = $this->mockContainer(PresetInterface::class); - - $registry->shouldReceive('findByName')->with('foo')->andReturn($preset); - - $preset->shouldReceive('init')->once(); - $preset->shouldReceive('generators')->once()->andReturn([]); - - $this->assertConsoleCommandOutputContainsStrings('temporal:make-preset', [ - 'preset' => 'foo', 'name' => ' bar' - ], [ - 'Generators for preset [foo] are not found.' - ]); - } - - public function testPresetShouldGenerateFiles(): void - { - $registry = $this->mockContainer(PresetRegistryInterface::class); - $preset = $this->mockContainer(PresetInterface::class); - $generator = $this->mockContainer(FileGeneratorInterface::class); - $printer = $this->mockContainer(PhpCodePrinter::class); - - $registry->shouldReceive('findByName')->with('foo')->andReturn($preset); - - $preset->shouldReceive('init')->once(); - $preset->shouldReceive('generators')->once()->andReturn([ - 'Baz' => $generator - ]); - $generator->shouldReceive('generate')->andReturn($printer); - $printer->shouldReceive('print'); - - $this->assertConsoleCommandOutputContainsStrings('temporal:make-preset', [ - 'preset' => 'foo', 'name' => ' bar' - ], [ - 'Generating workflow files...', - 'Class [App\Workflow\Bar\BarBaz] successfully generated.' - ]); - } -} diff --git a/tests/src/Commands/PresetListCommandTest.php b/tests/src/Commands/PresetListCommandTest.php deleted file mode 100644 index 5f8236a..0000000 --- a/tests/src/Commands/PresetListCommandTest.php +++ /dev/null @@ -1,36 +0,0 @@ -assertConsoleCommandOutputContainsStrings('temporal:presets', [], [ - 'No available Workflow presets found.' - ]); - } - - public function testPresetsShouldBeFound() - { - $registry = $this->mockContainer(PresetRegistryInterface::class); - $preset = $this->mockContainer(PresetInterface::class); - - $registry->shouldReceive('getList')->once()->andReturn([ - 'foo' => $preset - ]); - - $preset->shouldReceive('getDescription')->once()->andReturn('First preset description'); - - $this->assertConsoleCommandOutputContainsStrings('temporal:presets', [], [ - '| name | description |', - '| foo | First preset description |' - ]); - } -} diff --git a/tests/src/Commands/Scaffolder/ActivityCommandTest.php b/tests/src/Commands/Scaffolder/ActivityCommandTest.php new file mode 100644 index 0000000..280b91b --- /dev/null +++ b/tests/src/Commands/Scaffolder/ActivityCommandTest.php @@ -0,0 +1,187 @@ +files = $this->mockContainer(FilesInterface::class); + } + + public function testGenerate(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:activity', [ + 'name' => 'Payment', + ]); + } + + public function testGenerateWithAssignedWorker(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:activity', [ + 'name' => 'Payment', + '--worker' => 'scanner_service', + ]); + } + + public function testGenerateWithActivityName(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:activity', [ + 'name' => 'Payment', + '--activity-name' => 'payment', + ]); + } + + public function testGenerateWithMethods(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); + $this->assertSame( + <<<'PHP' + + */ + #[ActivityMethod(name: 'pay')] + public function pay(): mixed + { + // TODO: Implement activity method + } + + /** + * @return PromiseInterface + */ + #[ActivityMethod(name: 'refund')] + public function refund(): void + { + // TODO: Implement activity method + } + + /** + * @return PromiseInterface + */ + #[ActivityMethod(name: 'getPaymentStatus')] + public function getPaymentStatus(): bool + { + // TODO: Implement activity method + } +} + +PHP, + $body, + ); + return true; + }); + + $this->runCommand('create:activity', [ + 'name' => 'Payment', + '--method' => [ + 'pay', + 'refund:void', + 'getPaymentStatus:bool', + ] + ]); + } +} diff --git a/tests/src/Commands/Scaffolder/WorkflowCommandTest.php b/tests/src/Commands/Scaffolder/WorkflowCommandTest.php new file mode 100644 index 0000000..1a86f08 --- /dev/null +++ b/tests/src/Commands/Scaffolder/WorkflowCommandTest.php @@ -0,0 +1,265 @@ +files = $this->mockContainer(FilesInterface::class); + } + + public function testGenerate(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:workflow', [ + 'name' => 'Payment', + ]); + } + + public function testGenerateWithWorkflowName(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:workflow', [ + 'name' => 'Payment', + '--workflow-name' => 'PaymentWorkflow', + ]); + } + + public function testGenerateWithWorker(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:workflow', [ + 'name' => 'Payment', + '--worker' => 'test', + ]); + } + + public function testGenerateWithQueryMethods(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:workflow', [ + 'name' => 'Payment', + '--query-method' => [ + 'getPayment:string', + 'getTotal:int', + 'getLastTransaction', + ], + ]); + } + + public function testGenerateWithSignalMethods(): void + { + $this->files->shouldReceive('exists')->andReturnFalse(); + $this->files->shouldReceive('write')->once()->withArgs(function (string $path, string $body) { + $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); + $this->assertSame( + <<<'PHP' +runCommand('create:workflow', [ + 'name' => 'Payment', + '--signal-method' => [ + 'pay', + 'cancel', + ], + ]); + } +} diff --git a/tests/src/Config/TemporalConfigTest.php b/tests/src/Config/TemporalConfigTest.php index 6f56142..60068e2 100644 --- a/tests/src/Config/TemporalConfigTest.php +++ b/tests/src/Config/TemporalConfigTest.php @@ -24,7 +24,7 @@ public function testGetsDefaultNamespaceIfItNotSet(): void { $config = new TemporalConfig([]); - $this->assertSame('App\\Workflow', $config->getDefaultNamespace()); + $this->assertSame('App\\Endpoint\\Temporal\\Workflow', $config->getDefaultNamespace()); } public function testGetsDefaultTemporalNamespaceIfItNotSet(): void diff --git a/tests/src/Generator/ContextTest.php b/tests/src/Generator/ContextTest.php deleted file mode 100644 index c626c09..0000000 --- a/tests/src/Generator/ContextTest.php +++ /dev/null @@ -1,234 +0,0 @@ -context = new Context('src/app/', 'App\\Foo', 'Bar'); - } - - public function testWithCronSchedule(): void - { - $this->assertFalse($this->context->isScheduled()); - - $this->assertTrue($this->context->withCronSchedule()->isScheduled()); - } - - public function testWithActivity(): void - { - $this->assertFalse($this->context->hasActivity()); - - $this->assertTrue($this->context->withActivity()->hasActivity()); - } - - public function testWithHandler(): void - { - $this->assertFalse($this->context->hasHandler()); - - $this->assertTrue($this->context->withHandler()->hasHandler()); - } - - public function testGetsNamespace(): void - { - $this->assertSame( - 'App\\Foo', - $this->context->getNamespace() - ); - } - - public function testGetsPath(): void - { - $this->assertSame( - 'src/app/', - $this->context->getPath() - ); - } - - public function testGetsBaseClass(): void - { - $this->assertSame( - 'Bar', - $this->context->getBaseClass() - ); - - $this->assertSame( - 'BarBaz', - $this->context->getBaseClass('Baz') - ); - } - - public function testGetsClassPath(): void - { - $this->assertSame( - 'src/app/Bar.php', - $this->context->getClassPath() - ); - } - - public function testGetsBaseClassInterface(): void - { - $this->assertSame( - 'BarInterface', - $this->context->getBaseClassInterface() - ); - - $this->assertSame( - 'BarBazInterface', - $this->context->getBaseClassInterface('Baz') - ); - } - - public function testGetsBaseClassInterfaceWithNamespace(): void - { - $this->assertSame( - 'App\Foo\BarInterface', - $this->context->getBaseClassInterfaceWithNamespace() - ); - - $this->assertSame( - 'App\Foo\BarBazInterface', - $this->context->getBaseClassInterfaceWithNamespace('Baz') - ); - } - - public function testGetsClass(): void - { - $this->assertSame( - 'Bar', - $this->context->getClass() - ); - - $this->assertSame( - 'BarBaz', - $this->context->withClassPostfix('Baz')->getClass() - ); - - $this->assertSame( - 'Bar', - $this->context->withClassPostfix('Bar')->getClass() - ); - - $this->assertSame( - 'BarBazFoo', - $this->context->withClassPostfix('Baz')->getClass('Foo') - ); - } - - public function testGetsClassInterface(): void - { - $this->assertSame( - 'BarInterface', - $this->context->getClassInterface() - ); - - $this->assertSame( - 'BarBazInterface', - $this->context->withClassPostfix('Baz')->getClassInterface() - ); - - $this->assertSame( - 'BarInterface', - $this->context->withClassPostfix('Bar')->getClassInterface() - ); - - $this->assertSame( - 'BarBazFooInterface', - $this->context->withClassPostfix('Baz')->getClassInterface('Foo') - ); - } - - public function testGetsClassInterfaceWithNamespace(): void - { - $this->assertSame( - 'App\Foo\BarInterface', - $this->context->getClassInterfaceWithNamespace() - ); - - $this->assertSame( - 'App\Foo\BarBazInterface', - $this->context->withClassPostfix('Baz')->getClassInterfaceWithNamespace() - ); - - $this->assertSame( - 'App\Foo\BarInterface', - $this->context->withClassPostfix('Bar')->getClassInterfaceWithNamespace() - ); - - $this->assertSame( - 'App\Foo\BarBazFooInterface', - $this->context->withClassPostfix('Baz')->getClassInterfaceWithNamespace('Foo') - ); - } - - public function testGetsClassWithNamespace(): void - { - $this->assertSame( - 'App\Foo\Bar', - $this->context->getClassWithNamespace() - ); - - $this->assertSame( - 'App\Foo\BarFoo', - $this->context->getClassWithNamespace('Foo') - ); - - $this->assertSame( - 'App\Foo\BarBazFoo', - $this->context->withClassPostfix('Baz')->getClassWithNamespace('Foo') - ); - } - - public function testGetsHandlerMethodName(): void - { - $this->assertSame( - 'handle', - $this->context->getHandlerMethodName() - ); - - $this->assertFalse( - $this->context->isHandlerMethodNameChanged() - ); - - $this->assertSame( - 'foo', - $this->context->withHandlerMethod('foo')->getHandlerMethodName() - ); - - $this->assertTrue( - $this->context->isHandlerMethodNameChanged() - ); - } - - public function testGetsHandlerMethod(): void - { - $method = $this->context->getHandlerMethod(); - - $this->assertTrue($method->isPublic()); - - $this->assertSame( - '\Generator', - $method->getReturnType() - ); - - $this->assertSame( - 'handle', - $method->getName() - ); - - $this->assertSame( - 'foo', - $this->context->withHandlerMethod('foo')->getHandlerMethod()->getName() - ); - } -} diff --git a/tests/src/Generator/PhpCodePrinterTest.php b/tests/src/Generator/PhpCodePrinterTest.php deleted file mode 100644 index 37e4155..0000000 --- a/tests/src/Generator/PhpCodePrinterTest.php +++ /dev/null @@ -1,43 +0,0 @@ -addClass('Baz'); - - $printer = new PhpCodePrinter( - $namespace, - new Context('src/app/', 'App//Foo', 'Bar') - ); - - $files = $this->mockContainer(FilesInterface::class); - - $files->shouldReceive('write')->once()->withSomeOfArgs('src/app/Bar.php', <<print($files); - } -} diff --git a/tests/src/Generator/WorkflowTypesTest.php b/tests/src/Generator/WorkflowTypesTest.php deleted file mode 100644 index 8731035..0000000 --- a/tests/src/Generator/WorkflowTypesTest.php +++ /dev/null @@ -1,85 +0,0 @@ -dir = $this->getDirectoryByAlias('app').'src/Workflow/'; - } - - public function testMakesWorkflowWithoutActivityAndHandler() - { - $this->runCommand('temporal:make-workflow', [ - 'name' => 'PingSite', - ]); - - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflow.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflowInterface.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteActivity.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteActivityInterface.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteHandler.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteHandlerInterface.php'); - } - - public function testMakesWorkflowWithActivityWithoutHandler() - { - $this->runCommand('temporal:make-workflow', [ - 'name' => 'PingSite', - '--with-activity' => true, - ]); - - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflow.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflowInterface.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteActivity.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteActivityInterface.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteHandler.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteHandlerInterface.php'); - } - - public function testMakesWorkflowWithActivityAndHandler() - { - $this->runCommand('temporal:make-workflow', [ - 'name' => 'PingSite', - '--with-activity' => true, - '--with-handler' => true, - ]); - - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflow.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflowInterface.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteActivity.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteActivityInterface.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteHandler.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteHandlerInterface.php'); - } - - public function testMakesWorkflowWithoutActivityWithHandler() - { - $this->runCommand('temporal:make-workflow', [ - 'name' => 'PingSite', - '--with-handler' => true, - ]); - - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflow.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteWorkflowInterface.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteActivity.php'); - $this->assertFileDoesNotExist($this->dir.'PingSite/PingSiteActivityInterface.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteHandler.php'); - $this->assertFileExists($this->dir.'PingSite/PingSiteHandlerInterface.php'); - } - - protected function tearDown(): void - { - $this->cleanupDirectories($this->dir); - - parent::tearDown(); - } -} diff --git a/tests/src/Preset/PresetRegistryTest.php b/tests/src/Preset/PresetRegistryTest.php deleted file mode 100644 index c498c17..0000000 --- a/tests/src/Preset/PresetRegistryTest.php +++ /dev/null @@ -1,36 +0,0 @@ -assertCount(0, $registry->getList()); - - $registry->register('foo', $preset = \Mockery::mock(PresetInterface::class)); - - $this->assertCount(1, $registry->getList()); - - $this->assertSame($preset, $registry->findByName('foo')); - } - - public function testNotFoundPresetShouldThrowAnException(): void - { - $this->expectException(PresetNotFoundException::class); - $this->expectExceptionMessage('Preset with given name [foo] is not defined.'); - - $registry = new PresetRegistry(); - - $registry->findByName('foo'); - } -} diff --git a/tests/src/UtilsTest.php b/tests/src/UtilsTest.php deleted file mode 100644 index 79f8bbf..0000000 --- a/tests/src/UtilsTest.php +++ /dev/null @@ -1,59 +0,0 @@ -assertSame('userId', $parsed['userId']->getName()); - $this->assertSame('string', $parsed['userId']->getType()); - - $this->assertSame('name', $parsed['name']->getName()); - $this->assertSame('string', $parsed['name']->getType()); - - $this->assertSame('exit', $parsed['exit']->getName()); - $this->assertSame('bool', $parsed['exit']->getType()); - } - - public function testParseMethods(): void - { - $methods = [ - 'exit', - 'handler:string', - 'book:bool,name:string,exit:bool', - ]; - - $parsed = Utils::parseMethods($methods); - - $this->assertSame('exit', $parsed['exit']->getName()); - $this->assertSame('void', $parsed['exit']->getReturnType()); - $this->assertSame([], $parsed['exit']->getParameters()); - - $this->assertSame('handler', $parsed['handler']->getName()); - $this->assertSame('string', $parsed['handler']->getReturnType()); - $this->assertSame([], $parsed['handler']->getParameters()); - - $this->assertSame('book', $parsed['book']->getName()); - $this->assertSame('bool', $parsed['book']->getReturnType()); - $this->assertCount(2, $parsed['book']->getParameters()); - - $this->assertSame('name', $parsed['book']->getParameters()['name']->getName()); - $this->assertSame('string', $parsed['book']->getParameters()['name']->getType()); - - $this->assertSame('exit', $parsed['book']->getParameters()['exit']->getName()); - $this->assertSame('bool', $parsed['book']->getParameters()['exit']->getType()); - } -} diff --git a/tests/src/Workflow/WorkflowManagerTest.php b/tests/src/Workflow/WorkflowManagerTest.php deleted file mode 100644 index f2789f7..0000000 --- a/tests/src/Workflow/WorkflowManagerTest.php +++ /dev/null @@ -1,55 +0,0 @@ -manager = new WorkflowManager( - $this->client = m::mock(WorkflowClientInterface::class), - $this->reader = m::mock(WorkflowReader::class), - ); - } - - public function testCreatesWorkflowWithoutId(): void - { - $class = new \ReflectionClass(SimpleWorkflow::class); - - $this->reader->shouldReceive('fromClass') - ->with('foo') - ->andReturn(new WorkflowPrototype('foo', $class->getMethod('handle'), $class)); - - $this->assertInstanceOf(Workflow::class, $workflow = $this->manager->create('foo')); - $this->assertNotEmpty($workflow->getId()); - } - - public function testCreatesWorkflowWithId(): void - { - $class = new \ReflectionClass(SimpleWorkflow::class); - - $this->reader->shouldReceive('fromClass') - ->with('foo') - ->andReturn(new WorkflowPrototype('foo', $class->getMethod('handle'), $class)); - - $this->assertInstanceOf(Workflow::class, $workflow = $this->manager->create('foo', 'foo-id')); - $this->assertSame('foo-id', $workflow->getId()); - } -}