├── testbench.yaml ├── .gitignore ├── src ├── Exceptions │ ├── ParseException.php │ ├── CamundaException.php │ ├── InvalidArgumentException.php │ ├── ObjectNotFoundException.php │ └── UnexpectedResponseException.php ├── Dto │ ├── Tenant.php │ ├── VariableType │ │ ├── BooleanType.php │ │ ├── StringType.php │ │ ├── JsonType.php │ │ └── ObjectType.php │ ├── Variable.php │ ├── ProcessInstance.php │ ├── ProcessDefinition.php │ ├── Deployment.php │ ├── ExternalTask.php │ ├── Task.php │ ├── ProcessInstanceHistory.php │ └── TaskHistory.php ├── ServiceProvider.php ├── Collections │ └── VariableCollection.php ├── Http │ ├── CamundaClient.php │ ├── TaskHistoryClient.php │ ├── MessageEventClient.php │ ├── TenantClient.php │ ├── ProcessInstanceHistoryClient.php │ ├── ProcessDefinitionClient.php │ ├── DeploymentClient.php │ ├── ProcessInstanceClient.php │ ├── ExternalTaskClient.php │ └── TaskClient.php ├── Commands │ └── ConsumeExternalTaskCommand.php └── BpmnReader.php ├── tests ├── BpmnTest.php ├── CamundaClientTest.php ├── TestCase.php ├── VariableTest.php ├── Dto │ └── TaskDtoTest.php ├── DeploymentTest.php ├── ProcessDefinitionTest.php ├── ExternalTaskTest.php ├── TaskTest.php └── ProcessInstanceTest.php ├── phpunit.dist.xml ├── composer.json ├── resources ├── invalid.bpmn ├── external-task.bpmn ├── sample.bpmn ├── sample2.bpmn └── rekrutmen-sederhana.bpmn ├── .php-cs-fixer.php └── README.md /testbench.yaml: -------------------------------------------------------------------------------- 1 | providers: 2 | - Spatie\LaravelData\LaravelDataServiceProvider 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /reports 3 | phpunit.xml 4 | clover.xml 5 | composer.lock 6 | .phpunit.result.cache 7 | .php_cs.cache 8 | .php-cs-fixer.cache 9 | -------------------------------------------------------------------------------- /src/Exceptions/ParseException.php: -------------------------------------------------------------------------------- 1 | (bool) $value, 'type' => 'Boolean']; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Dto/VariableType/StringType.php: -------------------------------------------------------------------------------- 1 | (string) $value, 'type' => 'String']; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Dto/Variable.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 12 | $this->commands([ConsumeExternalTaskCommand::class]); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Dto/VariableType/JsonType.php: -------------------------------------------------------------------------------- 1 | json_encode($value, JSON_THROW_ON_ERROR), 16 | 'type' => 'Json' 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Dto/ProcessInstance.php: -------------------------------------------------------------------------------- 1 | getForms(); 14 | 15 | $this->assertNotEmpty($forms); 16 | } 17 | 18 | public function test_parse_empty_form_definition(): void 19 | { 20 | $file = __DIR__.'/../resources/sample.bpmn'; 21 | $reader = new BpmnReader($file); 22 | $forms = $reader->getForms(); 23 | 24 | $this->assertEmpty($forms); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/CamundaClientTest.php: -------------------------------------------------------------------------------- 1 | get('version'); 12 | 13 | $this->assertEquals(200, $response->status()); 14 | $this->assertArrayHasKey('version', $response->json()); 15 | $this->assertInstanceOf(\stdClass::class, $response->object()); 16 | } 17 | 18 | public function test_call_invalid_endpoint(): void 19 | { 20 | $response = CamundaClient::make()->get('invalid-endpoint'); 21 | 22 | $this->assertEquals(404, $response->status()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Dto/ProcessDefinition.php: -------------------------------------------------------------------------------- 1 | url = $url; 15 | $instance->payload = $payload; 16 | $instance->response = $response; 17 | 18 | return $instance; 19 | } 20 | 21 | public function context() 22 | { 23 | return [ 24 | 'url' => $this->url, 25 | 'payload' => $this->payload, 26 | 'response' => $this->response, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Dto/VariableType/ObjectType.php: -------------------------------------------------------------------------------- 1 | $value->toJson(), 19 | 'type' => 'Object', 20 | 'valueInfo' => [ 21 | 'objectTypeName' => 'java.util.Collection', 22 | 'serializationDataFormat' => 'application/json', 23 | ], 24 | ]; 25 | } 26 | 27 | return [ 28 | 'value' => json_encode($value, JSON_THROW_ON_ERROR), 29 | 'type' => 'Object', 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Dto/Deployment.php: -------------------------------------------------------------------------------- 1 | items as $key => $value) { 17 | $valueType = gettype($value); 18 | $typeClass = match ($valueType) { 19 | 'array' => JsonType::class, 20 | 'boolean' => BooleanType::class, 21 | 'object' => ObjectType::class, 22 | default => StringType::class, 23 | }; 24 | 25 | $variables[$key] = (new $typeClass())($value); 26 | } 27 | 28 | return $variables; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | src 10 | 11 | 12 | src/ServiceProvider.php 13 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('app.name', env('APP_NAME')); 23 | $app['config']->set('app.env', env('APP_ENV')); 24 | $app['config']->set('services.camunda.url', env('CAMUNDA_URL')); 25 | $app['config']->set('services.camunda.user', env('CAMUNDA_USER')); 26 | $app['config']->set('services.camunda.password', env('CAMUNDA_PASSWORD')); 27 | } 28 | 29 | protected function deploySampleBpmn(): Deployment 30 | { 31 | $files = [__DIR__.'/../resources/sample.bpmn']; 32 | 33 | 34 | return DeploymentClient::create('process_1', $files); 35 | } 36 | 37 | protected function truncateDeployment(): void 38 | { 39 | DeploymentClient::truncate(true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Dto/ExternalTask.php: -------------------------------------------------------------------------------- 1 | =8.1", 17 | "ext-simplexml": "*", 18 | "guzzlehttp/guzzle": "^7.2", 19 | "spatie/laravel-data": "^3.0|^4.0" 20 | }, 21 | "require-dev": { 22 | "orchestra/testbench": "^8.0", 23 | "brianium/paratest": "^6.2", 24 | "sempro/phpunit-pretty-print": "^1.4" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Laravolt\\Camunda\\": "src" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Laravolt\\Camunda\\Tests\\": "tests" 34 | } 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "2.x-dev" 39 | }, 40 | "laravel": { 41 | "providers": [ 42 | "Laravolt\\Camunda\\ServiceProvider" 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/VariableTest.php: -------------------------------------------------------------------------------- 1 | deploySampleBpmn(); 15 | } 16 | 17 | public function test_automatically_format_variables() 18 | { 19 | $rawVariables = [ 20 | 'title' => 'Some String', 21 | 'isActive' => true, 22 | 'tags' => ['satu' => 'foo', 'dua', 'tiga'], 23 | 'keys' => collect([1, 2]), 24 | ]; 25 | $variables = new VariableCollection($rawVariables); 26 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables->toArray()); 27 | $camundaVariables = ProcessInstanceClient::variables($processInstance->id); 28 | $this->assertEquals('String', $camundaVariables['title']->type); 29 | $this->assertEquals('Boolean', $camundaVariables['isActive']->type); 30 | $this->assertEquals('Json', $camundaVariables['tags']->type); 31 | $this->assertEquals('Object', $camundaVariables['keys']->type); 32 | } 33 | 34 | protected function tearDown(): void 35 | { 36 | $this->truncateDeployment(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Dto/Task.php: -------------------------------------------------------------------------------- 1 | withBasicAuth($user, $password); 21 | } 22 | 23 | return $request; 24 | } 25 | 26 | protected static function makeIdentifierPath(string $path, array $args): string 27 | { 28 | // If no named parameters defined, we assume it is an ID 29 | if (count($args) === 1 && isset($args[0])) { 30 | $args['id'] = $args[0]; 31 | } 32 | 33 | $args += ['id' => false, 'key' => false, 'tenantId' => false]; 34 | $identifier = $args['id']; 35 | if ($args['key']) { 36 | $identifier = 'key/'.$args['key']; 37 | if ($args['tenantId']) { 38 | $identifier .= '/tenant-id/'.$args['tenantId']; 39 | } 40 | } 41 | 42 | if (! $identifier) { 43 | throw new InvalidArgumentException(''); 44 | } 45 | 46 | return str_replace('{identifier}', $identifier, $path); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Commands/ConsumeExternalTaskCommand.php: -------------------------------------------------------------------------------- 1 | option('workerId'); 24 | foreach ($subscribers as $topicName => $subscriber) { 25 | $topics[$topicName] = collect($subscriber)->only(['topicName', 'lockDuration'])->toArray(); 26 | $summary[$topicName] = [$topicName, $subscriber['job'] ?? '-', 0]; 27 | } 28 | $externalTasks = ExternalTaskClient::fetchAndLock($workerId, array_values($topics)); 29 | 30 | /** @var \Laravolt\Camunda\Dto\ExternalTask $task */ 31 | foreach ($externalTasks as $task) { 32 | $jobClass = $subscribers[$task->topicName]['job'] ?? false; 33 | if ($jobClass) { 34 | $jobClass::dispatch($workerId, $task); 35 | $summary[$task->topicName][2]++; 36 | } 37 | } 38 | 39 | $this->table(['topic', 'Job', 'Job Dispatched'], $summary); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Dto/TaskHistory.php: -------------------------------------------------------------------------------- 1 | get("history/task?taskId=$id"); 15 | 16 | if ($response->status() === 200) { 17 | if (empty($response->json())) { 18 | throw new ObjectNotFoundException(sprintf('Cannot find task history with ID = %s', $id)); 19 | } 20 | 21 | return TaskHistory::from(Arr::first($response->json())); 22 | } 23 | 24 | throw new CamundaException($response->json('message') ?? $response->body()); 25 | } 26 | 27 | /** 28 | * @param string $processInstanceId 29 | * 30 | * @return TaskHistory[] 31 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 32 | */ 33 | public static function getByProcessInstanceId(string $processInstanceId): array 34 | { 35 | $response = self::make() 36 | ->get( 37 | 'history/task', 38 | [ 39 | 'processInstanceId' => $processInstanceId, 40 | 'finished' => true, 41 | ] 42 | ); 43 | 44 | if ($response->successful()) { 45 | $data = collect(); 46 | foreach ($response->json() as $task) { 47 | $data->push(TaskHistory::from($task)); 48 | } 49 | 50 | return $data->sortBy('endTime')->toArray(); 51 | } 52 | 53 | return []; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Http/MessageEventClient.php: -------------------------------------------------------------------------------- 1 | toString(); 37 | 38 | if ($businessKey) { 39 | $payload['businessKey'] = $businessKey; 40 | } 41 | 42 | $response = self::make()->post('message', $payload); 43 | if ($response->successful()) { 44 | return ProcessInstanceClient::findByBusniessKey($businessKey); 45 | // return new ProcessInstance($response->json()); 46 | // return true; 47 | } 48 | 49 | throw new InvalidArgumentException($response->body()); 50 | // return false; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Http/TenantClient.php: -------------------------------------------------------------------------------- 1 | get("tenant/$id"); 14 | 15 | if ($response->status() === 404) { 16 | throw new ObjectNotFoundException($response->json('message')); 17 | } 18 | 19 | return new Tenant($response->json()); 20 | } 21 | 22 | public static function get(array $parameters = []): array 23 | { 24 | $response = self::make()->get('tenant', $parameters); 25 | $result = []; 26 | foreach ($response->json() as $data) { 27 | $result[] = new Tenant($data); 28 | } 29 | 30 | return $result; 31 | } 32 | 33 | public static function create(string $id, string $name): bool 34 | { 35 | $response = self::make()->post( 36 | "tenant/create", 37 | compact('id', 'name') 38 | ); 39 | 40 | if ($response->status() === 204) { 41 | return true; 42 | } 43 | 44 | throw new CamundaException($response->body(), $response->status()); 45 | } 46 | 47 | public static function delete(string $id): bool 48 | { 49 | $response = self::make()->delete("tenant/{$id}"); 50 | 51 | if ($response->status() === 404) { 52 | throw new ObjectNotFoundException($response->json('message')); 53 | } 54 | 55 | return $response->status() === 204; 56 | } 57 | 58 | public static function truncate(): void 59 | { 60 | foreach (self::get() as $tenant) { 61 | self::delete($tenant->id); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Http/ProcessInstanceHistoryClient.php: -------------------------------------------------------------------------------- 1 | get('history/process-instance', $parameters)->json() as $res) { 25 | $instances[] = ProcessInstanceHistory::from($res); 26 | } 27 | 28 | return $instances; 29 | } 30 | 31 | /** 32 | * @param string $id 33 | * 34 | * @return \Laravolt\Camunda\Dto\ProcessInstanceHistory 35 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 36 | */ 37 | public static function find(string $id): ProcessInstanceHistory 38 | { 39 | $response = self::make()->get("history/process-instance/$id"); 40 | 41 | if ($response->status() === 404) { 42 | throw new ObjectNotFoundException($response->json('message')); 43 | } 44 | 45 | return ProcessInstanceHistory::from($response->json()); 46 | } 47 | 48 | public static function variables(string $id): array 49 | { 50 | $variables = self::make()->get("history/variable-instance", ['processInstanceId' => $id])->json(); 51 | 52 | return collect($variables)->mapWithKeys( 53 | fn ($data) => [$data['name'] => new Variable(name: $data['name'], value: $data['value'], type: $data['type'])] 54 | )->toArray(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/BpmnReader.php: -------------------------------------------------------------------------------- 1 | xml = new \SimpleXMLElement(file_get_contents($file)); 14 | $this->xml->registerXPathNamespace('bpmn', 'http://www.omg.org/spec/BPMN/20100524/MODEL'); 15 | $this->xml->registerXPathNamespace('camunda', 'http://camunda.org/schema/1.0/bpmn'); 16 | } 17 | 18 | public function getForms() 19 | { 20 | $nodes = $this->xml->xpath('//bpmn:startEvent') + $this->xml->xpath('//bpmn:userTask'); 21 | 22 | $forms = []; 23 | foreach ($nodes as $node) { 24 | try { 25 | $fields = $node->xpath('bpmn:extensionElements/camunda:formData/camunda:formField'); 26 | $formFields = []; 27 | foreach ($fields as $field) { 28 | $properties = collect($field->xpath('camunda:properties/camunda:property')) 29 | ->transform( 30 | fn ($node) => [(string) $node->attributes()->id => (string) $node->attributes()->value] 31 | ) 32 | ->toArray(); 33 | 34 | $formFields[] = [ 35 | 'id' => (string) $field->attributes()->id, 36 | 'label' => (string) $field->attributes()->label, 37 | 'type' => (string) $field->attributes()->type, 38 | 'properties' => $properties, 39 | ]; 40 | } 41 | if (!empty($formFields)) { 42 | $forms[] = [ 43 | 'id' => (string) $node->attributes()->id, 44 | 'label' => (string) $node->attributes()->name, 45 | 'fields' => $formFields, 46 | ]; 47 | } 48 | } catch (\ErrorException $exception) { 49 | } 50 | } 51 | 52 | return $forms; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /resources/invalid.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Flow_0st8cgr 7 | 8 | 9 | 10 | 11 | Flow_02wkiqx_invalid 12 | Flow_0st8cgr_invalid 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Http/ProcessDefinitionClient.php: -------------------------------------------------------------------------------- 1 | post($path, $payload); 36 | if ($response->successful()) { 37 | return ProcessInstance::from($response->json()); 38 | } 39 | 40 | throw new InvalidArgumentException($response->body()); 41 | } 42 | 43 | public static function xml(...$args): string 44 | { 45 | $path = self::makeIdentifierPath(path: 'process-definition/{identifier}/xml', args: $args); 46 | 47 | return self::make()->get($path)->json('bpmn20Xml'); 48 | } 49 | 50 | public static function get(array $parameters = []): array 51 | { 52 | $processDefinition = []; 53 | foreach (self::make()->get('process-definition', $parameters)->json() as $res) { 54 | $processDefinition[] = ProcessDefinition::from($res); 55 | } 56 | 57 | return $processDefinition; 58 | } 59 | 60 | public static function find(...$args): ProcessDefinition 61 | { 62 | $response = self::make()->get(self::makeIdentifierPath('process-definition/{identifier}', $args)); 63 | 64 | if ($response->status() === 404) { 65 | throw new ObjectNotFoundException($response->json('message')); 66 | } 67 | 68 | return ProcessDefinition::from($response->json()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /resources/external-task.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flow_02wkiqx 6 | 7 | 8 | Flow_0st8cgr 9 | 10 | 11 | 12 | 13 | Flow_02wkiqx 14 | Flow_0st8cgr 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/Dto/TaskDtoTest.php: -------------------------------------------------------------------------------- 1 | 'task-123', 15 | 'name' => 'Sample Task', 16 | 'assignee' => null, 17 | 'created' => '2025-08-09T06:50:00+00:00', 18 | 'due' => null, 19 | 'followUp' => null, 20 | 'lastUpdated' => '2025-08-09T07:10:11+00:00', 21 | 'delegationState' => null, 22 | 'description' => null, 23 | 'executionId' => 'exec-1', 24 | 'owner' => null, 25 | 'parentTaskId' => null, 26 | 'priority' => '50', 27 | 'processDefinitionId' => 'proc-def-1', 28 | 'processInstanceId' => 'proc-inst-1', 29 | 'taskDefinitionKey' => 'task_def_key', 30 | 'caseExecutionId' => null, 31 | 'caseInstanceId' => null, 32 | 'caseDefinitionId' => null, 33 | 'suspended' => false, 34 | 'formKey' => null, 35 | 'camundaFormRef' => null, 36 | 'tenantId' => null, 37 | 'taskState' => null, 38 | ]; 39 | 40 | $task = Task::from($payload); 41 | 42 | $this->assertInstanceOf(Carbon::class, $task->created); 43 | $this->assertInstanceOf(Carbon::class, $task->lastUpdated); 44 | } 45 | 46 | public function test_last_updated_can_be_null(): void 47 | { 48 | $payload = [ 49 | 'id' => 'task-456', 50 | 'name' => 'Another Task', 51 | 'assignee' => null, 52 | 'created' => '2025-08-09T06:50:00+00:00', 53 | 'due' => null, 54 | 'followUp' => null, 55 | 'lastUpdated' => null, 56 | 'delegationState' => null, 57 | 'description' => null, 58 | 'executionId' => 'exec-2', 59 | 'owner' => null, 60 | 'parentTaskId' => null, 61 | 'priority' => '10', 62 | 'processDefinitionId' => 'proc-def-2', 63 | 'processInstanceId' => 'proc-inst-2', 64 | 'taskDefinitionKey' => 'task_def_key_2', 65 | 'caseExecutionId' => null, 66 | 'caseInstanceId' => null, 67 | 'caseDefinitionId' => null, 68 | 'suspended' => true, 69 | 'formKey' => null, 70 | 'camundaFormRef' => null, 71 | 'tenantId' => null, 72 | 'taskState' => null, 73 | ]; 74 | 75 | $task = Task::from($payload); 76 | 77 | $this->assertInstanceOf(Carbon::class, $task->created); 78 | $this->assertNull($task->lastUpdated); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Http/DeploymentClient.php: -------------------------------------------------------------------------------- 1 | 'deployment-name', 'contents' => $name], 17 | ['name' => 'deployment-source', 'contents' => sprintf('%s (%s)', config('app.name'), config('app.env'))], 18 | ['name' => 'enable-duplicate-filtering', 'contents' => 'true'], 19 | ]; 20 | 21 | if (config('services.camunda.tenant_id')) { 22 | $multipart[] = [ 23 | 'name' => 'tenant-id', 24 | 'contents' => config('services.camunda.tenant_id'), 25 | ]; 26 | } 27 | 28 | $request = self::make()->asMultipart(); 29 | foreach ((array) $bpmnFiles as $bpmn) { 30 | $filename = pathinfo($bpmn)['basename']; 31 | $request->attach($filename, file_get_contents($bpmn), $filename); 32 | } 33 | 34 | $response = $request->post('deployment/create', $multipart); 35 | 36 | if ($response->status() === 400) { 37 | throw new ParseException($response->json('message')); 38 | } 39 | 40 | return Deployment::from($response->json()); 41 | } 42 | 43 | public static function find(string $id): Deployment 44 | { 45 | $response = self::make()->get("deployment/$id"); 46 | 47 | if ($response->status() === 404) { 48 | throw new ObjectNotFoundException($response->json('message')); 49 | } 50 | 51 | return Deployment::from($response->json()); 52 | } 53 | 54 | public static function get(array $parameters = []): array 55 | { 56 | $response = self::make()->get('deployment', $parameters); 57 | $result = []; 58 | foreach ($response->json() as $data) { 59 | $result[] = Deployment::from($data); 60 | } 61 | 62 | return $result; 63 | } 64 | 65 | public static function truncate(bool $cascade = false): void 66 | { 67 | $deployments = self::get(); 68 | foreach ($deployments as $deployment) { 69 | self::delete($deployment->id, $cascade); 70 | } 71 | } 72 | 73 | public static function delete(string $id, bool $cascade = false): bool 74 | { 75 | $cascadeFlag = $cascade ? 'cascade=true' : ''; 76 | $response = self::make()->delete("deployment/{$id}?".$cascadeFlag); 77 | 78 | if ($response->status() === 404) { 79 | throw new ObjectNotFoundException($response->json('message')); 80 | } 81 | 82 | return $response->status() === 204; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /resources/sample.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Flow_02wkiqx 10 | 11 | 12 | Flow_0st8cgr 13 | 14 | 15 | Flow_02wkiqx 16 | Flow_0st8cgr 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /resources/sample2.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Flow_02wkiqx 7 | 8 | 9 | Flow_1mpkw8w 10 | 11 | 12 | 13 | Flow_02wkiqx 14 | Flow_082h457 15 | 16 | 17 | Flow_082h457 18 | Flow_1mpkw8w 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/Http/ProcessInstanceClient.php: -------------------------------------------------------------------------------- 1 | get('process-instance'); 18 | } else { 19 | $res = self::make()->post('process-instance', $parameters); 20 | } 21 | foreach ($res->json() as $res) { 22 | $instances[] = ProcessInstance::from($res); 23 | } 24 | 25 | return $instances; 26 | } 27 | 28 | /** 29 | * Get process instances by variables. 30 | * 31 | * @param array $variables 32 | * $variables format: 33 | * $variables = [ 34 | * [ 35 | * 'name' => 'varname', 36 | * 'operator' => "eq", 37 | * 'value' => 'varvalue', 38 | * ], 39 | * ]; 40 | * `operator` can only contain `eq`, `neq`, `gt`, `gte`, `lt`, `lte`. 41 | * 42 | * @return ProcessInstance[] 43 | */ 44 | public static function getByVariables(array $variables = []): array 45 | { 46 | $instances = []; 47 | 48 | if (! $variables) { 49 | $res = self::make()->get('process-instance'); 50 | } else { 51 | $res = self::make()->post('process-instance', [ 52 | 'variables' => $variables, 53 | 54 | ]); 55 | } 56 | foreach ($res->json() as $res) { 57 | $instances[] = ProcessInstance::from($res); 58 | } 59 | 60 | return $instances; 61 | } 62 | 63 | public static function find(string $id): ProcessInstance 64 | { 65 | $response = self::make()->get("process-instance/$id"); 66 | 67 | if ($response->status() === 404) { 68 | throw new ObjectNotFoundException($response->json('message')); 69 | } 70 | 71 | return ProcessInstance::from($response->json()); 72 | } 73 | 74 | public static function findByBusniessKey(string $businessKey): ProcessInstance 75 | { 76 | $response = self::make()->post("process-instance", [ 77 | 'businessKey' => $businessKey, 78 | ]); 79 | 80 | if ($response->status() === 404) { 81 | throw new ObjectNotFoundException($response->json('message')); 82 | } 83 | 84 | $data = $response->json(); 85 | 86 | if (count($data) == 0) { 87 | throw new ObjectNotFoundException("Process Instance Not Found"); 88 | } 89 | 90 | return ProcessInstance::from($data[count($data) - 1]); 91 | } 92 | 93 | public static function variables(string $id): array 94 | { 95 | $variables = self::make()->get("process-instance/$id/variables", ['deserializeValues' => false])->json(); 96 | 97 | $temp = collect($variables)->mapWithKeys( 98 | fn ($data, $name) => [ 99 | $name => new Variable( 100 | name: $name, 101 | type: $data['type'], 102 | value: $data['value'], 103 | valueInfo: $data['valueInfo'] ?? [] 104 | ), 105 | ] 106 | ); 107 | 108 | $result = []; 109 | foreach ($temp as $name => $value) { 110 | $result[$name] = $value; 111 | } 112 | 113 | return $result; 114 | } 115 | 116 | public static function delete(string $id): bool 117 | { 118 | return self::make()->delete("process-instance/$id")->status() === 204; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/DeploymentTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('test', $deployment->name); 21 | } 22 | 23 | public function test_deploy_bpmn_with_tenant_id(): void 24 | { 25 | config()->set('services.camunda.tenant_id', 'sample-tenant'); 26 | 27 | $files = __DIR__.'/../resources/sample.bpmn'; 28 | $deployment = DeploymentClient::create('test', $files); 29 | 30 | $this->assertEquals('test', $deployment->name); 31 | } 32 | 33 | public function test_deploy_multiple_bpmn(): void 34 | { 35 | $files = [ 36 | __DIR__.'/../resources/sample.bpmn', 37 | __DIR__.'/../resources/sample2.bpmn', 38 | ]; 39 | $deployment = DeploymentClient::create('test', $files); 40 | 41 | $this->assertEquals('test', $deployment->name); 42 | } 43 | 44 | public function test_deploy_invalid_bpmn(): void 45 | { 46 | $this->expectException(ParseException::class); 47 | 48 | $files = __DIR__.'/../resources/invalid.bpmn'; 49 | DeploymentClient::create('test', $files); 50 | } 51 | 52 | public function test_get_deployment_by_id(): void 53 | { 54 | $files = __DIR__.'/../resources/sample.bpmn'; 55 | $deployment1 = DeploymentClient::create('test', $files); 56 | 57 | $deployment2 = DeploymentClient::find($deployment1->id); 58 | $this->assertEquals($deployment1->id, $deployment2->id); 59 | } 60 | 61 | public function test_get_deployment_by_invalid_id(): void 62 | { 63 | $this->expectException(ObjectNotFoundException::class); 64 | 65 | DeploymentClient::find('some-invalid-id'); 66 | } 67 | 68 | public function test_get_list_deployment(): void 69 | { 70 | $this->truncateDeployment(); 71 | DeploymentClient::create('deployment1', __DIR__.'/../resources/sample.bpmn'); 72 | DeploymentClient::create('deployment2', __DIR__.'/../resources/sample2.bpmn'); 73 | 74 | $deployments = DeploymentClient::get(); 75 | $this->assertCount(2, $deployments); 76 | } 77 | 78 | public function test_delete_deployment(): void 79 | { 80 | $deployment = DeploymentClient::create('deployment1', __DIR__.'/../resources/sample.bpmn'); 81 | $deleted = DeploymentClient::delete($deployment->id); 82 | 83 | $this->assertTrue($deleted); 84 | } 85 | 86 | public function test_delete_invalid_deployment(): void 87 | { 88 | $this->expectException(ObjectNotFoundException::class); 89 | 90 | $deployment = new \Laravolt\Camunda\Dto\Deployment( 91 | id: 'invalid-id', 92 | tenantId: null, 93 | source: null, 94 | name: 'test', 95 | deploymentTime: now(), 96 | processDefinitions: [] 97 | ); 98 | DeploymentClient::delete($deployment->id); 99 | } 100 | 101 | public function test_truncate_deployment(): void 102 | { 103 | DeploymentClient::create('deployment1', __DIR__.'/../resources/sample.bpmn'); 104 | DeploymentClient::create('deployment2', __DIR__.'/../resources/sample2.bpmn'); 105 | 106 | DeploymentClient::truncate(); 107 | 108 | $deployments = DeploymentClient::get(); 109 | $this->assertCount(0, $deployments); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/ProcessDefinitionTest.php: -------------------------------------------------------------------------------- 1 | truncateDeployment(); 15 | } 16 | 17 | public function test_start_new_process_instance(): void 18 | { 19 | $this->deploySampleBpmn(); 20 | 21 | $variables = ['title' => ['value' => 'Sample Title', 'type' => 'string']]; 22 | $businessKey = 'key-1'; 23 | $processInstance = 24 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables, businessKey: $businessKey); 25 | 26 | $this->assertIsString($processInstance->id); 27 | } 28 | 29 | public function test_start_with_empty_variables(): void 30 | { 31 | $this->deploySampleBpmn(); 32 | $processDefinition = ProcessDefinitionClient::find(key: 'process_1'); 33 | 34 | $variables = []; 35 | $businessKey = 'key-1'; 36 | 37 | $this->expectException(InvalidArgumentException::class); 38 | ProcessDefinitionClient::start($processDefinition->id, $variables, $businessKey); 39 | } 40 | 41 | public function test_get_list_process_definition(): void 42 | { 43 | $this->deploySampleBpmn(); 44 | $processDefinitions = ProcessDefinitionClient::get(); 45 | $this->assertCount(1, $processDefinitions); 46 | $this->assertInstanceOf(ProcessDefinition::class, $processDefinitions[0]); 47 | } 48 | 49 | public function test_find_by_id(): void 50 | { 51 | $this->deploySampleBpmn(); 52 | $processDefinitions = ProcessDefinitionClient::get(); 53 | 54 | $processDefinition = ProcessDefinitionClient::find($processDefinitions[0]->id); 55 | $this->assertNotNull($processDefinition); 56 | 57 | $processDefinition = ProcessDefinitionClient::find(id: $processDefinitions[0]->id); 58 | $this->assertNotNull($processDefinition); 59 | } 60 | 61 | public function test_find_by_invalid_id(): void 62 | { 63 | $this->expectException(ObjectNotFoundException::class); 64 | ProcessDefinitionClient::find(id: 'invalid-id'); 65 | } 66 | 67 | public function test_find_by_key(): void 68 | { 69 | $this->deploySampleBpmn(); 70 | $processDefinition = ProcessDefinitionClient::find(key: 'process_1'); 71 | $this->assertInstanceOf(ProcessDefinition::class, $processDefinition); 72 | } 73 | 74 | public function test_find_by_invalid_key(): void 75 | { 76 | $this->expectException(ObjectNotFoundException::class); 77 | ProcessDefinitionClient::find(key: 'invalid-key'); 78 | } 79 | 80 | public function test_find_by_key_and_tenant_id(): void 81 | { 82 | $tenantId = 'tenant-1'; 83 | config()->set('services.camunda.tenant_id', $tenantId); 84 | $this->deploySampleBpmn(); 85 | 86 | $processDefinition = ProcessDefinitionClient::find(key: 'process_1', tenantId: $tenantId); 87 | $this->assertEquals($tenantId, $processDefinition->tenantId); 88 | } 89 | 90 | public function test_find_xml_by_id(): void 91 | { 92 | $this->deploySampleBpmn(); 93 | $processDefinitions = ProcessDefinitionClient::get(); 94 | $id = $processDefinitions[0]->id; 95 | $xml = ProcessDefinitionClient::xml(id: $id); 96 | $this->assertNotNull($xml); 97 | } 98 | 99 | public function test_find_xml_by_key(): void 100 | { 101 | $this->deploySampleBpmn(); 102 | $xml = ProcessDefinitionClient::xml(key: 'process_1'); 103 | $this->assertNotNull($xml); 104 | } 105 | 106 | public function test_find_xml_without_id_or_key(): void 107 | { 108 | $this->expectException(InvalidArgumentException::class); 109 | ProcessDefinitionClient::xml(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/ExternalTaskTest.php: -------------------------------------------------------------------------------- 1 | createExternalProcess(); 23 | $topics = [ 24 | ['topicName' => 'pdf', 'lockDuration' => 600_000] 25 | ]; 26 | $externalTasks = ExternalTaskClient::fetchAndLock('worker1', $topics); 27 | $this->assertCount(1, $externalTasks); 28 | } 29 | 30 | public function test_complete_task() 31 | { 32 | $this->createExternalProcess(); 33 | $topics = [ 34 | ['topicName' => 'pdf', 'lockDuration' => 600_000] 35 | ]; 36 | $externalTasks = ExternalTaskClient::fetchAndLock('worker1', $topics); 37 | $this->assertCount(1, $externalTasks); 38 | $task = $externalTasks[0]; 39 | $completed = ExternalTaskClient::complete($task->id, 'worker1'); 40 | $this->assertTrue($completed); 41 | } 42 | 43 | public function test_lock_individual_task() 44 | { 45 | $process = $this->createExternalProcess(); 46 | $topics = [ 47 | ['topicName' => 'pdf', 'lockDuration' => 600_000] 48 | ]; 49 | $externalTasks = ExternalTaskClient::getByProcessInstanceId($process->id); 50 | $this->assertCount(1, $externalTasks); 51 | $task = $externalTasks[0]; 52 | ExternalTaskClient::lock($task->id, 'worker1', 600_000); 53 | $lockedTasks = ExternalTaskClient::getTaskLocked(); 54 | $this->assertCount(1, $lockedTasks); 55 | } 56 | 57 | public function test_complete_task_with_variables() 58 | { 59 | $processInstance = $this->createExternalProcess(); 60 | $topics = [ 61 | ['topicName' => 'pdf', 'lockDuration' => 600_000] 62 | ]; 63 | $externalTasks = ExternalTaskClient::fetchAndLock('worker1', $topics); 64 | $this->assertCount(1, $externalTasks); 65 | $task = $externalTasks[0]; 66 | $completed = ExternalTaskClient::complete( 67 | $task->id, 68 | 'worker1', 69 | ['title' => ['value' => 'Sample Title', 'type' => 'string']] 70 | ); 71 | $this->assertTrue($completed); 72 | $processInstanceHistory = ProcessInstanceHistoryClient::find($processInstance->id); 73 | $this->assertEquals('COMPLETED', $processInstanceHistory->state); 74 | $variables = ProcessInstanceHistoryClient::variables($processInstance->id); 75 | $this->assertArrayHasKey('signature', $variables); 76 | $this->assertArrayHasKey('title', $variables); 77 | } 78 | 79 | public function test_unlock_locked_task() 80 | { 81 | $this->createExternalProcess(); 82 | $topics = [ 83 | ['topicName' => 'pdf', 'lockDuration' => 600_000] 84 | ]; 85 | $externalTasks = ExternalTaskClient::fetchAndLock('worker1', $topics); 86 | $task = $externalTasks[0]; 87 | $unlocked = ExternalTaskClient::unlock($task->id); 88 | $this->assertTrue($unlocked); 89 | } 90 | 91 | public function test_unlock_unlocked_task() 92 | { 93 | $processInstance = $this->createExternalProcess(); 94 | $tasks = ExternalTaskClient::getByProcessInstanceId($processInstance->id); 95 | $task = $tasks[0]; 96 | $unlocked = ExternalTaskClient::unlock($task->id); 97 | $this->assertTrue($unlocked); 98 | } 99 | 100 | protected function tearDown(): void 101 | { 102 | $this->truncateDeployment(); 103 | } 104 | 105 | private function createExternalProcess(): \Laravolt\Camunda\Dto\ProcessInstance 106 | { 107 | $variables = ['signature' => ['value' => 'Fulan', 'type' => 'string']]; 108 | $businessKey = 'key-1'; 109 | 110 | return ProcessDefinitionClient::start(key: 'processExternalTask', variables: $variables, businessKey: $businessKey); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in('src') 5 | ->in('tests'); 6 | 7 | $config = new PhpCsFixer\Config(); 8 | 9 | return $config->setRules( 10 | [ 11 | '@PSR12' => true, 12 | 'array_syntax' => ['syntax' => 'short'], 13 | 'binary_operator_spaces' => [ 14 | 'default' => 'single_space', 15 | 'operators' => ['|' => 'no_space'], 16 | ], 17 | 'blank_line_after_namespace' => true, 18 | 'blank_line_after_opening_tag' => true, 19 | 'blank_line_before_statement' => [ 20 | 'statements' => ['return'], 21 | ], 22 | 'braces' => true, 23 | 'cast_spaces' => true, 24 | 'class_definition' => true, 25 | 'concat_space' => [ 26 | 'spacing' => 'none', 27 | ], 28 | 'declare_equal_normalize' => true, 29 | 'elseif' => true, 30 | 'encoding' => true, 31 | 'full_opening_tag' => true, 32 | 'fully_qualified_strict_types' => true, // added by Shift 33 | 'function_declaration' => true, 34 | 'function_typehint_space' => true, 35 | 'heredoc_to_nowdoc' => true, 36 | 'include' => true, 37 | 'increment_style' => ['style' => 'post'], 38 | 'indentation_type' => true, 39 | 'linebreak_after_opening_tag' => true, 40 | 'line_ending' => true, 41 | 'lowercase_cast' => true, 42 | 'lowercase_keywords' => true, 43 | 'lowercase_static_reference' => true, // added from Symfony 44 | 'magic_method_casing' => true, // added from Symfony 45 | 'magic_constant_casing' => true, 46 | 'method_argument_space' => true, 47 | 'native_function_casing' => true, 48 | 'no_extra_blank_lines' => [ 49 | 'tokens' => [ 50 | 'extra', 51 | 'throw', 52 | 'use', 53 | 'use_trait', 54 | ], 55 | ], 56 | 'no_blank_lines_after_class_opening' => true, 57 | 'no_blank_lines_after_phpdoc' => true, 58 | 'no_closing_tag' => true, 59 | 'no_empty_phpdoc' => true, 60 | 'no_empty_statement' => true, 61 | 'no_leading_import_slash' => true, 62 | 'no_leading_namespace_whitespace' => true, 63 | 'no_mixed_echo_print' => [ 64 | 'use' => 'echo', 65 | ], 66 | 'no_multiline_whitespace_around_double_arrow' => true, 67 | 'multiline_whitespace_before_semicolons' => [ 68 | 'strategy' => 'no_multi_line', 69 | ], 70 | 'no_short_bool_cast' => true, 71 | 'no_singleline_whitespace_before_semicolons' => true, 72 | 'no_spaces_after_function_name' => true, 73 | 'no_spaces_around_offset' => true, 74 | 'no_spaces_inside_parenthesis' => true, 75 | 'no_trailing_comma_in_list_call' => true, 76 | 'no_trailing_comma_in_singleline_array' => true, 77 | 'no_trailing_whitespace' => true, 78 | 'no_trailing_whitespace_in_comment' => true, 79 | 'no_unneeded_control_parentheses' => true, 80 | 'no_unused_imports' => true, 81 | 'no_useless_return' => true, 82 | 'no_whitespace_before_comma_in_array' => true, 83 | 'no_whitespace_in_blank_line' => true, 84 | 'normalize_index_brace' => true, 85 | 'not_operator_with_successor_space' => true, 86 | 'object_operator_without_whitespace' => true, 87 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 88 | 'phpdoc_indent' => true, 89 | 'phpdoc_no_access' => true, 90 | 'phpdoc_no_package' => true, 91 | 'phpdoc_no_useless_inheritdoc' => true, 92 | 'phpdoc_scalar' => true, 93 | 'phpdoc_single_line_var_spacing' => true, 94 | 'phpdoc_summary' => true, 95 | 'phpdoc_to_comment' => true, 96 | 'phpdoc_trim' => true, 97 | 'phpdoc_types' => true, 98 | 'phpdoc_var_without_name' => true, 99 | 'short_scalar_cast' => true, 100 | 'simplified_null_return' => false, // disabled by Shift 101 | 'single_blank_line_at_eof' => true, 102 | 'single_blank_line_before_namespace' => true, 103 | 'single_class_element_per_statement' => true, 104 | 'single_import_per_statement' => true, 105 | 'single_line_after_imports' => true, 106 | 'single_line_comment_style' => [ 107 | 'comment_types' => ['hash'], 108 | ], 109 | 'single_quote' => true, 110 | 'space_after_semicolon' => true, 111 | 'standardize_not_equals' => true, 112 | 'switch_case_semicolon_to_colon' => true, 113 | 'switch_case_space' => true, 114 | 'ternary_operator_spaces' => true, 115 | 'trailing_comma_in_multiline_array' => true, 116 | 'trim_array_spaces' => true, 117 | 'visibility_required' => [ 118 | 'elements' => ['method', 'property'], 119 | ], 120 | 'whitespace_after_comma_in_array' => true, ] 121 | ) 122 | ->setFinder($finder); 123 | -------------------------------------------------------------------------------- /src/Http/ExternalTaskClient.php: -------------------------------------------------------------------------------- 1 | $topic, 27 | 'job' => is_string($job) ? $job : $job['job'] ?? null, 28 | 'lockDuration' => $job['lockDuration'] ?? 600_000, 29 | ]; 30 | } 31 | 32 | public static function find(string $id): ExternalTask 33 | { 34 | $response = self::make()->get("external-task/$id"); 35 | 36 | if ($response->status() === 404) { 37 | throw new ObjectNotFoundException($response->json('message')); 38 | } 39 | 40 | return ExternalTask::from($response->json()); 41 | } 42 | 43 | /** 44 | * @param string $processInstanceId 45 | * 46 | * @return ExternalTask[] 47 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 48 | */ 49 | public static function getByProcessInstanceId(string $id): array 50 | { 51 | $response = self::make()->get("external-task?processInstanceId=$id"); 52 | 53 | $data = []; 54 | if ($response->successful()) { 55 | foreach ($response->json() as $task) { 56 | $data[] = ExternalTask::from($task); 57 | } 58 | } 59 | 60 | return $data; 61 | } 62 | 63 | /** 64 | * @return ExternalTask[] 65 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 66 | */ 67 | public static function getTaskLocked(): array 68 | { 69 | $response = self::make()->get("external-task?locked=true"); 70 | 71 | $data = []; 72 | if ($response->successful()) { 73 | foreach ($response->json() as $task) { 74 | $data[] = ExternalTask::from($task); 75 | } 76 | } 77 | 78 | return $data; 79 | } 80 | 81 | /** 82 | * @param string $workerId 83 | * @param array $topics 84 | * @param int $maxTasks 85 | * 86 | * @return ExternalTask[] 87 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 88 | */ 89 | public static function fetchAndLock(string $workerId, array $topics, int $maxTasks = 10): array 90 | { 91 | $payload = [ 92 | 'workerId' => $workerId, 93 | 'maxTasks' => $maxTasks, 94 | 'topics' => $topics, 95 | ]; 96 | 97 | $response = self::make()->post("external-task/fetchAndLock", $payload); 98 | 99 | if ($response->successful()) { 100 | $data = []; 101 | foreach ($response->json() as $raw) { 102 | $data[] = ExternalTask::from($raw); 103 | } 104 | 105 | return $data; 106 | } 107 | 108 | throw new CamundaException($response->json('message') ?? $response->body()); 109 | } 110 | 111 | public static function complete( 112 | string $id, 113 | string $workerId, 114 | array $variables = [], 115 | array $localVariables = [] 116 | ): bool { 117 | $payload = compact('workerId'); 118 | if ($variables) { 119 | $payload['variables'] = $variables; 120 | } 121 | if ($localVariables) { 122 | $payload['localVariables'] = $localVariables; 123 | } 124 | $url = "external-task/$id/complete"; 125 | $response = self::make()->post($url, $payload); 126 | $isSuccessful = $response->status() === 204; 127 | 128 | if (! $isSuccessful) { 129 | throw (new UnexpectedResponseException)->for($url, $payload, $response->json()); 130 | } 131 | 132 | return $isSuccessful; 133 | } 134 | 135 | 136 | public static function fail( 137 | string $id, 138 | string $workerId, 139 | string $errorMessage = "Does not compute", 140 | int $retryTimeout = 60000, 141 | ): bool 142 | { 143 | 144 | $payload = compact('workerId', 'errorMessage', 'retryTimeout'); 145 | $url = "external-task/$id/failure"; 146 | $response = self::make()->post($url, $payload); 147 | $isSuccessful = $response->status() === 204; 148 | 149 | if (!$isSuccessful) { 150 | throw (new UnexpectedResponseException)->for($url, $payload, $response->json()); 151 | } 152 | 153 | return $isSuccessful; 154 | } 155 | 156 | public static function lock(string $id, string $workerId, int $duration): bool 157 | { 158 | $response = 159 | self::make()->post("external-task/$id/lock", ['workerId' => $workerId, 'lockDuration' => $duration]); 160 | 161 | return $response->status() === 204; 162 | } 163 | 164 | public static function unlock(string $id): bool 165 | { 166 | $response = self::make()->post("external-task/$id/unlock"); 167 | 168 | return $response->status() === 204; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /tests/TaskTest.php: -------------------------------------------------------------------------------- 1 | deploySampleBpmn(); 19 | } 20 | 21 | public function test_find_by_id() 22 | { 23 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 24 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 25 | $tasks = TaskClient::getByProcessInstanceId($processInstance->id); 26 | 27 | foreach ($tasks as $task) { 28 | $tastObject = TaskClient::find($task->id); 29 | $this->assertEquals($task->id, $tastObject->id); 30 | } 31 | } 32 | 33 | public function test_find_by_ids() 34 | { 35 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 36 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 37 | $tasks = TaskClient::getByProcessInstanceIds([$processInstance->id]); 38 | 39 | foreach ($tasks as $task) { 40 | $tastObject = TaskClient::find($task->id); 41 | $this->assertEquals($task->id, $tastObject->id); 42 | } 43 | } 44 | 45 | 46 | public function test_find_by_ids_is_empty() 47 | { 48 | $tasks = TaskClient::getByProcessInstanceIds([]); 49 | self::assertEmpty($tasks); 50 | 51 | } 52 | 53 | public function test_handle_invalid_id() 54 | { 55 | $this->expectException(ObjectNotFoundException::class); 56 | TaskClient::find('invalid-id'); 57 | } 58 | 59 | public function test_get_completed_task() 60 | { 61 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 62 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 63 | $tasks = TaskClient::getByProcessInstanceId($processInstance->id); 64 | 65 | foreach ($tasks as $task) { 66 | TaskClient::submit( 67 | $task->id, 68 | ['email' => ['value' => 'uyab.exe@gmail.com', 'type' => 'string']] 69 | ); 70 | $completedTask = TaskHistoryClient::find($task->id); 71 | $this->assertInstanceOf(TaskHistory::class, $completedTask); 72 | } 73 | } 74 | 75 | public function test_get_completed_task_with_invalid_id() 76 | { 77 | $this->expectException(ObjectNotFoundException::class); 78 | TaskHistoryClient::find('invalid-task-id'); 79 | } 80 | 81 | public function test_get_completed_tasks() 82 | { 83 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 84 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 85 | $tasks = TaskClient::getByProcessInstanceId($processInstance->id); 86 | 87 | foreach ($tasks as $task) { 88 | TaskClient::submit( 89 | $task->id, 90 | ['email' => ['value' => 'uyab.exe@gmail.com', 'type' => 'string']] 91 | ); 92 | } 93 | 94 | $completedTasks = TaskHistoryClient::getByProcessInstanceId($processInstance->id); 95 | $this->assertCount(1, $completedTasks); 96 | } 97 | 98 | public function test_submit_form() 99 | { 100 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 101 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 102 | $tasks = TaskClient::getByProcessInstanceId($processInstance->id); 103 | 104 | foreach ($tasks as $task) { 105 | $submitted = TaskClient::submit($task->id, ['email' => ['value' => 'uyab.exe@gmail.com', 'type' => 'string']]); 106 | $this->assertTrue($submitted); 107 | } 108 | } 109 | 110 | public function test_submit_form_and_return_variables() 111 | { 112 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 113 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 114 | $tasks = TaskClient::getByProcessInstanceId($processInstance->id); 115 | 116 | foreach ($tasks as $task) { 117 | $variables = TaskClient::submitAndReturnVariables($task->id, ['email' => ['value' => 'uyab.exe@gmail.com', 'type' => 'string']]); 118 | 119 | // 2 variables: 1 from start form (title), 1 from task form (email) 120 | $this->assertCount(2, $variables); 121 | $this->assertInstanceOf(Variable::class, $variables['title']); 122 | } 123 | } 124 | 125 | public function test_submit_form_with_invalid_id() 126 | { 127 | $this->expectException(CamundaException::class); 128 | TaskClient::submit('invalid-id', ['foo' => 'bar']); 129 | } 130 | 131 | public function test_submit_form_and_return_variables_with_invalid_id() 132 | { 133 | $this->expectException(CamundaException::class); 134 | TaskClient::submitAndReturnVariables('invalid-id', ['foo' => 'bar']); 135 | } 136 | 137 | protected function tearDown(): void 138 | { 139 | $this->truncateDeployment(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Http/TaskClient.php: -------------------------------------------------------------------------------- 1 | get("task/$id"); 15 | 16 | if ($response->status() === 404) { 17 | throw new ObjectNotFoundException($response->json('message')); 18 | } 19 | 20 | return Task::from($response->json()); 21 | } 22 | 23 | /** 24 | * @param string $processInstanceId 25 | * 26 | * @return Task[] 27 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 28 | */ 29 | public static function getByProcessInstanceId(string $id): array 30 | { 31 | $response = self::make()->get("task?processInstanceId=$id"); 32 | 33 | $data = []; 34 | if ($response->successful()) { 35 | foreach ($response->json() as $task) { 36 | $data[] = Task::from($task); 37 | } 38 | } 39 | 40 | return $data; 41 | } 42 | 43 | 44 | /** 45 | * @param array $payload 46 | * 47 | * @return Task[] 48 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 49 | */ 50 | public static function get(array $payload): array 51 | { 52 | $response = self::make()->post("task", $payload); 53 | 54 | $data = []; 55 | if ($response->successful()) { 56 | foreach ($response->json() as $task) { 57 | $data[] = Task::from($task); 58 | } 59 | } 60 | return $data; 61 | } 62 | /** 63 | * @param array $payload 64 | * 65 | * @return Task[] 66 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 67 | */ 68 | public static function unfinishedTask(array $payload): array 69 | { 70 | $payload['unfinished'] = true; 71 | return self::get($payload); 72 | } 73 | 74 | 75 | /** 76 | * @param string $processInstanceIds 77 | * 78 | * @return Task[] 79 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 80 | */ 81 | public static function getByProcessInstanceIds(array $ids): array 82 | { 83 | if(empty($ids)){ 84 | return []; 85 | } 86 | $response = self::make()->post("task", [ 87 | "processInstanceIdIn" => $ids 88 | ]); 89 | 90 | $data = []; 91 | if ($response->successful()) { 92 | foreach ($response->json() as $task) { 93 | $data[] = Task::from($task); 94 | } 95 | } 96 | 97 | return $data; 98 | } 99 | 100 | public static function claim(string $id, string $userId): bool 101 | { 102 | $response = self::make()->post("task/$id/claim", [ 103 | "userId" => $userId 104 | ]); 105 | 106 | if ($response->successful()) { 107 | return true; 108 | } 109 | 110 | return false; 111 | } 112 | 113 | public static function unclaim(string $id): bool 114 | { 115 | $response = self::make()->post("task/$id/unclam"); 116 | 117 | if ($response->successful()) { 118 | return true; 119 | } 120 | 121 | return false; 122 | } 123 | 124 | public static function assign(string $id, string $userId): bool 125 | { 126 | $response = self::make()->post("task/$id/assignee", [ 127 | "userId" => $userId 128 | ]); 129 | 130 | if ($response->successful()) { 131 | return true; 132 | } 133 | 134 | return false; 135 | } 136 | 137 | 138 | /** 139 | * @param string $processInstanceIds 140 | * 141 | * @return Task[] 142 | * @throws \Spatie\DataTransferObject\Exceptions\UnknownProperties 143 | */ 144 | public static function getByAssignedAndProcessInstanceId($user_id, array $ids = []): array 145 | { 146 | $payload = [ 147 | "assignee" => $user_id 148 | ]; 149 | if ($ids != []) { 150 | $payload['processInstanceIdIn'] = implode(",", $ids); 151 | } 152 | 153 | $response = self::make()->post("task", $payload); 154 | 155 | $data = []; 156 | if ($response->successful()) { 157 | foreach ($response->json() as $task) { 158 | $data[] = Task::from($task); 159 | } 160 | } 161 | 162 | return $data; 163 | } 164 | 165 | public static function submit(string $id, array $variables): bool 166 | { 167 | $varData = (object)[]; 168 | if (!empty($variables)) { 169 | $varData = (object)$variables; 170 | } 171 | $response = self::make()->post( 172 | "task/$id/submit-form", 173 | ['variables' => $varData] 174 | ); 175 | 176 | if ($response->status() === 204) { 177 | return true; 178 | } 179 | 180 | throw new CamundaException($response->body(), $response->status()); 181 | } 182 | 183 | public static function submitAndReturnVariables(string $id, array $variables): array 184 | { 185 | $response = self::make()->post( 186 | "task/$id/submit-form", 187 | ['variables' => $variables, 'withVariablesInReturn' => true] 188 | ); 189 | 190 | if ($response->status() === 200) { 191 | $raws = $response->json(); 192 | $variables = []; 193 | foreach ($raws as $name => $raw) { 194 | $raw['name'] = $name; 195 | $variables[$name] = Variable::from($raw); 196 | } 197 | 198 | return $variables; 199 | } 200 | 201 | throw new CamundaException($response->body(), $response->status()); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /tests/ProcessInstanceTest.php: -------------------------------------------------------------------------------- 1 | deploySampleBpmn(); 17 | } 18 | 19 | public function test_find_by_id() 20 | { 21 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 22 | $processInstance1 = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 23 | $processInstance2 = ProcessInstanceClient::find(id: $processInstance1->id); 24 | $processInstance3 = ProcessInstanceClient::find($processInstance1->id); 25 | 26 | $this->assertEquals($processInstance1->id, $processInstance2->id); 27 | $this->assertEquals($processInstance2->id, $processInstance3->id); 28 | } 29 | 30 | public function test_get() 31 | { 32 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 33 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 34 | 35 | $processInstances = ProcessInstanceClient::get(); 36 | 37 | $this->assertCount(1, $processInstances); 38 | } 39 | public function test_get_with_parameter() 40 | { 41 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 42 | $processInstances = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 43 | $params = [ 44 | "processIsntanceIds" => [ 45 | $processInstances->id, 46 | ] 47 | ]; 48 | $processInstances = ProcessInstanceClient::get($params); 49 | 50 | $this->assertCount(1, $processInstances); 51 | } 52 | public function test_get_with_parameter_is_empty() 53 | { 54 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 55 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 56 | $processInstances = ProcessInstanceClient::get([]); 57 | 58 | $this->assertCount(1, $processInstances); 59 | } 60 | public function test_get_with_broken_parameter() 61 | { 62 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 63 | $processInstances = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 64 | $mapmany = []; 65 | for ($i = 0; $i < 500; $i++) { 66 | $mapmany[] = $processInstances->id; 67 | } 68 | $params = [ 69 | "processIsntanceIds" => $mapmany 70 | ]; 71 | $processInstances = ProcessInstanceClient::get($params); 72 | 73 | $this->assertCount(1, $processInstances); 74 | } 75 | 76 | public function test_get_by_parameters() 77 | { 78 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 79 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables, businessKey: '001'); 80 | 81 | $processInstances = ProcessInstanceClient::get(['businessKey' => '001']); 82 | $this->assertCount(1, $processInstances); 83 | 84 | $processInstances = ProcessInstanceClient::get(['businessKey' => '002']); 85 | $this->assertCount(0, $processInstances); 86 | } 87 | 88 | public function test_get_with_busniessKey() 89 | { 90 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 91 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables, businessKey: '001'); 92 | $processInstance = ProcessInstanceClient::findByBusniessKey('001'); 93 | $this->assertEquals('001', $processInstance->businessKey); 94 | } 95 | 96 | 97 | public function test_get_with_variables() 98 | { 99 | $random = Random::generate(); 100 | $variables = ['title' => ['value' => 'Foo' . $random, 'type' => 'string']]; 101 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables, businessKey: '001'); 102 | $processInstance = ProcessInstanceClient::getByVariables([ 103 | [ 104 | 'name' => 'title', 105 | 'operator' => "eq", 106 | 'value' => $variables['title']['value'], 107 | ], 108 | ]); 109 | $this->assertCount(1, $processInstance); 110 | } 111 | 112 | public function test_get_with_variables_empty() 113 | { 114 | $random = Random::generate(); 115 | $variables = ['title' => ['value' => 'Foo' . $random, 'type' => 'string']]; 116 | ProcessDefinitionClient::start(key: 'process_1', variables: $variables, businessKey: Random::generate()); 117 | $processInstance = ProcessInstanceClient::getByVariables([]); 118 | $this->assertCount(1, $processInstance); 119 | } 120 | 121 | 122 | public function test_get_variables() 123 | { 124 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 125 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 126 | $variables = ProcessInstanceClient::variables($processInstance->id); 127 | $this->assertCount(1, $variables); 128 | $this->assertInstanceOf(Variable::class, $variables['title']); 129 | $this->assertEquals('String', $variables['title']->type); 130 | $this->assertEquals('Foo', $variables['title']->value); 131 | } 132 | 133 | public function test_delete() 134 | { 135 | $variables = ['title' => ['value' => 'Foo', 'type' => 'string']]; 136 | $processInstance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 137 | $deleted = ProcessInstanceClient::delete($processInstance->id); 138 | $this->assertTrue($deleted); 139 | 140 | $this->expectException(ObjectNotFoundException::class); 141 | ProcessInstanceClient::find($processInstance->id); 142 | } 143 | 144 | protected function tearDown(): void 145 | { 146 | $this->truncateDeployment(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # laravolt/camunda 3 | Convenience Laravel HTTP client wrapper to interact with Camunda REST API. 4 | 5 | ## Installation 6 | `composer require laravolt/camunda` 7 | 8 | ## Configuration 9 | 10 | Prepare your `.env`: 11 | 12 | ```dotenv 13 | CAMUNDA_URL=http://localhost:8080/engine-rest 14 | 15 | #optional 16 | CAMUNDA_TENANT_ID= 17 | CAMUNDA_USER= 18 | CAMUNDA_PASSWORD= 19 | ``` 20 | 21 | Add following entries to `config/services.php`: 22 | ```php 23 | 'camunda' => [ 24 | 'url' => env('CAMUNDA_URL', 'https://localhost:8080/engine-rest'), 25 | 'user' => env('CAMUNDA_USER', 'demo'), 26 | 'password' => env('CAMUNDA_PASSWORD', 'demo'), 27 | 'tenant_id' => env('CAMUNDA_TENANT_ID', ''), 28 | ], 29 | ``` 30 | 31 | 32 | ## Usage 33 | 34 | ### Process Definition 35 | ```php 36 | use Laravolt\Camunda\Http\ProcessDefinitionClient; 37 | 38 | $variables = ['title' => ['value' => 'Sample Title', 'type' => 'string']]; 39 | 40 | // Start new process instance 41 | $instance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables); 42 | 43 | // Start new process instance with some business key 44 | $instance = ProcessDefinitionClient::start(key: 'process_1', variables: $variables, businessKey: 'somekey'); 45 | 46 | 47 | // Get BPMN definition in XML format 48 | ProcessDefinitionClient::xml(key: 'process_1'); 49 | ProcessDefinitionClient::xml(id: 'process_1:xxxx'); 50 | 51 | // Get all definition 52 | ProcessDefinitionClient::get(); 53 | 54 | // Get definitions based on some parameters 55 | $params = ['latestVersion' => true]; 56 | ProcessDefinitionClient::get($params); 57 | ``` 58 | 59 | Camunda API reference: https://docs.camunda.org/manual/latest/reference/rest/process-definition/ 60 | 61 | 62 | 63 | 64 | ### Process Instance 65 | ```php 66 | use Laravolt\Camunda\Http\ProcessInstanceClient; 67 | 68 | // Find by ID 69 | $processInstance = ProcessInstanceClient::find(id: 'some-id'); 70 | 71 | // Get all instances 72 | ProcessInstanceClient::get(); 73 | 74 | // Get instances based on some parameters 75 | $params = ['businessKeyLike' => 'somekey']; 76 | ProcessInstanceClient::get($params); 77 | 78 | ProcessInstanceClient::variables(id: 'some-id'); 79 | ProcessInstanceClient::delete(id: 'some-id'); 80 | ``` 81 | 82 | Camunda API reference: https://docs.camunda.org/manual/latest/reference/rest/process-instance/ 83 | 84 | 85 | ### Message Event 86 | 87 | ```php 88 | use Laravolt\Camunda\Http\MessageEventClient; 89 | // Start processinstance with message event 90 | // Required 91 | // messageName : message event name 92 | // businessKey : Busniess key for process instance 93 | 94 | // Rerturn Process insntance from message event 95 | 96 | MessageEventClient::start(messageName: "testing", businessKey: "businessKey") 97 | 98 | 99 | ``` 100 | 101 | 102 | 103 | ### Task 104 | ```php 105 | use Laravolt\Camunda\Http\TaskClient; 106 | 107 | $task = TaskClient::find(id: 'task-id'); 108 | $tasks = TaskClient::getByProcessInstanceId(id: 'process-instance-id'); 109 | $tasks = TaskClient::getByProcessInstanceIds(ids: 'arrayof-process-instance-ids'); 110 | TaskClient::submit(id: 'task-id', variables: ['name' => ['value' => 'Foo', 'type' => 'String']]); // will return true or false 111 | $variables = TaskClient::submitAndReturnVariables(id: 'task-id', variables: ['name' => ['value' => 'Foo', 'type' => 'String']]) // will return array of variable 112 | 113 | // Claim a Task 114 | $tasks = TaskClient::claim($task_id, $user_id); 115 | // Unclaim a Task 116 | $tasks = TaskClient::unclaim($task_id); 117 | // Assign a Task 118 | $tasks = TaskClient::assign($task_id, $user_id); 119 | 120 | 121 | 122 | ``` 123 | 124 | Camunda API reference: https://docs.camunda.org/manual/latest/reference/rest/task/ 125 | 126 | ### External Task 127 | ```php 128 | use Laravolt\Camunda\Http\ExternalTaskClient; 129 | 130 | $topics = [ 131 | ['topicName' => 'pdf', 'lockDuration' => 600_000] 132 | ]; 133 | $externalTasks = ExternalTaskClient::fetchAndLock('worker1', $topics); 134 | foreach ($externalTasks as $externalTask) { 135 | // do something with $externalTask 136 | // Mark as complete after finished 137 | ExternalTaskClient::complete($externalTasks->id); 138 | } 139 | 140 | // Unlock some task 141 | ExternalTaskClient::unlock($task->id) 142 | 143 | // Get task locked 144 | $externalTaskLocked = ExternalTaskClient::getTaskLocked(); 145 | ``` 146 | 147 | Camunda API reference: https://docs.camunda.org/manual/latest/reference/rest/external-task/ 148 | 149 | ### Consume External Task 150 | Create a new job to consume external task via `php artisan make:job ` and modify the skeleton: 151 | 152 | ```php 153 | use Laravolt\Camunda\Dto\ExternalTask; 154 | use Laravolt\Camunda\Http\ExternalTaskClient; 155 | 156 | public function __construct( 157 | public string $workerId, 158 | public ExternalTask $task 159 | ) { 160 | } 161 | 162 | public function handle() 163 | { 164 | // Do something with $this->task, e.g: get the variables and generate PDF 165 | $variables = \Laravolt\Camunda\Http\ProcessInstanceClient::variables($this->task->processDefinitionId); 166 | // PdfService::generate() 167 | 168 | // Complete the task 169 | $status = ExternalTaskClient::complete($this->task->id, $this->workerId); 170 | } 171 | 172 | ``` 173 | 174 | Subscribe to some topic: 175 | ```php 176 | // AppServiceProvider.php 177 | use Laravolt\Camunda\Http\ExternalTaskClient; 178 | 179 | public function boot() 180 | { 181 | ExternalTaskClient::subscribe('pdf', GeneratePdf::class); 182 | } 183 | ``` 184 | 185 | Register the scheduler: 186 | ```php 187 | // app/Console/Kernel.php 188 | protected function schedule(Schedule $schedule) 189 | { 190 | $schedule->command('camunda:consume-external-task --workerId=worker1')->everyMinute(); 191 | } 192 | ``` 193 | 194 | If you need shorter pooling time (sub-minute frequency), please check [Laravel Short Schedule](https://github.com/spatie/laravel-short-schedule). 195 | 196 | Reference: 197 | - https://laravel.com/docs/master/scheduling 198 | - https://laravel.com/docs/master/queues 199 | - https://github.com/spatie/laravel-short-schedule 200 | 201 | ### Task History (Completed Task) 202 | 203 | ```php 204 | use Laravolt\Camunda\Http\TaskHistoryClient; 205 | 206 | $completedTask = TaskHistoryClient::find(id: 'task-id'); 207 | $completedTasks = TaskHistoryClient::getByProcessInstanceId(id: 'process-instance-id'); 208 | ``` 209 | 210 | Camunda API reference: https://docs.camunda.org/manual/latest/reference/rest/history/task/ 211 | 212 | 213 | 214 | ### Deployment 215 | 216 | ```php 217 | use Laravolt\Camunda\Http\DeploymentClient; 218 | 219 | // Deploy bpmn file(s) 220 | DeploymentClient::create('test-deploy', '/path/to/file.bpmn'); 221 | DeploymentClient::create('test-deploy', ['/path/to/file1.bpmn', '/path/to/file2.bpmn']); 222 | 223 | // Get deployment list 224 | DeploymentClient::get(); 225 | 226 | // Find detailed info about some deployment 227 | DeploymentClient::find($id); 228 | 229 | // Truncate (delete all) deployments 230 | $cascade = true; 231 | DeploymentClient::truncate($cascade); 232 | 233 | // Delete single deployment 234 | DeploymentClient::delete(id: 'test-deploy', cascade: $cascade); 235 | 236 | ``` 237 | 238 | 239 | 240 | ### Raw Endpoint 241 | 242 | You can utilize `Laravolt\Camunda\CamundaClient` to call any Camunda REST endpoint. 243 | ```php 244 | use Laravolt\Camunda\CamundaClient; 245 | 246 | $response = CamundaClient::make()->get('version'); 247 | echo $response->status(); // 200 248 | echo $response->object(); // sdtClass 249 | echo $response->json(); // array, something like ["version" => "7.14.0"] 250 | ``` 251 | > `CamundaClient::make()` is a wrapper for [Laravel HTTP Client](https://laravel.com/docs/master/http-client) with base URL already set based on your Camunda services configuration. Take a look at the documentation for more information. 252 | -------------------------------------------------------------------------------- /resources/rekrutmen-sederhana.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Flow_02wkiqx 16 | Flow_1bpz4e7 17 | 18 | 19 | Flow_1bpz4e7 20 | Flow_1xc9dz1 21 | Flow_0borlq2 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Flow_02wkiqx 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Flow_0borlq2 60 | Flow_19x83oz 61 | 62 | 63 | 64 | ${status_administrasi} = true 65 | 66 | 67 | 68 | 69 | ${status_administrasi} = false 70 | 71 | 72 | ${lulus_wawancara = false} 73 | 74 | 75 | Flow_19x83oz 76 | Flow_1m5nwcq 77 | Flow_0lfjxwi 78 | 79 | 80 | ${lulus_wawancara = true} 81 | 82 | 83 | Flow_0lfjxwi 84 | 85 | 86 | 87 | Flow_1xc9dz1 88 | Flow_1m5nwcq 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | --------------------------------------------------------------------------------