citomni / provider-skeleton
Template provider for CitOmni apps: boot constants (MAP/CFG), example route/controller, service, command, and model. Ultra-lean, deterministic, PHP 8.2+.
Fund package maintenance!
LarsGMortensen
Open Collective
Ko Fi
Installs: 3
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:project
pkg:composer/citomni/provider-skeleton
Requires
- php: ^8.2
- citomni/kernel: ^1.0
Suggests
- ext-opcache: Better performance in production.
- citomni/cli: CLI runtime if you wire commands into a command runner.
- citomni/http: HTTP runtime (Router, Request, Response, View) if you use the provided routes/controllers.
README
Minimal, deterministic provider template for CitOmni (PHP 8.2+). Contributes config and services via boot constants - with optional routes, controllers, commands, and models. No magic. No surprises.
♻️ Green by design - fewer CPU cycles, lower memory, faster deploys. More requests per watt.
Scope: Provider/package skeleton (mode-neutral). Use with
citomni/httpif you expose HTTP routes; usecitomni/cliif you wire commands into a runner. For application skeletons, seecitomni/http-skeleton,citomni/cli-skeleton, orcitomni/app-skeleton.
Further reading
- Runtime / Execution Mode Layer — why CitOmni has exactly two modes and how deterministic merging works:
https://github.com/citomni/docs/blob/main/concepts/runtime-modes.md - Provider Packages: Design, Semantics, and Best Practices — MAP_/CFG_, routes, precedence, testing:
https://github.com/citomni/docs/blob/main/concepts/services-and-providers.md
Requirements
- PHP 8.2+
- Composer
citomni/kernel(required)- Optional:
citomni/http(if you use routes/controllers),citomni/cli(if you expose commands)
Install (scaffold a new provider repo)
This repository is a template (Composer project). Create a new provider from it:
composer create-project citomni/provider-skeleton my-provider
cd my-provider
From here you'll rename things (package name, namespace, classes) and turn it into your own provider library.
What's included (in this skeleton)
provider-skeleton/
├─ src/
│ ├─ Boot/
│ │ ├─ Services.php # MAP_*/CFG_* boot constants
│ │ └─ Routes.php # Example /hello route map (optional)
│ ├─ Controller/
│ │ └─ HelloController.php # Extends Kernel BaseController
│ ├─ Command/
│ │ └─ HelloCommand.php # Extends Kernel BaseCommand
│ ├─ Service/
│ │ └─ GreetingService.php # Extends Kernel BaseService
│ └─ Model/
│ └─ DemoModel.php # Extends Kernel BaseModel
├─ tests/
│ └─ SkeletonSmokeTest.php # Tiny smoke-test stub (optional)
├─ stubs/
│ └─ provider.composer.json.stub # READY-TO-PUBLISH composer.json (library)
├─ composer.json # TYPE: project (template for create-project)
├─ README.md
├─ LICENSE
└─ .gitignore
PSR-4: "CitOmni\\ProviderSkeleton\\": "src/"
SPDX header: All PHP files should carry the standard CitOmni header.
Deterministic configuration (last wins)
Per execution mode (HTTP|CLI), config merges in this order:
- Vendor baseline (by mode)
- Providers (classes listed in your app's
/config/providers.php; order matters) - App base (
/config/citomni_{http|cli}_cfg.php) - Env overlay (
/config/citomni_{http|cli}_cfg.{dev|stage|prod}.php)
Merged config is exposed as a deep, read-only wrapper; large lists (e.g., routes) remain raw arrays for performance:
$baseUrl = $this->app->cfg->http->base_url; $routes = $this->app->cfg->routes; // raw array by design
Services use the same precedence (app overrides provider overrides vendor).
See also: https://github.com/citomni/docs/blob/main/concepts/runtime-modes.md
Quick start checklist (after scaffold)
-
Switch to your identity
-
Open
/stubs/provider.composer.json.stub. -
Replace placeholders:
your-vendor/your-providerYourVendor\\YourProvider\\- URLs and author fields
-
Save it as
/composer.json(overwriting the skeleton's), and ensure"type": "library".
-
-
Rename the namespace in code
- Update PSR-4 in
/composer.jsonto"YourVendor\\YourProvider\\": "src/" - Search/replace namespaces in
src/(andtests/if used):
GNU sed (bash):
find src tests -type f -name '*.php' -print0 \ | xargs -0 sed -i 's/CitOmni\\ProviderSkeleton\\/YourVendor\\YourProvider\\/g'
PowerShell:
gci -rec src,tests -filter *.php | % { (Get-Content $_.FullName) -replace 'CitOmni\\ProviderSkeleton\\','YourVendor\\YourProvider\\' | Set-Content $_.FullName }
- Update PSR-4 in
-
Rename the boot provider class
- Keep the file path:
src/Boot/Services.php - Change the class namespace to
YourVendor\YourProvider\Boot\Services
- Keep the file path:
-
Decide what to keep
- The examples (
GreetingService,HelloController,HelloCommand,DemoModel) are there to smoke-test your wiring. Keep or prune freely.
- The examples (
-
Validate & autoload
composer validate composer dump-autoload -o
-
Publish
- Push to your Git hosting (e.g., GitHub)
- Submit to Packagist (or enable auto-submit via webhook)
Provider contract (boot constants)
Your provider contributes config and services through class constants. Only present constants are read.
<?php declare(strict_types=1); namespace CitOmni\ProviderSkeleton\Boot; final class Services { public const MAP_HTTP = [ 'greeting' => [ 'class' => \CitOmni\ProviderSkeleton\Service\GreetingService::class, 'options' => ['prefix' => 'Hello'], ], ]; public const CFG_HTTP = [ 'provider_skeleton' => [ 'enabled' => true, 'greeting' => ['prefix' => 'Hello'], ], 'routes' => \CitOmni\ProviderSkeleton\Boot\Routes::MAP, ]; public const MAP_CLI = [ 'hello' => \CitOmni\ProviderSkeleton\Command\HelloCommand::class, ]; public const CFG_CLI = [ 'provider_skeleton' => ['enabled' => true], ]; }
- Service definitions: either FQCN or
['class' => FQCN, 'options' => [...]] - Ctor contract:
__construct(App $app, array $options = []) - Routes live in config and remain a raw array (performance)
More details: https://github.com/citomni/docs/blob/main/concepts/services-and-providers.md
Example route & controller (optional)
<?php declare(strict_types=1); namespace CitOmni\ProviderSkeleton\Boot; final class Routes { public const MAP = [ '/hello' => [ 'controller' => \CitOmni\ProviderSkeleton\Controller\HelloController::class, 'methods' => ['GET'], 'options' => ['who' => 'world'], ], ]; }
<?php declare(strict_types=1); namespace CitOmni\ProviderSkeleton\Controller; use CitOmni\Kernel\Controller\BaseController; /** * HelloController - minimal demo controller. * * Behavior: * - Inherits $this->app and $this->routeConfig from BaseController. * - BaseController automatically calls ->init() after construction. */ final class HelloController extends BaseController { /** Lightweight, one-time setup (optional). */ protected function init(): void { // no-op; keep it lean } /** GET /hello - emits tiny HTML. */ public function index(): void { $who = (string)($this->routeConfig['options']['who'] ?? 'world'); $msg = $this->app->greeting->make($who); echo "<!doctype html><meta charset=\"utf-8\"><title>Hello</title>"; echo "<p>{$msg}</p>"; } }
Example service
<?php declare(strict_types=1); namespace CitOmni\ProviderSkeleton\Service; use CitOmni\Kernel\Service\BaseService; /** * GreetingService - tiny example with a configurable prefix. * * Typical usage: * $this->app->greeting->make('Alice'); // "Hello, Alice" */ final class GreetingService extends BaseService { /** Lightweight, one-time setup (optional). */ protected function init(): void { // reserved for future tweaks (e.g., precompute prefix) } public function make(string $name): string { $cfgPrefix = $this->app->cfg->toArray()['provider_skeleton']['greeting']['prefix'] ?? null; $prefix = \is_string($cfgPrefix) && $cfgPrefix !== '' ? $cfgPrefix : ($this->options['prefix'] ?? 'Hello'); return $prefix . ', ' . $name; } }
Example command (optional)
This skeleton assumes the kernel offers a minimal \CitOmni\Kernel\Command\BaseCommand (constructor + init() + abstract run()), and your CLI runner calls $app->hello->run($argv).
<?php declare(strict_types=1); namespace CitOmni\ProviderSkeleton\Command; use CitOmni\Kernel\Command\BaseCommand; /** * HelloCommand - example command implementing BaseCommand contract. */ final class HelloCommand extends BaseCommand { protected function init(): void { // validate/normalize $this->options if needed } public function run(array $argv = []): int { $name = $argv[0] ?? ($this->options['default_name'] ?? 'world'); $line = $this->app->greeting->make($name); \fwrite(\STDOUT, $line . \PHP_EOL); return 0; } }
Example model (optional)
<?php declare(strict_types=1); namespace CitOmni\ProviderSkeleton\Model; use CitOmni\Kernel\Model\BaseModel; /** * DemoModel - optional skeleton demonstrating BaseModel constructor pattern. */ final class DemoModel extends BaseModel { protected function init(): void { // precompute cheap derived state if needed } /** @return array<int,string> */ public function listSamples(): array { return ['alpha', 'beta', 'gamma']; } }
SPDX header template (use in every PHP file)
<?php declare(strict_types=1); /* * SPDX-License-Identifier: MIT * Copyright (c) [START_YEAR]-[CURRENT_YEAR] [YOUR NAME] * * [PACKAGE TITLE] — [One-line description] * Source: https://github.com/[your-vendor]/[your-repo] * License: See the LICENSE file distributed with this source code. */
From skeleton -> finished provider (library)
-
Replace composer metadata Use
/stubs/provider.composer.json.stubas your base: copy to/composer.json, set"type": "library", and fill in identity/URLs/authors. -
Change namespace Update PSR-4 and classes to
YourVendor\YourProvider\...(see the sed/PowerShell snippets above). -
Rename boot class Keep
src/Boot/Services.php, but set the FQCN toYourVendor\YourProvider\Boot\Services. -
Validate & autoload
composer validate composer dump-autoload -o
-
Publish
- Push to GitHub
- Submit to Packagist (or enable auto-hook)
Using your finished provider in an app
- Install in the app:
composer require your-vendor/your-provider
- Enable the provider (whitelist):
<?php // app/config/providers.php return [ \YourVendor\YourProvider\Boot\Services::class, ];
- (Optional) Override defaults:
<?php // app/config/citomni_http_cfg.php return [ 'your_provider' => [ 'enabled' => true, 'greeting' => ['prefix' => 'Hej'], ], ];
Performance & caching (production)
-
Use compiled caches:
<appRoot>/var/cache/cfg.{http|cli}.php<appRoot>/var/cache/services.{http|cli}.php
-
Generate atomically during deploy:
$app = new \CitOmni\Kernel\App(__DIR__ . '/../config', \CitOmni\Kernel\Mode::HTTP); $app->warmCache(true, true);
-
Enable OPcache; consider
validate_timestamps=0(then invalidate on deploy).
Testing
This skeleton ships with a tiny smoke test stub (optional).
For integrated, framework-native testing (dev-only), consider citomni/testing.
Coding & documentation conventions
- PHP 8.2+, PSR-1/PSR-4
- PascalCase classes, camelCase methods/vars, UPPER_SNAKE_CASE constants
- K&R braces, tabs for indentation
- PHPDoc & inline comments in English
- Fail fast; do not catch unless necessary (global handler logs)
Coding & Documentation Conventions
All CitOmni projects follow the shared conventions documented here: CitOmni Coding & Documentation Conventions
License
CitOmni Provider Skeleton is released under the MIT License.
See LICENSE for full license terms.
For trademark usage and attribution requirements, see NOTICE.
FAQ
Q1: Can I use the name "CitOmni" in my own provider package?
You may make factual references such as “Built for CitOmni” or “Compatible with CitOmni,”
provided that your use is purely descriptive and does not imply official status, endorsement,
or affiliation. You may not:
- use “CitOmni” (or a confusingly similar name) as part of your company, domain, or top-level package name;
- modify or combine the CitOmni logo with your own branding;
- present your work as “official,” “certified,” or “approved” unless such status has been formally granted.
See the NOTICE for full trademark policy.
Q2: What does the MIT License allow me to do with this skeleton?
Everything the MIT License normally allows — copy, modify, redistribute, and include
in your own projects (open or proprietary). You just need to:
- keep the copyright and license notice in redistributed copies, and
- include your own license for your resulting work.
Q3: If I build a proprietary or commercial provider from this skeleton, am I compliant?
Yes. The skeleton is intentionally MIT-licensed so you can:
- create your own provider package (open-source or closed-source),
- license it under your chosen terms,
- and distribute it via Packagist, private repositories, or direct delivery to clients.
You do not need to open-source your derived provider.
However, you must not remove or misrepresent the CitOmni copyright
and trademark notices that remain in any retained portions of this template.
Q4: Do I need to credit CitOmni in my README or composer.json?
Attribution is appreciated but not legally required.
If you want to mention it, a simple line such as:
Built using the CitOmni Provider Skeleton (MIT)
is perfect.
Q5: Where can I find official CitOmni documentation and licensing guidance?
- Docs hub: https://github.com/citomni/docs
- Provider best practices: https://github.com/citomni/docs/blob/main/concepts/services-and-providers.md
- Runtime & merge model: https://github.com/citomni/docs/blob/main/concepts/runtime-modes.md
- Trademark policy: NOTICE
Trademarks
"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 the project's NOTICE.
Author
Developed by Lars Grove Mortensen © 2012-present Contributions and pull requests are welcome.
Built with ❤️ on the CitOmni philosophy: low overhead, high performance, ready for anything.