Ultra-lean application kernel for CitOmni-based applications.
citomni/kernel is the smallest common runtime layer in the CitOmni ecosystem. It provides the structural primitives shared by CitOmni-based applications and packages, while deliberately avoiding transport runtimes, persistence logic, and framework-level magic.
The package exists to keep application boot deterministic, explicit, cheap to execute, and frugal with resources. In CitOmni terms, that means performance is not treated as a luxury feature bolted on later, but as part of the architectural contract from day one.
Small runtime surface. Predictable behavior. Less waste. Green by design.
The kernel provides:
Appas the central application objectCfgas a strict, deep, read-only configuration wrapperArrfor deterministic array normalization and merge helpersModefor execution-mode selection- thin abstract base classes for:
BaseControllerBaseCommandBaseOperationBaseRepositoryBaseService
In practice, the kernel gives the rest of CitOmni a shared structural contract without trying to become a transport runtime on its own.
The kernel does not provide:
- HTTP routing runtime
- request / response handling
- session handling
- cookies
- CSRF protection
- template rendering
- CLI command dispatch
- error handling / exception rendering
- mail, logging, DB access, or other infrastructure services
Those concerns belong in mode packages such as citomni/http and citomni/cli, or in provider/application code.
CitOmni kernel is built around four priorities:
-
Determinism
- Boot order must be explicit and reviewable.
- Merge rules must be stable.
- No hidden registration or discovery.
-
Low overhead
- Minimal indirection.
- Minimal runtime work.
- No namespace scanning or reflection-driven service discovery.
- Less framework work per request/process.
-
High performance
- Predictable data shapes.
- Fail-fast behavior.
- Cheap runtime primitives.
- Lower overhead means lower CPU time, lower memory churn, and less wasted work.
-
Maintainable structure
- Transport concerns stay outside the kernel.
- Persistence concerns stay outside the kernel.
- Shared contracts stay small and explicit.
CitOmni's performance philosophy is practical rather than theatrical: do less, allocate less, surprise less. That is good for latency, good for hosting costs, and, at scale, simply better engineering hygiene.
Install through Composer:
composer require citomni/kernelIn real applications, citomni/kernel is normally used together with one or both mode packages:
citomni/httpcitomni/cli
Current package structure:
citomni/kernel/
├── .gitignore
├── composer.json
├── CONVENTIONS.md
├── LICENSE
├── NOTICE
├── README.md
├── TRADEMARKS.md
├── src/
│ ├── App.php
│ ├── Arr.php
│ ├── Cfg.php
│ ├── Mode.php
│ ├── Command/
│ │ └── BaseCommand.php
│ ├── Controller/
│ │ └── BaseController.php
│ ├── Operation/
│ │ └── BaseOperation.php
│ ├── Repository/
│ │ └── BaseRepository.php
│ └── Service/
│ └── BaseService.php
└── tests/
└── Command/
└── BaseCommandTest.php
PSR-4 autoload root:
"CitOmni\\Kernel\\": "src/"Within CitOmni, the kernel defines the smallest shared contract used by applications and packages.
Conceptually:
- Adapters speak transport protocols.
- Operations decide what happens.
- Repositories talk to storage.
- Services provide reusable tools.
- Utils compute.
- Exceptions encode failure semantics.
The kernel itself only ships the shared primitives and thin base classes behind that structure. It does not implement transport runtimes or infrastructure services itself.
CitOmni applications define a few constants early in the entrypoint.
Typical HTTP entrypoint:
<?php
declare(strict_types=1);
define('CITOMNI_ENVIRONMENT', 'dev'); // dev | stage | prod
define('CITOMNI_PUBLIC_PATH', __DIR__);
define('CITOMNI_APP_PATH', \dirname(__DIR__));
// Optional (recommended for stage/prod):
// define('CITOMNI_PUBLIC_ROOT_URL', 'https://www.example.com');
require CITOMNI_APP_PATH . '/vendor/autoload.php';Notes:
CITOMNI_PUBLIC_PATHis HTTP-only.CITOMNI_APP_PATHpoints to the app root.CITOMNI_PUBLIC_ROOT_URLis optional in development, but recommended explicitly in stage/production.- The kernel assumes these are defined by the delivery-layer entrypoint, not by the kernel itself.
Mode selects the active execution mode.
enum Mode: string {
case HTTP = 'http';
case CLI = 'cli';
}Pass this to App so the kernel can load the correct mode-specific baselines and provider constants.
Arr contains small deterministic array helpers used by the kernel.
Recursive merge for associative arrays where the later side wins per key.
Behavior:
- associative arrays are merged recursively
- numeric arrays (lists) are replaced, not merged
- integer keys are overwritten directly by the later side
Normalizes config-like input into a plain array.
Accepted inputs:
- array
- object
Traversable
Anything else throws RuntimeException.
Cfg is a strict, deep, read-only wrapper around the merged configuration array.
Examples:
$app->cfg->http->base_url;
$app->cfg->locale->timezone;
$app->cfg->security->csrf_protection;Behavior:
- associative arrays are wrapped as nested
Cfgnodes - numeric arrays remain plain arrays
- unknown keys throw
OutOfBoundsException - writes/unsets throw
LogicException - implements
ArrayAccess,IteratorAggregate, andCountable toArray()returns the underlying raw array
Important access note:
$csrf = (bool)($this->app->cfg->security->csrf_protection ?? true);The null-coalescing operator is only safe for the final key in the chain. If intermediate nodes may not exist, guard them first with isset().
App is the central application object.
final class App {
public readonly Cfg $cfg;
public readonly array $routes;
public function __construct(string $configDir, Mode $mode);
public function __get(string $id): object;
public function getAppRoot(): string;
public function getConfigDir(): string;
public function buildConfig(?string $env = null): array;
public function warmCache(bool $overwrite = true, bool $opcacheInvalidate = true): array;
public function hasService(string $id): bool;
public function hasAnyService(string ...$ids): bool;
public function hasPackage(string $slug): bool;
public function hasNamespace(string $prefix): bool;
public function vardumpServices(): void;
public function memoryMarker(string $label, bool $asHeader = false): void;
}App is responsible for:
- building final configuration
- building final route arrays
- building the final service map
- exposing services as lazy singletons
- exposing the final config as a strict read-only object
At a practical level, App is the one shared object used throughout a CitOmni request/process.
new App($configDir, $mode) expects:
- a path that resolves to your app's
/configdirectory - a
Modeenum (Mode::HTTPorMode::CLI)
If the config directory cannot be resolved, construction fails fast.
If compiled cache files exist, the constructor prefers them:
<appRoot>/var/cache/cfg.{http|cli}.php<appRoot>/var/cache/routes.{http|cli}.php<appRoot>/var/cache/services.{http|cli}.php
If a cache file is missing or invalid, the kernel falls back to the normal build pipeline.
Services are resolved lazily from a deterministic service map.
Supported definition shapes:
'mailer' => \App\Service\Mailer::class,or:
'mailer' => [
'class' => \App\Service\Mailer::class,
'options' => [
'transport' => 'smtp',
],
],Instantiation behavior is explicit:
- string FQCN ->
new $class($this) - array definition ->
new $class($this, $options)
Unknown service ids fail fast.
There is:
- no autowiring
- no namespace scanning
- no fallback discovery
- no hidden service registration
<?php
declare(strict_types=1);
// 1) Check availability
if (!$app->hasService('router')) {
throw new RuntimeException('Router service missing.');
}
$app->router->run();
// 2) Pick the first available cache backend
$candidates = ['apcuCache', 'redisCache', 'fileCache'];
$cacheId = null;
foreach ($candidates as $id) {
if ($app->hasService($id)) {
$cacheId = $id;
break;
}
}
if ($cacheId !== null) {
$app->{$cacheId}->set('healthcheck', 'ok', ttl: 60);
}
// 3) Feature toggle by package slug
if ($app->hasPackage('citomni/auth')) {
$app->role->enforce('ADMIN');
}
// 4) Namespace presence
if ($app->hasNamespace('\CitOmni\Commerce')) {
// Optional commerce module is installed
}
// 5) Read routes directly
$homeController = $app->routes['/']['controller'] ?? null;
// 6) Lightweight timing/memory markers (dev only)
$app->memoryMarker('boot', true);
$app->memoryMarker('after-routing');CitOmni kernel follows deterministic merge rules.
For configuration and routes, merge behavior is:
- deep associative merge
- last wins
Effective order:
- vendor baseline
- providers
- app base
- environment overlay
For services, merge behavior is:
- PHP array union (
+) - left wins
Effective precedence:
- app overrides provider
- provider overrides vendor
In short:
- config/routes: last wins
- services: left wins
That distinction is intentional and part of the contract.
App::buildConfig() builds config in this order:
-
mode-package baseline config
-
provider
CFG_HTTP/CFG_CLI -
app config file:
config/citomni_http_cfg.phpconfig/citomni_cli_cfg.php
-
environment overlay:
config/citomni_http_cfg.{env}.phpconfig/citomni_cli_cfg.{env}.php
Routes are built in this order:
-
mode-package baseline routes
-
provider
ROUTES_HTTP/ROUTES_CLI -
app routes file:
config/citomni_http_routes.phpconfig/citomni_cli_routes.php
-
environment overlay:
config/citomni_http_routes.{env}.phpconfig/citomni_cli_routes.{env}.php
Services are built in this order:
-
mode-package baseline map
-
provider
MAP_HTTP/MAP_CLI -
app override file:
config/services.php
Provider packages contribute boot metadata through:
src/Boot/Registry.phpTypical constants are:
MAP_HTTPMAP_CLICFG_HTTPCFG_CLIROUTES_HTTPROUTES_CLI
All are optional.
Important rule:
- routes must not be nested inside config constants
A provider may contribute only services, only config, only routes, or any combination of the three.
The current kernel implementation reads mode-package baselines from:
\CitOmni\Http\Boot\Registry\CitOmni\Cli\Boot\Registry
Specifically, it expects mode-specific constants such as:
CFG_HTTP/CFG_CLIMAP_HTTP/MAP_CLIROUTES_HTTP/ROUTES_CLI
So while provider packages are documented via src/Boot/Registry.php, the current kernel code also expects Registry-based baseline access from the mode packages it integrates with.
The kernel can use compiled cache artifacts for configuration, routes, and services.
Cache targets:
var/cache/cfg.http.phpvar/cache/routes.http.phpvar/cache/services.http.phpvar/cache/cfg.cli.phpvar/cache/routes.cli.phpvar/cache/services.cli.php
These caches are performance tools, not correctness mechanisms.
Expected properties:
- side-effect free
- plain PHP files
- deterministic content
- safe to regenerate
warmCache(bool $overwrite = true, bool $opcacheInvalidate = true): array rebuilds config, routes, and services, then writes the three cache files atomically.
Returned shape:
[
'cfg' => '/absolute/path/to/cfg.http.php' | null,
'routes' => '/absolute/path/to/routes.http.php' | null,
'services' => '/absolute/path/to/services.http.php' | null,
]If overwrite is false and a target already exists, that entry returns null.
Typical deploy usage:
$app = new \CitOmni\Kernel\App(__DIR__ . '/../config', \CitOmni\Kernel\Mode::HTTP);
$app->warmCache(overwrite: true, opcacheInvalidate: true);
$cli = new \CitOmni\Kernel\App(__DIR__ . '/../config', \CitOmni\Kernel\Mode::CLI);
$cli->warmCache(overwrite: true, opcacheInvalidate: true);Ensure the process can write to <appRoot>/var/cache/.
Dev-only helper that dumps the current service map.
Outside dev, it throws RuntimeException.
Dev-oriented lightweight marker for memory/timing diagnostics.
Behavior:
-
outside
dev, it returns immediately -
with
$asHeader = trueand headers still open, it emits:X-CitOmni-MemMark: ...
-
otherwise it emits an HTML comment
This is intentionally cheap and practical, not a profiling framework.
The base classes in this package are intentionally thin.
They exist to standardize constructor contracts and shared access to App, not to hide behavior behind inheritance.
HTTP-facing adapter base.
Responsibilities belong to the adapter layer:
- request parsing
- CSRF/session checks
- response shaping
- view/model translation for transport output
A controller must not own SQL or non-trivial orchestration.
CLI-facing adapter base.
Responsibilities belong to the adapter layer:
- argument parsing
- terminal output
- exit codes
A command must not own SQL or cross-cutting workflow logic.
Transport-agnostic orchestration base.
An operation:
- is instantiated explicitly
- is SQL-free
- may coordinate services and repositories
- returns domain-shaped arrays
Operations are introduced when orchestration earns its existence, not by default.
Persistence base.
Repositories own:
- SQL
- datastore IO
- persistence-oriented mapping and lookup logic
Repositories do not own transport shaping or workflow orchestration.
Reusable App-aware service base.
Services are:
- registered in the service map
- resolved through
$app->{id} - typically infrastructure or cross-cutting helpers
Examples include mail, logging, text lookup, formatting, caching, and similar reusable tools.
The normal decision path in CitOmni is:
-
trivial read/write:
- Controller/Command -> Repository
-
non-trivial orchestration:
- Controller/Command -> Operation -> Repository/Services
This keeps the call graph explicit and prevents needless abstraction.
<?php
declare(strict_types=1);
use App\Operation\PublishArticle;
$operation = new PublishArticle($this->app);
$result = $operation->run($input);Operations are instantiated explicitly with new ...($this->app). They are not hidden behind container magic.
<?php
declare(strict_types=1);
if ($this->app->hasService('mailer')) {
$this->app->mailer->send($message);
}<?php
declare(strict_types=1);
$timezone = $this->app->cfg->locale->timezone ?? 'UTC';<?php
declare(strict_types=1);
$route = $this->app->routes['/dashboard'] ?? null;The kernel package itself is small, but it is designed to sit inside the wider CitOmni application structure.
Typical application-layer folders include:
src/Http/Controllersrc/Cli/Commandsrc/Operationsrc/Repositorysrc/Servicesrc/Exceptionconfiglanguagetemplatespublicbinvar
The kernel does not require every folder to exist in this package. It only supplies the shared structural primitives used by that architecture.
The kernel is designed around mechanical sympathy with ordinary PHP runtimes.
That means:
- explicit wiring over discovery
- arrays with predictable shapes
- lazy service instantiation
- fail-fast behavior
- no hidden runtime work
The goal is not fashionable abstraction. The goal is a small, predictable runtime surface with low operating cost.
CitOmni prefers architecture that earns its keep. If a layer, helper, or boot mechanism adds overhead without adding proportional value, it does not belong in the kernel. The fastest code is not always the code with the cleverest story, but very often the code that simply does less.
That is part of what "green by design" means here: fewer moving parts, fewer wasted cycles, and less accidental framework work between input and outcome.
CitOmni treats resource efficiency as an engineering concern, not a slogan.
When a framework avoids unnecessary discovery, hidden boot work, and inflated abstraction layers, the result is not only easier to reason about. It also tends to use less CPU time, less memory, and fewer machine cycles to complete the same job.
For a single request, that may look modest. Across many requests, many CLI runs, and many deployments, it adds up. Lower overhead is good for speed, good for operational cost, and good for the broader goal of building software that wastes fewer resources.
The kernel fails fast by design.
Typical failure cases include:
- missing config directory
- malformed provider registration
- invalid service definitions
- unknown service ids
- invalid cache writes
- unknown config keys
The kernel should not silently recover from structural mistakes that deserve to be fixed.
- Code style: PHP 8.2+, PSR-4, tabs, K&R braces
- Keep vendor files side-effect free
- Do not hide failures behind silent fallbacks
- Prefer deterministic structure over cleverness
All CitOmni and LiteX projects follow the shared conventions documented here: CitOmni Coding & Documentation Conventions
CitOmni Kernel is open source under the MIT License. See: LICENSE.
Trademark notice: "CitOmni" and the CitOmni logo are trademarks of Lars Grove Mortensen. Use of the name or logo must follow the policy in NOTICE. Do not imply endorsement or affiliation without prior written permission.
Can I build proprietary providers or plugins on top of CitOmni? Yes. Your apps and providers may use any license terms you choose.
Do I need to keep attribution?
Yes. Keep the copyright and license notice from LICENSE in distributions of the Software.
Can I call my product "CitOmni Something"? No. The name "CitOmni" and the logo are protected by trademark. Do not imply sponsorship, endorsement, or affiliation without permission.
"CitOmni" and the CitOmni logo are trademarks of Lars Grove Mortensen. You may make factual references to CitOmni, but do not modify the marks, create confusingly similar logos, or imply sponsorship, endorsement, or affiliation without prior written permission.
Do not register or use citomni (or confusingly similar terms) in company names, domains, social handles, or top-level vendor/package names.
For details, see NOTICE.
Developed by Lars Grove Mortensen © 2012-present. Contributions and pull requests are welcome.
CitOmni Kernel keeps the common core deliberately small, because efficient software should spend its resources on the application's work, not on the framework getting ready to do it.
Built with ❤️ on the CitOmni philosophy: low overhead, high performance, and ready for anything.