jardiscore / kernel
Hexagonal DDD kernel for PHP — DomainKernel (the Koffer) + an optional ENV-driven Bootstrap-Packer; the Application-side runtime Jardis-generated domain code is injected into
Package info
pkg:composer/jardiscore/kernel
Requires
- php: >=8.2
- ext-pdo: *
- jardissupport/classversion: ^1.0
- jardissupport/contract: ^2.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)
- jardis/dev-skills: ^1.0
- jardisadapter/cache: ^1.0
- jardisadapter/dbconnection: ^1.0
- jardisadapter/eventdispatcher: ^1.0
- jardisadapter/filesystem: ^1.0
- jardisadapter/http: ^1.0
- jardisadapter/logger: ^1.0
- jardisadapter/mailer: ^1.0
- phpstan/phpstan: 2.1.56
- phpunit/phpunit: 11.5.55
- squizlabs/php_codesniffer: 3.13.5
Suggests
- ext-redis: For Redis-based caching and logging via the Bootstrap-Packer
- jardisadapter/cache: For multi-layer caching (Memory, Redis, Database) via the Bootstrap-Packer
- jardisadapter/dbconnection: For ConnectionPool with read/write splitting, health checks and load balancing
- jardisadapter/eventdispatcher: For PSR-14 event dispatching via the Bootstrap-Packer
- jardisadapter/filesystem: For local and S3 filesystem abstraction via the Bootstrap-Packer
- jardisadapter/http: For PSR-18 HTTP client with handler pipeline via the Bootstrap-Packer
- jardisadapter/logger: For log handlers (File, Console, Redis, Database, etc.) via the Bootstrap-Packer
- jardisadapter/mailer: For SMTP mailer with STARTTLS, AUTH, HTML/text, attachments via the Bootstrap-Packer
This package is auto-updated.
Last update: 2026-07-17 05:43:21 UTC
README
Part of Jardis — the Domain-Driven Design platform for PHP. You model your domain; Jardis generates the production-ready hexagonal code (DTOs, Command/Query handlers, repositories, persistence). This package is the runtime the generated code runs on.
The Application-side offer for Jardis-generated domains. One immutable
infrastructure holder (DomainKernel, the "Koffer") plus an optional
ENV-driven packer (BuildDomainKernelFromEnv) — you inject the kernel into
the generated domain facade's constructor, nothing more.
Why Jardis Kernel?
Generated Jardis domains are hexagonal all the way down: they depend only on
jardissupport/contract interfaces, never on a concrete implementation. This
package is one way to satisfy those interfaces at runtime — a minimal,
adapter-agnostic infrastructure bundle.
DomainKernelInterfacein, done. Every generated{Domain}Contextand BC facade takes the Koffer via constructor injection — nothing else to wire.- Plain PDO works. Pass a PDO, get going. Need connection pooling later?
Swap in a ConnectionPool. Same
dbConnection()accessor. - Every service is optional. Nullable by design — a Koffer without a logger is a perfectly valid Koffer; the domain checks and acts accordingly.
- ClassVersion built in. Versioned classes via namespace injection is a
Support-package concern (
jardissupport/classversion); the Koffer just carries a container that resolves it. - Immutable kernel. Once built, nothing changes. Safe for application servers, workers, long-running processes.
- ENV wiring is optional, not assumed.
BuildDomainKernelFromEnvpacks a Koffer from cascading.envfiles if you want that; build your ownDomainKerneldirectly if you don't.
Installation
composer require jardiscore/kernel
Quickstart
1. Build a Koffer
The simplest Koffer — no services at all:
use JardisCore\Kernel\DomainKernel; $kernel = new DomainKernel(domainRoot: __DIR__);
A Koffer with a database connection — plain PDO is enough:
$kernel = new DomainKernel( domainRoot: __DIR__, connection: new PDO('mysql:host=localhost;dbname=shop', 'root', ''), );
2. Hand it to the generated domain facade
Jardis-generated domains take the Koffer via constructor injection — nothing
extends DomainApp anymore (Kernel-Entkopplung, see "Constitutional Note"
below):
$ecommerce = new Ecommerce($kernel); // {Domain} facade, generated by Jardis $response = $ecommerce->order()->placeOrder(['customer' => 'Acme', 'total' => 99.90]); $response->isSuccess(); // true $response->getData(); // ['PlaceOrder' => ['orderId' => 42]] $response->getEvents(); // ['PlaceOrder' => [OrderPlaced {...}]]
The generated {Domain}Context base class (the Naht, handle()/context())
and the ContextResponse → DomainResponse response pipeline are themselves
part of what Jardis generates per domain — see the platform-implementation
skill / docs.jardis.io for the generated-code contract. This package only
provides the Koffer these generated classes consume.
3. Or: pack the Koffer from ENV
For projects that want zero manual service wiring, BuildDomainKernelFromEnv
assembles a Koffer from a cascading .env tree (templates:
docs/env-examples/):
use JardisCore\Kernel\Bootstrap\BuildDomainKernelFromEnv; $packer = new BuildDomainKernelFromEnv(); $kernel = $packer(__DIR__ . '/config'); // reads config/.env (+ cascade) $ecommerce = new Ecommerce($kernel);
BuildDomainKernelFromEnv wires up to eleven services (cache, logger, event
dispatcher + listener registry, HTTP client, DB connection, mailer,
filesystem) from DB_* / CACHE_* / LOG_* / HTTP_* / MAIL_* /
REDIS_* ENV keys — see docs/env-examples/README.md
for the full key reference. Every adapter it can use
(jardisadapter/cache, jardisadapter/dbconnection,
jardisadapter/eventdispatcher, jardisadapter/filesystem,
jardisadapter/http, jardisadapter/logger, jardisadapter/mailer) is a
composer suggest — not installed, or not configured, means that accessor
stays null on the packed Koffer. Nothing throws for a missing optional
service.
DomainKernel — the Koffer
$kernel = new DomainKernel( domainRoot: '/path/to/config', // required container: $factory, // ?ContainerInterface cache: $cache, // ?CacheInterface logger: $logger, // ?LoggerInterface eventDispatcher: $dispatcher, // ?EventDispatcherInterface eventListenerRegistry: $registry, // ?EventListenerRegistryInterface httpClient: $client, // ?ClientInterface connection: $pool, // ConnectionPoolInterface|PDO|null mailer: $mailer, // ?MailerInterface filesystem: $filesystemService, // ?FilesystemServiceInterface env: ['db_host' => 'localhost'], // array — private ENV, takes precedence over $_ENV );
| Method | Return |
|---|---|
domainRoot() |
string |
env(string $key) |
mixed — case-insensitive; private ENV > $_ENV |
container() |
Factory — always wraps the injected container |
cache() |
?CacheInterface |
logger() |
?LoggerInterface |
eventDispatcher() |
?EventDispatcherInterface |
eventListenerRegistry() |
?EventListenerRegistryInterface — paired with eventDispatcher(); same underlying provider instance (D3) |
httpClient() |
?ClientInterface |
dbConnection() |
ConnectionPoolInterface|PDO|null |
mailer() |
?MailerInterface |
filesystem() |
?FilesystemServiceInterface |
DomainKernel builds nothing and reads no ENV itself — it is a pure,
immutable consumer. All ENV/service-assembly is Bootstrap\BuildDomainKernelFromEnv's
job (or your own equivalent).
eventListenerRegistry() exists so a generated {Agg}EventRouter can
register itself on the domain facade's constructor without any Application
wiring: a fresh build carries new routers automatically. Without a registry in
the Koffer, event routing simply stays inactive — no error.
Multi-Domain Service Sharing (explicit)
There is no static registry anymore (Kernel-Entkopplung removed the
first-write-wins ServiceRegistry, G11) — sharing services across domains is
now an explicit choice, not implicit global state:
$kernel = (new BuildDomainKernelFromEnv())(__DIR__ . '/config'); $ecommerce = new Ecommerce($kernel); // same Koffer instance $billing = new Billing($kernel); // same Koffer instance -> same connection, cache, ...
A domain that needs its own services builds its own Koffer instead of sharing one:
$billingKernel = (new BuildDomainKernelFromEnv())(__DIR__ . '/config-billing'); $billing = new Billing($billingKernel);
Advanced: ConnectionPool (optional)
For application servers and read replicas, install jardisadapter/dbconnection
and pass a ConnectionPool instead of plain PDO — either directly, or let
BuildDomainKernelFromEnv build one from DB_READER{N}_HOST ENV keys (see
docs/env-examples/.env.database.example):
use JardisAdapter\DbConnection\ConnectionPool; use JardisAdapter\DbConnection\Factory\ConnectionFactory; $factory = new ConnectionFactory(); $kernel = new DomainKernel( domainRoot: __DIR__, connection: 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.
Everything downstream ($kernel->dbConnection()) doesn't change.
Architecture
BuildDomainKernelFromEnv Bootstrap-Packer (optional). ENV -> Koffer.
├── Handler\BuildConnectionFromEnv mysql | pgsql | sqlite (+ pool)
├── Handler\BuildRedisFromEnv shared fan-out (-> cache + logger)
├── Handler\ExtractPdoFromConnection feeds the cache "db" layer
├── Handler\BuildCacheFromEnv memory | apcu | redis | db
├── Handler\BuildLoggerFromEnv file | console | slack | ... (+redis)
├── Handler\BuildEventListenerProviderFromEnv shared provider (D3)
├── Handler\BuildEventDispatcherFromProvider wraps the same provider
├── Handler\BuildHttpClientFromEnv
├── Handler\BuildMailerFromEnv
└── Handler\BuildFilesystemFromEnv
DomainKernel Immutable. Constructor injection only.
├── env(key) Case-insensitive. Private > $_ENV
├── container() Always Factory. Wraps external container.
├── cache() ?CacheInterface
├── logger() ?LoggerInterface
├── eventDispatcher() ?EventDispatcherInterface
├── eventListenerRegistry() ?EventListenerRegistryInterface (paired with eventDispatcher, D3)
├── httpClient() ?ClientInterface
├── dbConnection() ConnectionPoolInterface | PDO | null
├── mailer() ?MailerInterface
└── filesystem() ?FilesystemServiceInterface
Everything downstream of the Koffer — the generated {Domain}Context Naht
(handle()/context()), resource()/payload()/version()/result(), and
the ContextResponse → DomainResponseTransformer → DomainResponse
pipeline — is generated per domain by Jardis itself (Kernel-Entkopplung: the
generated domain is JardisCore-free; it imports only
jardissupport/contract). See the platform-implementation skill for that
generated-code contract.
Constitutional Note (Kernel-Entkopplung D4)
As of this package's v2, jardiscore/kernel sits outside the hexagonal
inner rings — it is Application-layer, not Domain-layer. Concretely:
- The Koffer core (
DomainKernel+ the contract interfaces it implements) stays adapter-free: onlyjardissupport/contract+ PSR interfaces. - The
Bootstrap\sub-namespace legitimately imports concrete adapter packages (jardisadapter/*) — that is Application wiring, not Domain code, and Application code is allowed to depend on concrete infrastructure. - Generated Jardis domains never import anything under
Bootstrap\— they only ever seeDomainKernelInterface.
This mirrors the project-wide rule ("Composition over Inheritance", flat extends only inside generated code) applied one layer up: the Application composes the Koffer from adapters; the Domain composes its behaviour from the Koffer.
Related Packages
Included dependencies:
| Package | Purpose |
|---|---|
jardissupport/contract |
Interface contracts (DomainKernelInterface, EventListenerRegistryInterface, etc.) |
jardissupport/classversion |
Versioned class resolution via namespace injection |
jardissupport/factory |
PSR-11 Container + class instantiation |
jardissupport/dotenv |
Cascading .env loading — used by BuildDomainKernelFromEnv |
Optional (composer suggest, used by Bootstrap\BuildDomainKernelFromEnv):
| Package | Purpose |
|---|---|
jardisadapter/cache |
Multi-layer caching (Memory, APCu, Redis, Database) |
jardisadapter/dbconnection |
ConnectionPool with read/write splitting, health checks, load balancing |
jardisadapter/eventdispatcher |
PSR-14 event dispatching + listener registry |
jardisadapter/filesystem |
Local and S3 filesystem abstraction |
jardisadapter/http |
PSR-18 HTTP client with handler pipeline |
jardisadapter/logger |
PSR-3 logger with file/console/network/queue handlers |
jardisadapter/mailer |
SMTP mailer with STARTTLS, AUTH, HTML/text, attachments |
Documentation
Full documentation, guides, and API reference:
License
Jardis is open source under the MIT License. Free for any purpose — commercial or non-commercial.
Jardis — Development with Passion Built by Headgent Development
AI-Assisted Development
This package ships with a skill for Claude Code, Cursor, Continue, and Aider. Install it in your consuming project:
composer require --dev jardis/dev-skills
More details: https://docs.jardis.io/en/skills