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

Maintainers

Package info

github.com/jardisCore/kernel

Homepage

Documentation

pkg:composer/jardiscore/kernel

Transparency log

Statistics

Installs: 211

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 1

v2.0.1 2026-07-17 05:43 UTC

README

Build Status Latest Version License: MIT PHP Version PHPStan Level PSR-12 Coverage PSR-11 PSR-14 PSR-16 PSR-18

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.

  • DomainKernelInterface in, done. Every generated {Domain}Context and 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. BuildDomainKernelFromEnv packs a Koffer from cascading .env files if you want that; build your own DomainKernel directly 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 ContextResponseDomainResponse 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 ContextResponseDomainResponseTransformerDomainResponse 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: only jardissupport/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 see DomainKernelInterface.

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:

docs.jardis.io/en/core/kernel

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