Custom Services Provider
When to Use
Use this when you want to expose a custom Drupal capability — one not covered by the four built-in submodules — as an Orchestration service callable by external platforms.
Decision
| Question | Answer |
|---|---|
| How is a custom provider discovered? | Symfony service tag: orchestration_services_provider |
| Do I need a plugin annotation? | No — tagged Symfony service is sufficient |
What must getId() return? |
A string with no colons (UUID format: getId() . '::' . $service_id) |
What must execute() return? |
array\|string — serialize objects before returning |
What must getAll() avoid? |
Exceptions (breaks the whole /orchestration/services endpoint) and expensive work (called on every request) |
Pattern
1. Implement ServicesProviderInterface:
namespace Drupal\my_module;
use Drupal\orchestration\Service;
use Drupal\orchestration\ServiceConfig;
use Drupal\orchestration\ServicesProviderInterface;
class MyServicesProvider implements ServicesProviderInterface {
public function getId(): string {
return 'my_module'; // No colons; use module machine name
}
public function getAll(): array {
$service = new Service($this, 'my_action', 'My Action', 'Does something useful.');
$service->addConfig(new ServiceConfig(
key: 'target_id',
label: 'Target ID',
description: 'The ID of the thing to act on.',
required: TRUE,
type: 'string',
isEditable: TRUE,
defaultValue: '',
weight: 0,
constraints: [],
));
return [$service];
}
public function execute(Service $service, array $config): array|string {
$targetId = (string) ($config['target_id'] ?? '');
// ... your logic ...
return ['success' => TRUE, 'target_id' => $targetId];
}
}
2. Register as a tagged Drupal service:
# my_module.services.yml
services:
my_module.orchestration_services_provider:
class: Drupal\my_module\MyServicesProvider
tags:
- { name: 'orchestration_services_provider' }
3. Verify:
drush cache-rebuild
curl -u admin:password https://your-site.example.com/orchestration/services
# Look for {"id": "my_module::my_action", ...}
Choice constraint example (creates a dropdown in the external platform):
new ServiceConfig(
key: 'status',
label: 'Status',
required: TRUE,
type: 'string',
constraints: ['Choice' => ['choices' => ['draft' => 'Draft', 'published' => 'Published']]],
)
// API response options: [{"key": "draft", "name": "Draft"}, {"key": "published", "name": "Published"}]
Common Mistakes
- Wrong: Using a colon in
getId()→ Right: The UUID isgetId() . '::' . $service_id; a colon in the provider ID creates a malformed triple-colon UUID - Wrong: Returning objects from
execute()→ Right: The contract isarray|string; serialize to array before returning - Wrong: Forgetting
drush cache-rebuildafter adding the service tag → Right: The service collector is resolved at container build time - Wrong: Throwing exceptions from
getAll()→ Right: An exception in one provider breaks the entire/orchestration/servicesendpoint for all callers; catch internally and log - Wrong: Doing expensive work (entity loads, external API calls) in
getAll()→ Right: This method is called on every/orchestration/servicesrequest; cache if needed
See Also
- Architecture
- Orchestration API Reference
- Reference:
src/ServicesProviderInterface.php,src/Service.php,src/ServiceConfig.php,docs/develop/plugin.md