From e0e3d49a380d8815db9415c8f68c7284106e9d5e Mon Sep 17 00:00:00 2001 From: butschster Date: Tue, 19 Dec 2023 13:45:13 +0400 Subject: [PATCH] Code refactoring 1. Removes code generation with interfaces, handlers, etc. Now there is only an ability to generate Workflow class and Activity class with methods. 2. Removes generator based on presets. 3. Removes WorkflowManager class. --- .editorconfig | 3 + CHANGELOG.md | 7 - README.md | 525 +----------------- composer.json | 40 +- psalm.xml | 25 +- src/Attribute/AssignWorker.php | 4 +- src/Bootloader/PrototypeBootloader.php | 11 +- src/Bootloader/ScaffolderBootloader.php | 51 ++ src/Bootloader/TemporalBridgeBootloader.php | 79 ++- src/Commands/MakePresetCommand.php | 60 -- src/Commands/MakeWorkflowCommand.php | 71 --- src/Commands/PresetListCommand.php | 55 -- src/Commands/Scaffolder/ActivityCommand.php | 57 ++ src/Commands/Scaffolder/WorkflowCommand.php | 64 +++ src/Commands/WithContext.php | 149 ----- src/Config/TemporalConfig.php | 2 +- src/Exception/PresetNotFoundException.php | 10 - src/Generator/ActivityGenerator.php | 43 -- src/Generator/ActivityInterfaceGenerator.php | 34 -- src/Generator/Context.php | 287 ---------- src/Generator/FileGeneratorInterface.php | 12 - src/Generator/Generator.php | 39 -- src/Generator/HandlerGenerator.php | 132 ----- src/Generator/HandlerInterfaceGenerator.php | 26 - src/Generator/PhpCodePrinter.php | 34 -- src/Generator/Utils.php | 155 ------ src/Generator/WorkflowGenerator.php | 69 --- src/Generator/WorkflowInterfaceGenerator.php | 44 -- src/Preset/PresetInterface.php | 23 - src/Preset/PresetRegistry.php | 37 -- src/Preset/PresetRegistryInterface.php | 27 - src/Preset/WorkflowPreset.php | 17 - .../Declaration/ActivityDeclaration.php | 58 ++ .../Declaration/WorkflowDeclaration.php | 76 +++ src/Workflow/RunningWorkflow.php | 20 - src/Workflow/Workflow.php | 168 ------ src/Workflow/WorkflowManager.php | 93 ---- src/Workflow/WorkflowSignal.php | 24 - src/WorkflowManagerInterface.php | 44 -- src/WorkflowPresetLocator.php | 41 -- src/WorkflowPresetLocatorInterface.php | 16 - .../Bootloader/PrototypeBootloaderTest.php | 1 - .../TemporalBridgeBootloaderTest.php | 33 -- tests/src/Commands/MakePresetCommandTest.php | 64 --- tests/src/Commands/PresetListCommandTest.php | 36 -- .../Scaffolder/ActivityCommandTest.php | 187 +++++++ .../Scaffolder/WorkflowCommandTest.php | 265 +++++++++ tests/src/Config/TemporalConfigTest.php | 2 +- tests/src/Generator/ContextTest.php | 234 -------- tests/src/Generator/PhpCodePrinterTest.php | 37 -- tests/src/Generator/WorkflowTypesTest.php | 85 --- tests/src/Preset/PresetRegistryTest.php | 36 -- tests/src/UtilsTest.php | 59 -- tests/src/Workflow/WorkflowManagerTest.php | 55 -- 54 files changed, 852 insertions(+), 2974 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 src/Bootloader/ScaffolderBootloader.php delete mode 100644 src/Commands/MakePresetCommand.php delete mode 100644 src/Commands/MakeWorkflowCommand.php delete mode 100644 src/Commands/PresetListCommand.php create mode 100644 src/Commands/Scaffolder/ActivityCommand.php create mode 100644 src/Commands/Scaffolder/WorkflowCommand.php delete mode 100644 src/Commands/WithContext.php delete mode 100644 src/Exception/PresetNotFoundException.php delete mode 100644 src/Generator/ActivityGenerator.php delete mode 100644 src/Generator/ActivityInterfaceGenerator.php delete mode 100644 src/Generator/Context.php delete mode 100644 src/Generator/FileGeneratorInterface.php delete mode 100644 src/Generator/Generator.php delete mode 100644 src/Generator/HandlerGenerator.php delete mode 100644 src/Generator/HandlerInterfaceGenerator.php delete mode 100644 src/Generator/PhpCodePrinter.php delete mode 100644 src/Generator/Utils.php delete mode 100644 src/Generator/WorkflowGenerator.php delete mode 100644 src/Generator/WorkflowInterfaceGenerator.php delete mode 100644 src/Preset/PresetInterface.php delete mode 100644 src/Preset/PresetRegistry.php delete mode 100644 src/Preset/PresetRegistryInterface.php delete mode 100644 src/Preset/WorkflowPreset.php create mode 100644 src/Scaffolder/Declaration/ActivityDeclaration.php create mode 100644 src/Scaffolder/Declaration/WorkflowDeclaration.php delete mode 100644 src/Workflow/RunningWorkflow.php delete mode 100644 src/Workflow/Workflow.php delete mode 100644 src/Workflow/WorkflowManager.php delete mode 100644 src/Workflow/WorkflowSignal.php delete mode 100644 src/WorkflowManagerInterface.php delete mode 100644 src/WorkflowPresetLocator.php delete mode 100644 src/WorkflowPresetLocatorInterface.php delete mode 100644 tests/src/Commands/MakePresetCommandTest.php delete mode 100644 tests/src/Commands/PresetListCommandTest.php create mode 100644 tests/src/Commands/Scaffolder/ActivityCommandTest.php create mode 100644 tests/src/Commands/Scaffolder/WorkflowCommandTest.php delete mode 100644 tests/src/Generator/ContextTest.php delete mode 100644 tests/src/Generator/PhpCodePrinterTest.php delete mode 100644 tests/src/Generator/WorkflowTypesTest.php delete mode 100644 tests/src/Preset/PresetRegistryTest.php delete mode 100644 tests/src/UtilsTest.php delete mode 100644 tests/src/Workflow/WorkflowManagerTest.php 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 a7c158f..fac723c 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,14 +40,14 @@ "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", - "vimeo/psalm": "^4.9" + "spiral/testing": "^2.6", + "vimeo/psalm": "^5.0" }, "autoload": { "psr-4": { @@ -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 7c6aaae..615df74 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,27 +1,22 @@ - + - + - + - + + - - - - - - - + diff --git a/src/Attribute/AssignWorker.php b/src/Attribute/AssignWorker.php index 6d1e765..6b2691d 100644 --- a/src/Attribute/AssignWorker.php +++ b/src/Attribute/AssignWorker.php @@ -4,9 +4,7 @@ namespace Spiral\TemporalBridge\Attribute; -use Spiral\Attributes\NamedArgumentConstructor; - -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS)] final class AssignWorker { public function __construct( 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 f2f3c43..0000000 --- a/src/Generator/Utils.php +++ /dev/null @@ -1,155 +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, ',')) { - [$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, ',')) { - [$method, $params] = \explode(',', $method, 2); - } - - if (\str_contains($method, ':')) { - [$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, ':')) { - [$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 390f424..0000000 --- a/tests/src/Commands/MakePresetCommandTest.php +++ /dev/null @@ -1,64 +0,0 @@ -expectException(PresetNotFoundException::class); - $this->expectErrorMessage('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 16dafa6..0000000 --- a/tests/src/Generator/PhpCodePrinterTest.php +++ /dev/null @@ -1,37 +0,0 @@ -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 9de2db6..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->expectErrorMessage('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()); - } -}