dbellettini / geteventstore-core
KurrentDB client for PHP (core)
Installs: 41
Dependents: 0
Suggesters: 0
Security: 0
Stars: 68
Watchers: 12
Forks: 24
Open Issues: 0
Requires
Requires (Dev)
- ergebnis/composer-normalize: ^2.48
- friendsofouro/http-batch-guzzle: ^3.1
- friendsofphp/php-cs-fixer: ^3.87
- guzzlehttp/guzzle: ^7.10
- maglnet/composer-require-checker: ^4.16
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.3
- rector/rector: ^2.1
- symfony/var-dumper: ^7.3
Suggests
- friendsofouro/http-batch-guzzle: Default PSR-7 + Batch implementation
- friendsofouro/kurrentdb-bundle: Integrate KurrentDB with Symfony 7.3 or later
Replaces
- dbellettini/geteventstore-core: v0.20.0
- dbellettini/php-eventstore-client: v0.20.0
- friendsofouro/geteventstore-core: v0.20.0
- dev-main
- v0.20.0
- v0.19.0
- v0.18.0
- v0.17.0
- v0.16.0
- v0.15.1
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.0
- v0.9.0
- v0.8.0
- v0.7.0
- v0.6.2
- v0.6.1
- v0.6.0
- v0.5.0
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.1
- v0.3.0
- v0.2.0
- v0.2.0-rc1
- v0.2.0-alpha2
- v0.2.0-alpha
- v0.1.1
- v0.1.0
- dev-remove-guzzle-dependency
- dev-fix/method-visibility-order
- dev-feature/batch-iterator-with-page-limit
- dev-feature/add-strict-types-declaration
- dev-refactor/exception-hierarchy
- dev-fix/issue-47-writetostream-error-handling
- dev-pr-modern-php
- dev-batch-iterator
This package is auto-updated.
Last update: 2025-09-27 14:45:57 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
Architecture
The library follows a clean architecture with a facade pattern that promotes separation of concerns and follows SOLID principles:
Facade Pattern
EventStore acts as a facade that delegates operations to specialized services:
- StreamReader - Handles all stream reading operations
- StreamWriter - Manages stream writing and deletion operations
- StreamIteratorFactory - Creates stream iterators for navigation
Factory Pattern
EventStoreFactory provides the recommended way to instantiate EventStore with proper dependency injection and connection validation:
// Simple creation with default dependencies $eventStore = EventStoreFactory::create($uri); // With custom HTTP client $eventStore = EventStoreFactory::createWithHttpClient($uri, $httpClient); // With all custom dependencies $eventStore = EventStoreFactory::createWithDependencies( $uri, $httpClient, $streamReader, $streamWriter, $streamIteratorFactory );
Benefits
- Testability - Each service can be mocked independently
- Separation of Concerns - Clear boundaries between reading, writing, and iteration
- SOLID Principles - Interface segregation and dependency inversion
- Maintainability - Easier to extend and modify individual components
- Type Safety - Strong typing throughout the service layer
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\EventStoreFactory; // Create EventStore using factory (recommended) $eventStore = EventStoreFactory::create('http://admin:changeit@127.0.0.1:2113'); // Or with custom HTTP client use KurrentDB\Http\GuzzleHttpClient; $httpClient = new GuzzleHttpClient(); $eventStore = EventStoreFactory::createWithHttpClient( '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'); $eventStore = EventStoreFactory::createWithHttpClient($url, $httpClient); // APCu cache (in-memory) $httpClient = GuzzleHttpClient::withApcuCache(); $eventStore = EventStoreFactory::createWithHttpClient($url, $httpClient); // Custom PSR-6 cache use Symfony\Component\Cache\Adapter\RedisAdapter; $cacheAdapter = new RedisAdapter($redisClient); $httpClient = GuzzleHttpClient::withPsr6Cache($cacheAdapter); $eventStore = EventStoreFactory::createWithHttpClient($url, $httpClient);
Custom Service Dependencies
For advanced use cases, you can provide custom implementations of the core services:
use KurrentDB\EventStoreFactory; use KurrentDB\Service\StreamReaderInterface; use KurrentDB\Service\StreamWriterInterface; use KurrentDB\Service\StreamIteratorFactoryInterface; // Create custom service implementations $customStreamReader = new MyCustomStreamReader($httpClient); $customStreamWriter = new MyCustomStreamWriter($httpClient); $customIteratorFactory = new MyCustomIteratorFactory($streamReader); // Create EventStore with custom dependencies $eventStore = EventStoreFactory::createWithDependencies( $uri, $httpClient, $customStreamReader, $customStreamWriter, $customIteratorFactory );
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\StreamGoneException; 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 (StreamGoneException $e) { // Stream was permanently deleted (hard delete) echo "Stream gone: " . $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 = EventStoreFactory::createWithHttpClient($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 facade class for all operationsEventStoreFactory
- Factory for creating EventStore instances with proper dependenciesWritableEvent
- 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
Service Classes
StreamReader
- Handles stream reading operationsStreamWriter
- Manages stream writing and deletionStreamIteratorFactory
- Creates stream iterators for navigation
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 # Check source dependencies make check-src-deps
Dependency Validation
The project includes dependency validation using composer-require-checker
to ensure all used dependencies are properly declared in composer.json
:
# Check for missing dependencies in source code
make check-src-deps
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.