jardiscore / kernel
DDD Kernel for PHP — BoundedContext, ContextResponse, DomainResponse, DomainKernel
Requires
- php: >=8.2
- ext-pdo: *
- jardissupport/classversion: ^1.0
- jardissupport/contract: ^1.0
- jardissupport/dotenv: ^1.0
- jardissupport/factory: ^1.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.0.4
- phpunit/phpunit: ^11.0
- squizlabs/php_codesniffer: ^3.11.2
Suggests
- jardisadapter/dbconnection: For ConnectionPool with read/write splitting, health checks and load balancing
This package is auto-updated.
Last update: 2026-04-01 08:55:51 UTC
README
Part of the Jardis Business Platform — Enterprise-grade PHP components for Domain-Driven Design
The DDD kernel you always wanted. Eight files. Zero magic. Full control.
Why Jardis Kernel?
Most DDD frameworks force you into their world. Jardis Kernel gives you the building blocks and stays out of the way.
new Domain()— done. No configuration, no YAML, no service files. Your domain boots itself.- Plain PDO works. Pass a PDO, get going. Need connection pooling later? Swap in a ConnectionPool. Same API.
- Services shared across domains automatically. Five domains, one database connection. First-write-wins. Zero plumbing.
- Every service is optional. Need a cache? Override one method. Don't need one? Don't touch anything.
- ClassVersion built in. Versioned classes via namespace injection — the foundation for the Jardis Builder.
- Immutable kernel. Once built, nothing changes. Safe for application servers, workers, long-running processes.
Installation
composer require jardiscore/kernel
Quickstart
1. Create your Domain
use JardisCore\Kernel\Domain; class Ecommerce extends Domain { public function order(): OrderContext { return new OrderContext($this->kernel()); } }
2. Define a Bounded Context
use JardisCore\Kernel\BoundedContext; use JardisCore\Kernel\Response\DomainResponseTransformer; class PlaceOrder extends BoundedContext { public function __invoke(): DomainResponse { $order = $this->payload(); $pdo = $this->resource()->connection(); $stmt = $pdo->prepare('INSERT INTO orders (customer, total) VALUES (?, ?)'); $stmt->execute([$order['customer'], $order['total']]); $this->result()->addData('orderId', (int) $pdo->lastInsertId()); $this->result()->addEvent(new OrderPlaced($order)); return (new DomainResponseTransformer())->transform($this->result()); } }
3. Use it
$shop = new Ecommerce(); $response = $shop->order()->placeOrder(['customer' => 'Acme', 'total' => 99.90]); $response->isSuccess(); // true $response->getData(); // ['PlaceOrder' => ['orderId' => 42]] $response->getEvents(); // ['PlaceOrder' => [OrderPlaced {...}]]
That's it. No bootstrap file. No container setup. No framework.
Provide a Database
The simplest way — a plain PDO:
class Ecommerce extends Domain { protected function connection(): ConnectionPoolInterface|PDO|false|null { return new PDO('mysql:host=localhost;dbname=shop', 'root', ''); } }
That's all you need. A PDO. Works everywhere.
Provide Services
Override protected methods to add infrastructure. Every method uses three-state logic:
| Return | Meaning |
|---|---|
| object | Use this service. Share it with other Domains (first-write-wins). |
| null | No local service. Use the shared one from another Domain if available. |
| false | Explicitly disabled. Don't use shared fallback either. |
class Ecommerce extends Domain { protected function connection(): ConnectionPoolInterface|PDO|false|null { return new PDO('mysql:host=localhost;dbname=shop', 'root', ''); } protected function logger(): LoggerInterface|false|null { return new MyLogger('/var/log/shop.log'); } protected function classVersionConfig(): ClassVersionConfig { return new ClassVersionConfig( version: ['v1' => ['v1'], 'v2' => ['v2', 'current']], fallbacks: ['v2' => ['v1']], ); } }
Multi-Domain Service Sharing
Multiple domains in one application share services automatically:
$ecommerce = new Ecommerce(); // Builds PDO, registers it shared $billing = new Billing(); // Gets the same PDO — zero config $analytics = new Analytics(); // Same. First-write-wins.
A domain that needs its own connection? Override the method. A domain that wants no connection at all? Return false.
Advanced: ConnectionPool (optional)
For application servers and read replicas, install jardisadapter/dbconnection and use ConnectionPool instead of plain PDO:
composer require jardisadapter/dbconnection
use JardisAdapter\DbConnection\ConnectionPool; use JardisAdapter\DbConnection\Factory\ConnectionFactory; class Ecommerce extends Domain { protected function connection(): ConnectionPoolInterface|PDO|false|null { $factory = new ConnectionFactory(); return new ConnectionPool( writer: $factory->mysql('primary', 'user', 'pass', 'shop'), readers: [ $factory->mysql('replica1', 'user', 'pass', 'shop'), $factory->mysql('replica2', 'user', 'pass', 'shop'), ], ); } }
ConnectionPool provides lifecycle management, health checks, round-robin load balancing, and automatic writer fallback when no readers are available. The rest of your code doesn't change.
Direct Kernel Usage
For full control without Domain, use DomainKernel directly:
use JardisCore\Kernel\DomainKernel; $kernel = new DomainKernel( domainRoot: __DIR__ . '/src', connection: new PDO('mysql:host=localhost;dbname=shop', 'root', ''), logger: $myLogger, env: ['app_env' => 'production'], ); $kernel->env('APP_ENV'); // 'production' (case-insensitive) $kernel->connection(); // PDO instance $kernel->container(); // Factory (always available)
Architecture
Domain Entry point. Lazy bootstrap. Service sharing.
├── domainRoot() Auto-detected via Reflection
├── classVersion() Built from classVersionConfig()
├── cache/logger/...() Three-state: object | null | false
├── factory() Factory + ClassVersion + DI Container
└── loadEnv() domainRoot/.env → private ENV
DomainKernel Immutable. Constructor injection only.
├── env(key) Case-insensitive. Private > $_ENV
├── container() Always Factory. Wraps external container.
└── connection() ConnectionPoolInterface | PDO | null
BoundedContext Use case handler.
├── handle(class, ...args) Smart resolution: ClassVersion → Factory → Container
├── resource() Access to DomainKernel
├── payload() Request data
└── result() Lazy ContextResponse
ContextResponse → DomainResponseTransformer → DomainResponse
Mutable accumulator Recursive aggregation Immutable answer
Related Packages
Included dependencies:
| Package | Purpose |
|---|---|
jardissupport/contract |
Interface contracts (DomainKernelInterface, etc.) |
jardissupport/classversion |
Versioned class resolution via namespace injection |
jardissupport/factory |
PSR-11 Container + class instantiation |
jardissupport/dotenv |
ENV file loading |
Optional (composer suggest):
| Package | Purpose |
|---|---|
jardisadapter/dbconnection |
ConnectionPool with read/write splitting, health checks, load balancing |
License
Jardis is source-available under the PolyForm Shield License 1.0.0. Free for virtually every purpose — including commercial use. The only restriction: don't build a competing framework.
Jardis — Development with Passion Built by Headgent Development