Skip to content

FriendsOfOuro/kurrentdb-php-core

Repository files navigation

KurrentDB PHP Client

PHP 8 Latest Stable Version License

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 [email protected]: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:[email protected]:2113');

// Or with custom HTTP client
use KurrentDB\Http\GuzzleHttpClient;

$httpClient = new GuzzleHttpClient();
$eventStore = EventStoreFactory::createWithHttpClient(
    'http://admin:[email protected]:2113',
    $httpClient
);

Writing Events

use KurrentDB\WritableEvent;
use KurrentDB\WritableEventCollection;

// Write a single event
$event = WritableEvent::newInstance(
    'UserRegistered',
    ['userId' => '123', 'email' => '[email protected]'],
    ['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 operations
  • EventStoreFactory - Factory for creating EventStore instances with proper dependencies
  • WritableEvent - Represents an event to be written
  • WritableEventCollection - Collection of events for atomic writes
  • StreamFeed - Paginated view of a stream
  • Event - Represents a read event with version and metadata

Service Classes

  • StreamReader - Handles stream reading operations
  • StreamWriter - Manages stream writing and deletion
  • StreamIteratorFactory - Creates stream iterators for navigation

Enums

  • StreamDeletion - SOFT or HARD deletion modes
  • EntryEmbedMode - NONE, RICH, or BODY embed modes
  • LinkRelation - FIRST, LAST, NEXT, PREVIOUS, etc.

Interfaces

  • EventStoreInterface - Main service interface
  • HttpClientInterface - HTTP client abstraction
  • WritableToStream - 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.

Support

About

PHP Client for KurrentDB HTTP API

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 15

Languages