Learn how to use the Service Layer architecture in your Claude PHP Agent applications.
- What is the Service Layer?
- Quick Start
- Core Concepts
- Your First Service
- Service Dependencies
- Configuration
- Testing
- Next Steps
The Service Layer is an enterprise-grade architecture that provides:
- Centralized Management - Single ServiceManager for all services
- Automatic Dependency Injection - Services automatically get their dependencies
- Lifecycle Management - Proper initialization and cleanup
- Type Safety - Compile-time service type checking
- Easy Testing - Mock services for unit tests
Create a bootstrap file for your application:
<?php
// bootstrap.php
use ClaudeAgents\Services\ServiceManager;
use ClaudeAgents\Services\Settings\SettingsServiceFactory;
use ClaudeAgents\Services\Cache\CacheServiceFactory;
// Get ServiceManager singleton
$serviceManager = ServiceManager::getInstance();
// Register services you need
$serviceManager
->registerFactory(new SettingsServiceFactory())
->registerFactory(new CacheServiceFactory());
return $serviceManager;<?php
require_once 'bootstrap.php';
use ClaudeAgents\Services\ServiceManager;
use ClaudeAgents\Services\ServiceType;
// Get the service manager
$serviceManager = ServiceManager::getInstance();
// Get a service (creates it if needed)
$cache = $serviceManager->get(ServiceType::CACHE);
// Use the service
$cache->set('user:123', ['name' => 'John Doe']);
$user = $cache->get('user:123');
echo "User: " . $user['name'];// At application shutdown
register_shutdown_function(function() {
ServiceManager::getInstance()->teardownAll();
});The ServiceManager is a singleton that manages all services:
// Get the singleton instance
$manager = ServiceManager::getInstance();
// Register a service factory
$manager->registerFactory(new CacheServiceFactory());
// Get a service (lazy initialization)
$cache = $manager->get(ServiceType::CACHE);
// Check if service exists
if ($manager->has(ServiceType::CACHE)) {
// Service is registered
}Use the ServiceType enum for type-safe service access:
use ClaudeAgents\Services\ServiceType;
// Available service types:
ServiceType::SETTINGS // Configuration
ServiceType::CACHE // Caching
ServiceType::STORAGE // File storage
ServiceType::VARIABLE // Variables & secrets
ServiceType::TRACING // Observability tracing
ServiceType::TELEMETRY // Metrics
ServiceType::SESSION // User sessions
ServiceType::TRANSACTION // Database transactionsServices go through three states:
// 1. Registered (factory registered, not created)
$manager->registerFactory(new CacheServiceFactory());
// 2. Initialized (service created and ready)
$cache = $manager->get(ServiceType::CACHE);
assert($cache->isReady()); // true
// 3. Torn down (cleaned up)
$manager->teardownAll();Let's build a simple application using services:
<?php
use ClaudeAgents\Services\ServiceManager;
use ClaudeAgents\Services\ServiceType;
use ClaudeAgents\Services\Storage\StorageServiceFactory;
use ClaudeAgents\Services\Cache\CacheServiceFactory;
class UserProfileManager
{
private $storage;
private $cache;
public function __construct()
{
$manager = ServiceManager::getInstance();
$this->storage = $manager->get(ServiceType::STORAGE);
$this->cache = $manager->get(ServiceType::CACHE);
}
public function getProfile(string $userId): array
{
// Try cache first
$cacheKey = "profile:{$userId}";
if ($this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
// Load from storage
$data = $this->storage->getFile('users', "{$userId}.json");
$profile = json_decode($data, true);
// Cache for 1 hour
$this->cache->set($cacheKey, $profile, 3600);
return $profile;
}
public function updateProfile(string $userId, array $data): void
{
// Save to storage
$this->storage->saveFile('users', "{$userId}.json", json_encode($data));
// Invalidate cache
$this->cache->delete("profile:{$userId}");
}
}
// Bootstrap
$manager = ServiceManager::getInstance();
$manager
->registerFactory(new StorageServiceFactory())
->registerFactory(new CacheServiceFactory());
// Use
$profiles = new UserProfileManager();
$profile = $profiles->getProfile('user-123');Services can depend on other services. The ServiceManager automatically resolves dependencies:
// CacheService depends on SettingsService
class CacheService implements ServiceInterface
{
public function __construct(
private SettingsService $settings // Auto-injected!
) {}
}
// Just register both factories
$manager
->registerFactory(new SettingsServiceFactory())
->registerFactory(new CacheServiceFactory());
// Dependencies are automatically resolved
$cache = $manager->get(ServiceType::CACHE);
// SettingsService was created first, then injected into CacheService// These are equivalent:
$manager
->registerFactory(new CacheServiceFactory())
->registerFactory(new SettingsServiceFactory());
// Or
$manager
->registerFactory(new SettingsServiceFactory())
->registerFactory(new CacheServiceFactory());Create a configuration file:
// config/services.php
return [
'cache' => [
'driver' => 'redis',
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
],
],
'storage' => [
'directory' => './storage',
],
];Load it during bootstrap:
use ClaudeAgents\Services\Settings\SettingsServiceFactory;
$manager->registerFactory(
new SettingsServiceFactory(configFile: './config/services.php')
);
// Access configuration
$settings = $manager->get(ServiceType::SETTINGS);
$cacheDriver = $settings->get('cache.driver'); // 'redis'Override configuration with environment variables:
# Prefix with CLAUDE_AGENT_
export CLAUDE_AGENT_CACHE_DRIVER=redis
export CLAUDE_AGENT_CACHE_REDIS_HOST=localhost// Automatically loaded from environment
$settings = $manager->get(ServiceType::SETTINGS);
$driver = $settings->get('cache.driver'); // 'redis' from envuse ClaudeAgents\Services\ServiceManager;
use ClaudeAgents\Services\ServiceType;
use ClaudeAgents\Cache\ArrayCache;
use ClaudeAgents\Services\Cache\CacheService;
use PHPUnit\Framework\TestCase;
class UserProfileManagerTest extends TestCase
{
private ServiceManager $manager;
protected function setUp(): void
{
$this->manager = ServiceManager::getInstance();
$this->manager->reset();
// Create mock cache
$settings = new SettingsService();
$settings->initialize();
$mockCache = new CacheService($settings, new ArrayCache());
$mockCache->initialize();
// Register mock
$this->manager->mock(ServiceType::CACHE, $mockCache);
}
public function testGetProfile(): void
{
$profiles = new UserProfileManager();
// Test with mocked cache
// ...
}
}class CacheServiceTest extends TestCase
{
public function testSetAndGet(): void
{
$settings = new SettingsService();
$settings->initialize();
$cache = new CacheService($settings, new ArrayCache());
$cache->initialize();
$cache->set('key', 'value');
$this->assertSame('value', $cache->get('key'));
}
}Now that you understand the basics, explore specific services:
- SettingsService Tutorial - Configuration management
- CacheService Tutorial - Caching strategies
- StorageService Tutorial - File storage
- VariableService Tutorial - Secrets management
- TracingService Tutorial - Observability
- TelemetryService Tutorial - Metrics
- SessionService Tutorial - Session management
class MyClass
{
private ServiceManager $services;
public function __construct(?ServiceManager $services = null)
{
$this->services = $services ?? ServiceManager::getInstance();
}
protected function getCache()
{
return $this->services->get(ServiceType::CACHE);
}
}class MyClass
{
public function __construct(
private ?CacheService $cache = null
) {
$this->cache = $cache ?? ServiceManager::getInstance()
->get(ServiceType::CACHE);
}
}class MyClass
{
public static function create(?ServiceManager $manager = null): self
{
$manager = $manager ?? ServiceManager::getInstance();
return new self($manager);
}
}ServiceNotFoundException: No factory registered for service: cache
Solution: Register the factory before accessing the service:
$manager->registerFactory(new CacheServiceFactory());
$cache = $manager->get(ServiceType::CACHE);RuntimeException: Circular dependency detected while creating service: X
Solution: Review service dependencies and refactor to break the cycle.
Solution: Services are automatically initialized. If you create a service manually, call initialize():
$service = new MyService();
$service->initialize();You've learned:
✅ How to bootstrap the ServiceManager
✅ How to register and use services
✅ Service lifecycle management
✅ Dependency injection basics
✅ Configuration management
✅ Testing with mock services
Ready to dive deeper? Check out the service-specific tutorials!