cordis-php/cordis

A strictly typed, YAML-composed plugin runtime for PHP 8.2+.

Maintainers

Package info

github.com/cognesy/cordis-php

pkg:composer/cordis-php/cordis

Transparency log

Statistics

Installs: 182

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-08-26 19:06 UTC

This package is auto-updated.

Last update: 2026-08-26 22:14:25 UTC


README

Cordis PHP is a strictly typed PHP 8.2+ runtime for building applications from reversible plugins and YAML composition. It takes the durable parts of Cordis:

  • plugin instances own a scope and clean up in reverse order;
  • services can appear, disappear, and be replaced while consumers restart;
  • a loader reconciles a YAML entry tree instead of treating configuration as a one-time bootstrap script;
  • patches compose a base configuration with deployment or user layers; and
  • expressions are structured data, never executable PHP.

It is deliberately synchronous. PHP processes usually own the event loop at a higher layer, so a plugin's lifecycle is deterministic and can be embedded in CLI, worker, web, or framework hosts without imposing an async runtime.

Install

composer require cordis-php/cordis

Minimal composition

<?php

declare(strict_types=1);

use CordisPhp\Plugin\PluginRegistry;
use CordisPhp\Runtime\Runtime;

$plugins = new PluginRegistry();
$plugins->registerClass('clock', App\ClockPlugin::class);

$runtime = new Runtime($plugins);
$loader = $runtime->yaml(__DIR__ . '/app.yml');
$loader->reload();
# app.yml
- id: clock
  name: clock
  config:
    timezone: UTC

Plugins implement CordisPhp\Contract\Plugin. They receive a child Context, can provide services, subscribe to events, and return an optional cleanup closure. Every registration is automatically tied to the plugin's scope.

YAML grammar

Each entry has an id and either a plugin name or a group of child entries. config and disabled are the only expression-capable fields.

- id: app
  group:
    - id: storage
      name: storage.sqlite
      config:
        path:
          $expr:
            env: APP_DB
            default: var/app.sqlite
    - id: api
      name: api.http
      inject: [storage]

The safe expression vocabulary is small and explicit:

  • { $expr: { env: NAME, default: value } }
  • { $expr: { service: service-name } }
  • { $expr: { coalesce: [value-or-expression, ...] } }
  • { $expr: { concat: [value-or-expression, ...] } }

No YAML value is evaluated as PHP. A !expr YAML tag is also accepted and uses the same structured payload.

The runtime envelope is closed and has a CI/editor-friendly YAML Schema: YAML composition guide and schema. Plugin config remains opaque to that envelope schema and is validated by its registered plugin.

Layering and reload

YamlRuntimeLoader::reload($patches) reads and validates a complete document, applies id-targeted patches, and reconciles the live plugin tree. A patch can replace a row's fields or append entries to the root or a group:

- id: api
  config:
    port: 8080
- insert:
    - id: metrics
      name: telemetry.metrics

reloadIfChanged() is the host-friendly hot-reload primitive: a CLI watcher, RoadRunner worker, or framework development server decides when to call it. Lifecycle changes settle synchronously, and provider services are unpublished before provider cleanup runs so dependents stop first. Long-lived hosts must still call reload at a quiescent safe point: Cordis cannot invalidate a service reference already held by application code that is still executing.

Quality gates

composer install
composer qa
composer examples
ys -f resources/schema/composition.schema.yaml examples/03-yaml-live-reload/composition.initial.yaml

The Pest suite covers lifetimes, service replacement, dependency pending and restart, event modes, YAML validation, expression safety, patches, groups, health snapshots, isolation, interception, and live reconciliation. Pull requests run the suite on PHP 8.2 through 8.5, with Symfony YAML 7 and Symfony YAML 8 on the PHP versions supported by each line.

Releases

The package is published through Packagist from the public GitHub repository. Release versions are owned by ops/version/current.yaml, and v<version> tags drive the checked GitHub Release workflow. Contributors preparing a release can inspect the complete procedure with:

just ops version check
just ops release gate

Packagist derives Composer versions from the VCS tags, so the Composer manifest intentionally does not duplicate the release version.

Repository operations

The repository carries a versioned, self-describing operations catalogue under ops/. It keeps local developer and agent workflows explicit without replacing Composer's PHP tooling:

just                         # discover the local command surface
just validate                # verify the versioned operations catalogue
just check                   # run its derived fast quality lane
just ci                      # run the CI-shaped local workflow
just ops release status      # reach any capability directly

Each capability declares its command surface, required tools, path ownership, and any agent skill. The control validator checks those contracts, and the repository-wide check and test lanes are derived from the manifests rather than maintained as a second hand-written list.

Operator and agent skills

The solution-facing skill catalogue maps real Cordis use cases to focused procedures for design, agent harnesses, plugin development, YAML configuration, refactoring, operations, troubleshooting, and verification. They are repository-local durable instructions for operators and coding agents; they complement the repository-maintenance skills stored inside ops/.

Runnable examples

The examples directory contains nine self-checking CLI programs. They show scoped cleanup, dynamic dependency restart, YAML reloads, event pipelines, runtime observability, service isolation, configuration validation, deployment-owned service swaps, and local service interception using only this library's public API.

Run all of them with:

composer examples