dbellettini / eventstore-client
KurrentDB client for PHP (core)
Installs: 7 427
Dependents: 5
Suggesters: 0
Security: 0
Stars: 68
Watchers: 13
Forks: 24
Open Issues: 3
Requires
- php: ^8.3
- guzzlehttp/psr7: ^2.8
- php-http/httplug: ^2.4
- ramsey/uuid: ^4.9
Requires (Dev)
- ergebnis/composer-normalize: ^2.48
- friendsofphp/php-cs-fixer: ^3.87
- guzzlehttp/guzzle: ^7.10
- kevinrob/guzzle-cache-middleware: ^6.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.3
- rector/rector: ^2.1
- symfony/cache: ^7.3
Suggests
- friendsofouro/broadway-eventstore: Integrate Event Store with Broadway
- friendsofouro/geteventstore-bundle: Integrate Event Store with Symfony 2
- guzzlehttp/guzzle: Default PSR-7 implementation
- kevinrob/guzzle-cache-middleware: Cache HTTP requests
- symfony/cache: Use Symfony to cache HTTP requests
Replaces
- dbellettini/geteventstore-core: v0.14.0
- dbellettini/php-eventstore-client: v0.14.0
- friendsofouro/geteventstore-core: v0.14.0
This package is auto-updated.
Last update: 2025-09-09 12:05:41 UTC
README
A modern PHP client library for KurrentDB (formerly EventStoreDB) HTTP API, designed for event sourcing applications.
Note: This library uses the HTTP API. For TCP integration, see prooph/event-store-client.
Features
- ✅ Support for KurrentDB HTTP API
- ✅ Event stream management (read, write, delete)
- ✅ Optimistic concurrency control
- ✅ Stream iteration (forward and backward)
- ✅ Batch operations for performance
- ✅ Built-in HTTP caching support
- ✅ PSR-7 and PSR-18 compliant
- ✅ Type-safe with PHP 8.4 features
- ✅ Comprehensive error handling
Requirements
- PHP 8.4 or higher
- KurrentDB server (HTTP API enabled)
Installation
Via Composer
composer require friendsofouro/kurrentdb-core
Via Metapackage (Recommended)
For the complete package with additional integrations:
composer require friendsofouro/kurrentdb
Local Development
git clone git@github.com:FriendsOfOuro/kurrentdb-php-core.git cd kurrentdb-php-core # Start the environment make up # Install dependencies make install # Run tests to verify setup make test
Quick Start
Basic Setup
use KurrentDB\EventStore; use KurrentDB\Http\GuzzleHttpClient; // Create HTTP client and connect to KurrentDB $httpClient = new GuzzleHttpClient(); $eventStore = new EventStore('http://admin:changeit@127.0.0.1:2113', $httpClient);
Writing Events
use KurrentDB\WritableEvent; use KurrentDB\WritableEventCollection; // Write a single event $event = WritableEvent::newInstance( 'UserRegistered', ['userId' => '123', 'email' => 'user@example.com'], ['timestamp' => time()] // optional metadata ); $version = $eventStore->writeToStream('user-123', $event); // Write multiple events atomically $events = new WritableEventCollection([ WritableEvent::newInstance('OrderPlaced', ['orderId' => '456']), WritableEvent::newInstance('PaymentProcessed', ['amount' => 99.99]) ]); $eventStore->writeToStream('order-456', $events);
Reading Events
use KurrentDB\StreamFeed\EntryEmbedMode; $feed = $eventStore->openStreamFeed('user-123'); // Get entries and read events foreach ($feed->getEntries() as $entry) { $event = $eventStore->readEvent($entry->getEventUrl()); echo sprintf("Event: %s, Version: %d\n", $event->getType(), $event->getVersion() ); } // Read with embedded event data for better performance $feed = $eventStore->openStreamFeed('user-123', EntryEmbedMode::BODY);
Stream Navigation
use KurrentDB\StreamFeed\LinkRelation; // Navigate through pages $feed = $eventStore->openStreamFeed('large-stream'); $nextPage = $eventStore->navigateStreamFeed($feed, LinkRelation::NEXT); // Use iterators for convenient traversal $iterator = $eventStore->forwardStreamFeedIterator('user-123'); foreach ($iterator as $entryWithEvent) { $event = $entryWithEvent->getEvent(); // Process event... } // Backward iteration $reverseIterator = $eventStore->backwardStreamFeedIterator('user-123');
Optimistic Concurrency Control
use KurrentDB\ExpectedVersion; // Write with expected version $eventStore->writeToStream( 'user-123', $event, 5 ); // Special version expectations $eventStore->writeToStream('new-stream', $event, ExpectedVersion::NO_STREAM); $eventStore->writeToStream('any-stream', $event, ExpectedVersion::ANY);
Stream Management
use KurrentDB\StreamDeletion; // Soft delete (can be recreated) $eventStore->deleteStream('old-stream', StreamDeletion::SOFT); // Hard delete (permanent, will be 410 Gone) $eventStore->deleteStream('obsolete-stream', StreamDeletion::HARD);
Advanced Usage
HTTP Caching
Improve performance with built-in caching:
// Filesystem cache $httpClient = GuzzleHttpClient::withFilesystemCache('/tmp/kurrentdb-cache'); // APCu cache (in-memory) $httpClient = GuzzleHttpClient::withApcuCache(); // Custom PSR-6 cache use Symfony\Component\Cache\Adapter\RedisAdapter; $cacheAdapter = new RedisAdapter($redisClient); $httpClient = GuzzleHttpClient::withPsr6Cache($cacheAdapter); $eventStore = new EventStore($url, $httpClient);
Batch Operations
Read multiple events efficiently:
// Collect event URLs $eventUrls = []; foreach ($feed->getEntries() as $entry) { $eventUrls[] = $entry->getEventUrl(); } // Batch read $events = $eventStore->readEventBatch($eventUrls); foreach ($events as $event) { // Process events... }
Error Handling
use KurrentDB\Exception\StreamNotFoundException; use KurrentDB\Exception\WrongExpectedVersionException; use KurrentDB\Exception\StreamDeletedException; try { $eventStore->writeToStream('user-123', $event, 10); } catch (WrongExpectedVersionException $e) { // Handle version conflict echo "Version mismatch: " . $e->getMessage(); } catch (StreamNotFoundException $e) { // Stream doesn't exist echo "Stream not found: " . $e->getMessage(); } catch (StreamDeletedException $e) { // Stream was deleted echo "Stream deleted: " . $e->getMessage(); }
Custom HTTP Client
You can provide your own HTTP client implementing HttpClientInterface
:
use KurrentDB\Http\HttpClientInterface; class MyCustomHttpClient implements HttpClientInterface { public function send(RequestInterface $request): ResponseInterface { // Custom implementation } public function sendBatch(RequestInterface ...$requests): \Iterator { // Batch implementation } } $eventStore = new EventStore($url, new MyCustomHttpClient());
Development Setup
Quick Start with Make
# Start KurrentDB and build PHP container make up # Install dependencies make install # Run tests make test # Run tests with coverage make test-coverage # Check code style make cs-fixer-ci # Fix code style make cs-fixer # Run static analysis make phpstan # Run benchmarks make benchmark # View logs make logs # Stop containers make down
Testing
The project uses PHPUnit for testing:
# Run all tests make test # Run with coverage report make test-coverage # Run specific test file docker compose exec php bin/phpunit tests/Tests/EventStoreTest.php
API Reference
Main Classes
EventStore
- Main client class for all operationsWritableEvent
- Represents an event to be writtenWritableEventCollection
- Collection of events for atomic writesStreamFeed
- Paginated view of a streamEvent
- Represents a read event with version and metadata
Enums
StreamDeletion
- SOFT or HARD deletion modesEntryEmbedMode
- NONE, RICH, or BODY embed modesLinkRelation
- FIRST, LAST, NEXT, PREVIOUS, etc.
Interfaces
EventStoreInterface
- Main service interfaceHttpClientInterface
- HTTP client abstractionWritableToStream
- Objects that can be written to streams
Docker Environment
The project includes a complete Docker setup with:
- KurrentDB (latest) with projections enabled and health checks
- PHP container with all required extensions and dependencies
- Persistent volumes for KurrentDB data and logs
- Automatic service dependency management
The KurrentDB instance is configured with:
- HTTP API on port 2113
- Default credentials:
admin:changeit
- All projections enabled
- AtomPub over HTTP enabled
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Before submitting:
# Run tests make test # Check code style make cs-fixer-ci # Run static analysis make phpstan
License
This project is licensed under the MIT License - see the LICENSE file for details.
Disclaimer
This project is not endorsed by Event Store LLP nor Kurrent Inc.