{"id":27437439,"url":"https://github.com/keepsuit/laravel-temporal","last_synced_at":"2025-04-14T20:29:12.859Z","repository":{"id":61290703,"uuid":"528467318","full_name":"keepsuit/laravel-temporal","owner":"keepsuit","description":"Laravel integration with temporal.io workflow engine","archived":false,"fork":false,"pushed_at":"2025-04-09T16:02:22.000Z","size":405,"stargazers_count":37,"open_issues_count":2,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-09T16:23:01.310Z","etag":null,"topics":["laravel","temporal","workflow"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/keepsuit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-24T14:46:10.000Z","updated_at":"2025-04-09T16:02:26.000Z","dependencies_parsed_at":"2024-01-02T09:41:12.365Z","dependency_job_id":"a5fa0ab2-37f8-4256-a1e1-650692475db5","html_url":"https://github.com/keepsuit/laravel-temporal","commit_stats":{"total_commits":123,"total_committers":4,"mean_commits":30.75,"dds":0.1869918699186992,"last_synced_commit":"b6e757286b897fdfcf09c2430175cdd23e977539"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":"spatie/package-skeleton-laravel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepsuit%2Flaravel-temporal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepsuit%2Flaravel-temporal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepsuit%2Flaravel-temporal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keepsuit%2Flaravel-temporal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keepsuit","download_url":"https://codeload.github.com/keepsuit/laravel-temporal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248954886,"owners_count":21188879,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["laravel","temporal","workflow"],"created_at":"2025-04-14T20:29:12.253Z","updated_at":"2025-04-14T20:29:12.850Z","avatar_url":"https://github.com/keepsuit.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel temporal.io\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/keepsuit/laravel-temporal.svg?style=flat-square)](https://packagist.org/packages/keepsuit/laravel-temporal)\n[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/keepsuit/laravel-temporal/run-tests.yml?branch=main\u0026label=tests\u0026style=flat-square)](https://github.com/keepsuit/laravel-temporal/actions?query=workflow%3Arun-tests+branch%3Amain)\n[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/keepsuit/laravel-temporal/fix-php-code-style-issues.yml?branch=main\u0026label=code%20style\u0026style=flat-square)](https://github.com/keepsuit/laravel-temporal/actions?query=workflow%3A\"Fix+PHP+code+style+issues\"+branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/keepsuit/laravel-temporal.svg?style=flat-square)](https://packagist.org/packages/keepsuit/laravel-temporal)\n\nThis package allow an easy integration of a Laravel app with a [temporal.io](https://temporal.io/),\nwhich is _a distributed, scalable, durable, and highly available orchestration engine for asynchronous long-running business logic in a microservice\narchitecture_.\n\nThis package provides:\n\n- Commands to create a new workflow, activity and interceptor\n- Command to start the worker which will execute workflows and activities from the provided task queue\n- Command to start a temporal dev server\n- Testing helpers that allows mock of workflows and activities executions\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require keepsuit/laravel-temporal\n```\n\nThen download the latest `roadrunner` executable for your platform:\n\n```bash\nphp artisan temporal:install\n```\n\nor\n\n```bash\n./vendor/bin/rr get-binary\n```\n\n\u003e [!NOTE]\n\u003e You should run this command after every update to ensure that you have the latest version of `roadrunner` executable.\n\nYou can publish the config file with:\n\n```bash\nphp artisan vendor:publish --tag=\"temporal-config\"\n```\n\nThis is the contents of the published config file:\n\n```php\n\u003c?php\n\nreturn [\n    /**\n     * Temporal server address\n     */\n    'address' =\u003e env('TEMPORAL_ADDRESS', 'localhost:7233'),\n\n    /**\n     * TLS configuration (optional)\n     * Allows to configure the client to use a secure connection to the server.\n     */\n    'tls' =\u003e [\n        /**\n         * Path to the client key file (/path/to/client.key)\n         */\n        'client_key' =\u003e env('TEMPORAL_TLS_CLIENT_KEY'),\n        /**\n         * Path to the client cert file (/path/to/client.pem)\n         */\n        'client_cert' =\u003e env('TEMPORAL_TLS_CLIENT_CERT'),\n        /**\n         * Path to the root CA certificate file (/path/to/ca.cert)\n         */\n        'root_ca' =\u003e env('TEMPORAL_TLS_ROOT_CA'),\n        /**\n         * Override server name (default is hostname) to verify against the server certificate\n         */\n        'server_name' =\u003e env('TEMPORAL_TLS_SERVER_NAME'),\n    ],\n\n    /**\n     * Temporal namespace\n     */\n    'namespace' =\u003e env('TEMPORAL_NAMESPACE', \\Temporal\\Client\\ClientOptions::DEFAULT_NAMESPACE),\n\n    /**\n     * Default task queue\n     */\n    'queue' =\u003e \\Temporal\\WorkerFactory::DEFAULT_TASK_QUEUE,\n\n    /**\n     * Default retry policy\n     */\n    'retry' =\u003e [\n\n        /**\n         * Default retry policy for workflows\n         */\n        'workflow' =\u003e [\n            /**\n             * Initial retry interval (in seconds)\n             * Default: 1\n             */\n            'initial_interval' =\u003e null,\n\n            /**\n             * Retry interval increment\n             * Default: 2.0\n             */\n            'backoff_coefficient' =\u003e null,\n\n            /**\n             * Maximum interval before fail\n             * Default: 100 x initial_interval\n             */\n            'maximum_interval' =\u003e null,\n\n            /**\n             * Maximum attempts\n             * Default: unlimited\n             */\n            'maximum_attempts' =\u003e null,\n        ],\n\n        /**\n         * Default retry policy for activities\n         */\n        'activity' =\u003e [\n            /**\n             * Initial retry interval (in seconds)\n             * Default: 1\n             */\n            'initial_interval' =\u003e null,\n\n            /**\n             * Retry interval increment\n             * Default: 2.0\n             */\n            'backoff_coefficient' =\u003e null,\n\n            /**\n             * Maximum interval before fail\n             * Default: 100 x initial_interval\n             */\n            'maximum_interval' =\u003e null,\n\n            /**\n             * Maximum attempts\n             * Default: unlimited\n             */\n            'maximum_attempts' =\u003e null,\n        ],\n    ],\n\n    /**\n     * Interceptors (middlewares) registered in the worker\n     */\n    'interceptors' =\u003e [\n    ],\n\n    /**\n     * Manual register workflows\n     */\n    'workflows' =\u003e [\n    ],\n\n    /**\n     * Manual register activities\n     */\n    'activities' =\u003e [\n    ],\n\n    /**\n     * Directories to watch when server is started with `--watch` flag\n     */\n    'watch' =\u003e [\n        'app',\n        'config',\n    ],\n\n    /**\n     * Integrations options\n     */\n    'integrations' =\u003e [\n\n        /**\n         * Eloquent models serialization/deserialization options\n         */\n        'eloquent' =\u003e [\n            /**\n             * Default attribute key case conversion when serialize a model before sending to temporal.\n             * Supported values: 'snake', 'camel', null.\n             */\n            'serialize_attribute_case' =\u003e null,\n\n            /**\n             * Default attribute key case conversion when deserializing payload received from temporal.\n             * Supported values: 'snake', 'camel', null.\n             */\n            'deserialize_attribute_case' =\u003e null,\n\n            /**\n             * If true adds additional metadata fields (`__exists`, `__dirty`) to the serialized model to improve deserialization.\n             * `__exists`: indicate that the model is saved to database.\n             * `__dirty`: indicate that the model has unsaved changes. (original values are not included in the serialized payload but the deserialized model will be marked as dirty)\n             */\n            'include_metadata_field' =\u003e false,\n        ],\n    ],\n];\n```\n\n## Usage\n\nHere we will see the utilities provided by this package.\nFor more information about Temporal and Workflow/Activity options please refer to\nthe [official documentation](https://docs.temporal.io/application-development/?lang=php).\n\n### Create workflows and activities\n\nTo create a new workflow, you can use the `temporal:make:workflow {name}` command, which will create a new workflow interface \u0026 relative class in\nthe `app/Temporal/Workflows` directory.\n\nTo create a new activity, you can use the `temporal:make:activity {name}` command, which will create a new activity interface \u0026 relative class in\nthe `app/Temporal/Activities` directory.\n\n\u003e [!NOTE]\n\u003e If you already have workflow/activities in `app/Workflows` and `app/Activities` directories,\n\u003e the make commands will create the new workflow/activity in the these directories.\n\nWorkflows in `app/Temporal/Workflows` and `app/Workflows` and activities in `app/Temporal/Activities`, `app/Activities`, `app/Temporal/Workflows` and `app/Workflows` are automatically registered.\nIf you put your workflows and activities in other directories, you can register them manually in the `workflows` and `activities` config keys or with `TemporalRegistry` in your service provider.\n\n```php\nclass AppServiceProvider extends ServiceProvider\n{\n    public function register()\n    {\n        $this-\u003ecallAfterResolving(\\Keepsuit\\LaravelTemporal\\TemporalRegistry::class, function (\\Keepsuit\\LaravelTemporal\\TemporalRegistry $registry) {\n            $registry-\u003eregisterWorkflows(YourWorkflowInterface::class)\n                -\u003eregisterActivities(YourActivityInterface::class);\n        }\n        \n        // or\n        \n        Temporal::registry()\n            -\u003eregisterWorkflows(YourWorkflowInterface::class)\n            -\u003eregisterActivities(YourActivityInterface::class);\n    }\n}\n```\n\n### Build and start a workflow\n\nTo start a workflow, you must build a stub through the `Temporal` Facade.\n\n```php\n$workflow = Temporal::newWorkflow()\n    -\u003ewithTaskQueue('custom-task-queue') // Workflow options can be provided with fluent methods\n    -\u003ebuild(YourWorkflowInterface::class);\n\n// This will start a new workflow execution and wait for the result\n$result = $workflow-\u003eyourMethod();\n\n// This will start a new workflow execution and return immediately\nTemporal::workflowClient()-\u003estart($workflow);\n```\n\n### Build and start an activity\n\nTo start an activity, you must build a stub through the `Temporal` Facade (note that activities must be built inside a workflow).\nActivity methods returns a Generator, so you must use the `yield` keyword to wait for the result.\n\n```php\n$activity = Temporal::newActivity()\n    -\u003ewithTaskQueue('custom-task-queue') // Activity options can be provided with fluent methods\n    -\u003ebuild(YourActivityInterface::class);\n\n$result = yield $activity-\u003eyourActivityMethod();\n```\n\n### Build and start a child workflow\n\nChild workflows works like activity and like activities must be built inside a workflow.\n\n```php\n$childWorkflow = Temporal::newChildWorkflow()\n    -\u003ebuild(YourChildWorkflowInterface::class);\n\n$result = yield $childWorkflow-\u003eyourActivityMethod();\n```\n\n### Input and output payloads\n\nPayloads provided to workflows/activities as params and returned from them must be serialized, sent to the Temporal server and deserialized by the worker.\nActivities can be executed by workers written in different languages, so the payload must be serialized in a common format.\nOut of the box temporal sdk supports native php types and [protobuf](https://developers.google.com/protocol-buffers) messages.\nThis package adds some laravel specific options for serialization/deserialization of objects:\n\n- `TemporalSerializable` interface can be implemented to add support for custom serialization/deserialization.\n- Eloquent models can be correctly serialized/deserialized (with relations) adding `TemporalSerializable` interface and `TemporalEloquentSerialize` trait.\n- [spatie/laravel-data](https://github.com/spatie/laravel-data) data objects are supported out of the box.\n\n#### Spatie/Laravel-Data support\n\n`spatie/laravel-data` is a package that provides a simple way to work with data objects in Laravel.\nIn order to take full advantage of `laravel-data`, it is suggested to use `v4.3.0` or higher.\n\n\u003e [!NOTE]\n\u003e The provided `TemporalSerializableCastAndTransformer` is compatible only with `laravel-data` `v4.3` or higher,\n\u003e if you are using an older version you can create your cast/transform.\n\nChanges to be made in `config/data.php`:\n\n```php\n    // Enable iterables cast/transform\n    'features' =\u003e [\n        'cast_and_transform_iterables' =\u003e true,\n    ],\n\n    // Add support for TemporalSerializable transform\n    'transformers' =\u003e [\n        //...\n        \\Keepsuit\\LaravelTemporal\\Contracts\\TemporalSerializable::class =\u003e \\Keepsuit\\LaravelTemporal\\Integrations\\LaravelData\\TemporalSerializableCastAndTransformer::class,\n    ],\n\n    // Add support for TemporalSerializable cast\n    'casts' =\u003e [\n        //...\n        \\Keepsuit\\LaravelTemporal\\Contracts\\TemporalSerializable::class =\u003e \\Keepsuit\\LaravelTemporal\\Integrations\\LaravelData\\TemporalSerializableCastAndTransformer::class,\n    ],\n```\n\n### Interceptors\n\nTemporal interceptors are similar to laravel middleware and can be used to modify inbound and outbound SDK calls.\nInterceptors can be registered in the `interceptors` config key.\nSee [temporal sdk v2.7](https://github.com/temporalio/sdk-php/releases/tag/v2.7.0) release notes for more information.\nTo create a new interceptor, you can use the `temporal:make:interceptor {name}` command, which will create a new interceptor class in the `app/Temporal/Interceptors` directory.\n\n### Run the temporal worker\n\nTo run the temporal worker, you can use the `temporal:work {queue?}` command.\n\nIf you want to customize the options of the temporal worker, you can call `Temporal::buildWorkerOptionsUsing` in your service provider:\n\n```php\nclass AppServiceProvider extends ServiceProvider\n{\n    public function boot(): vodi {\n        \\Keepsuit\\LaravelTemporal\\Facade\\Temporal::buildWorkerOptionsUsing(function (string $taskQueue) {\n            // you can build different worker options based on the task queue\n            return \\Temporal\\Worker\\WorkerOptions::new()\n                -\u003ewithMaxConcurrentActivityTaskPollers(10)\n                -\u003ewithMaxConcurrentWorkflowTaskPollers(10);\n        });\n    }\n}\n```\n\n## Testing utilities\n\nIn order to test workflows end-to-end, you need a temporal server running.\nThis package provides two options to run a temporal server for testing purposes:\n\n- Run `temporal:server` command, which will start a temporal testing server and use the `WithTemporalWorker` trait which will start a test worker\n- Use the `WithTemporal` trait, which will start a temporal testing server and the test worker when running test and stop it on finish\n\n\u003e When using `WithTemporal` trait, you can set `TEMPORAL_TESTING_SERVER` env variable to `false`\n\u003e to disable the testing server and run only the worker.\n\n### Time skipping\n\nThe default temporal server implementation is the dev server included in the temporal cli and this doesn't support time skipping.\nIn order to enable time skipping, you must:\n\n- Run the `temporal:server` command with the `--enable-time-skipping` flag.\n- Set `TEMPORAL_TESTING_SERVER_TIME_SKIPPING` env variable to `true` when using `WithTemporal` trait.\n\n### Mocking workflows\n\nMocking a workflow can be useful when the workflow should be executed in another service or simply when you want to test other parts of your code\nwithout running the workflow.\nThis works for child workflows too.\n\n```php\nTemporal::fake();\n\n$workflowMock = Temporal::mockWorkflow(YourWorkflowInterface::class)\n    -\u003eonTaskQueue('custom-queue'); // not required but useful for mocking and asserting that workflow is executed on the correct queue\n    -\u003eandReturn('result');\n\n// Your test code...\n\n$workflowMock-\u003eassertDispatched();\n$workflowMock-\u003eassertDispatchedTimes(1);\n$workflowMock-\u003eassertNotDispatched();\n\n// All assertion method support a callback to assert the workflow input\n$workflowMock-\u003eassertDispatched(function ($input) {\n    return $input['foo'] === 'bar';\n});\n```\n\n### Mocking activities\n\nMocking activities works like workflows, but for activity you must provide interface and the method to mock.\n\n```php\nTemporal::fake();\n\n$activityMock = Temporal::mockActivity([YourActivityInterface::class, 'activityMethod'])\n    -\u003eonTaskQueue('custom-queue'); // not required but useful for mocking and asserting that activity is executed on the correct queue\n    -\u003eandReturn('result');\n\n// Your test code...\n\n$activityMock-\u003eassertDispatched();\n$activityMock-\u003eassertDispatchedTimes(1);\n$activityMock-\u003eassertNotDispatched();\n\n// All assertion method support a callback to assert the activity input\n$activityMock-\u003eassertDispatched(function ($input) {\n    return $input['foo'] === 'bar';\n});\n```\n\n### Assertions\n\nDispatches assertions can be done through the `Temporal` facade but there are some downsides compared to the options above:\n\n- You must provide the workflow/activity interface and method name, so this is duplicated\n- If you want to ensure that the workflow/activity is executed on the correct queue, you must check the task queue yourself\n\n```php\nTemporal::assertWorkflowDispatched(YourWorkflowInterface::class, function($workflowInput, string $taskQueue) {\n    return $workflowInput['foo'] === 'bar' \u0026\u0026 $taskQueue === 'custom-queue';\n});\n\nTemporal::assertActivityDispatched([YourActivityInterface::class, 'activityMethod'], function($activityInput, string $taskQueue) {\n    return $activityInput['foo'] === 'bar' \u0026\u0026 $taskQueue === 'custom-queue';\n});\n```\n\n## PHPStan\n\nThis package provides a PHPStan extension to improve the experience when working with Temporal proxy classes.\n\nIf you have [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer) installed, you are ready to go.\nOtherwise, you have to add the extension to your `phpstan.neon` file:\n\n```neon\nincludes:\n    - ./vendor/keepsuit/laravel-temporal/extension.neon\n```\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Credits\n\n- [Fabio Capucci](https://github.com/keepsuit)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeepsuit%2Flaravel-temporal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeepsuit%2Flaravel-temporal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeepsuit%2Flaravel-temporal/lists"}