Search by

omniscient / omnibridge

GlitchArt

The core of the omnibridge family: the contracts any host answers for an application that runs on more than one framework (addresses, translations, languages, access, sessions, tokens, stored files, routes, environment) and the registry the application reaches them through. No framework, no ORM: eac

Package info

github.com/glitchr-studio/omnibridge

pkg:composer/omniscient/omnibridge

Statistics

Installs: 85

Dependents: 4

Suggesters: 1

Stars: 0

Open Issues: 0

1.x-dev 2026-10-21 13:25 UTC

This package is auto-updated.

Last update: 2026-10-21 13:25:35 UTC


README

The core of the omnibridge family: the contracts any host answers for an application that runs on more than one framework, and the registry the application reaches them through. No framework, no ORM, no template engine: the package requires PHP and three PSR interfaces (container, clock, event dispatcher), and its BareTest proves that every class loads with nothing else.

composer require omniscient/omnibridge:1.x-dev

The bridges, omnibridge/<host>, answer these contracts with their host's services and fill the registry at boot: omnibridge/laravel (Laravel), omnibridge/twig (the contract a Twig page asks of its host), omnibridge/doctrine, omnibridge/react; under Symfony, omniscient/omnibase's bundle for now (omnibridge/symfony next). A bridge is only the adapter of the contracts for one technique: it chooses no versions, and an application that speaks only to the contracts changes host by changing bridge and configuration.

use Omnibridge\Runtime;

Runtime::urls()->path('article', ['slug' => 'le-fuji']);   // the host's router
Runtime::translations()->trans('menu.home');                // the host's catalogues
Runtime::access()->isGranted('ROLE_ADMIN');                 // the host's security
Runtime::clock()->now();                                    // the host's clock, else the machine's

The contracts

Omnibridge\Contract\*, each generic to any host:

Contract What it answers Why it is generic
Urls a route's path and whole URL by its name, a public file's address every host has named routes and serves public files
Translations a message from the catalogues (ICU), whether a key exists every host has message catalogues and can tell whether one holds a key
Locales the current, default and available locales (fr-FR) every host has a current and a default locale
Access whether the visitor holds an attribute, on a subject; the user Symfony's authorization checker, Laravel's gate: the same two questions
Sessions a user signed out when it is the one signed in every host with sign-in has a sign-out
Tokens a form's token for an intent (CSRF), and whether a token sent back is valid every host protects its forms; the token is checked by the host that made it
Files a stored file's public path and URL, its local path, whether its storage is remote storages named by the application's configuration, mapped by each bridge to its host's (Flysystem, disks)
Routes a route's declared path, the route of an address, its icons every router knows a route's pattern and matches an address; icons are metadata, null where the host keeps none
Environment the environment's name, debug, the secret every host runs in an environment with a secret

Also here: PSR-20's clock (Runtime::clock(), SystemClock when the host gave none) and PSR-14's event dispatcher (Runtime::events()), which need no contract of their own.

The registry

Omnibridge\Runtime holds one service per contract, asked by the interface's name. A host fills it once, at boot: with a PSR-11 container whose ids are those names (setContainer(): Laravel's application, a Symfony service locator), or one service at a time (set(), which wins). A contract asked and not filled throws Omnibridge\Exception\MissingContractException (never a silent null), but the clock; find() answers null for a caller that can do without. A different container forgets what the previous one gave; what was set by hand stays.

A contract is the same whatever name it is asked by. A family keeps a former name with Runtime::alias($contract, $former) (a class_alias, and the registry told), declared when Composer's autoloader is loaded: a service set or bound under either name is the same object, and the container is asked by the own name, then by the name asked, then by the former names - a host that still binds only a former name is found by the new one. formerNames() lists them; a missing contract's exception names them.

The registry is not final: an application family adds its own contracts' accessors in a class of its own over the same state, as omniscient/omnibase's Base\Contract\Runtime does.

The data structure: Omnibridge\Schema

The structure of an application's data - tables, columns and their types, indexes and unique constraints, relations and their join columns, hierarchies and their discriminators, the names the naming gives - described once, outside any ORM (Marco's bridge principle, 2026-10-19: several persistence techniques, the same tables), so that Doctrine reads it today and another persistence (Eloquent or other) maps the same tables from it tomorrow.

  • Schema\Declaration\*: what the entity classes declare, read from their attributes by reflection and without instantiating them - the attributes are Doctrine\ORM\Mapping's, the vocabulary omnibase's entities are written in, but their classes need not exist: Vocabulary holds each attribute's parameters and defaults as data (omnibridge/doctrine's VocabularyTest keeps it equal to doctrine/orm's constructors). AttributeReader reads a class into a ClassDeclaration (its attributes, its properties with the class that declares each, its lifecycle methods); DeclarationSet keeps declarations as data (a JSON file, toArray()/fromArray()). A persistence bridge maps the classes from a Declarations source and never reads the attributes itself.
  • Schema\Description: the neutral description - Entity (table, kind, parent and root, Inheritance, identifier, Fields, Relations with their JoinColumns or JoinTable), and the Tables they make (Columns with their portable type, primary key, Indexes, ForeignKeys). Plain data too (toJson()/fromJson()).
  • Schema\Builder: builds the description from the declarations alone, the way Doctrine completes its metadata (a parent's fields inherited, a type guessed from the property's, default join columns and join tables, default discriminator maps) and the way its SchemaTool makes the tables (one table per joined class, one for a single-table hierarchy, a join table per owning many-to-many, the implicit indexes behind foreign keys, DBAL's generated names). Builder\Rule is an application's own convention, applied at the three moments an ORM gives its listeners (before the declaration is read, once read, once the class is whole): omnibase's translations, discriminator entries, vaults and order columns are rules in omniscient/omnibase.
  • Schema\Naming / CamelCaseNaming: how the unnamed is named (omnibase's camel case: App\Entity\Thread\Tag is threadTag, Thread::$tags the join table thread_tags (thread_id, threadTag_id)), and a generated index or foreign key name as DBAL generates it. omnibridge/doctrine's NamingStrategy answers Doctrine with it.
  • Schema\Database\MySqlSchema: a MySQL schema read from information_schema (PDO, read-only) and compared with a description, column by column - the independent proof that the description is the database.
use Omnibridge\Schema\Builder;

$description = (new Builder(rules: $applicationRules))->build($entityClasses);
$description->table('thread')->column('slug');          // string(255), not null
$description->entity(Thread::class)->relation('tags');   // many_to_many, join table thread_tags
file_put_contents('schema.json', $description->toJson());

omnibridge/doctrine reads the same declarations into Doctrine's metadata (DeclarationDriver) and writes an entity manager's metadata back in this form (MetadataDescription): the two descriptions are compared in its tests and on each site (omnibase's harness, lesringostars), and the site's with its database's information_schema.

What stays with omniscient/omnibase

The contracts that describe omnibase's own models, not a host, stay Base\Contract\* in omniscient/omnibase:

Contract Why it is omnibase's
Nouns what an omnibase entity is called, by omnibase's rule over its "entities" catalogues (namespaces, politeness, gender)
Media omnibase's image route (ux_image) and its obfuscated data
Pictures omnibase's |picture filter: a stored image at a size, by omnibase's thumbnails
Texts omnibase's stored texts (Editor.js, Quill) and their |wysiwyg rendering
Base\Screen\ContextProvider the frame of omnibase's screens (site, links, viewer, theme)
SecurityUser, TwoFactorUser, Framework what omnibase's User declares to a framework's security, and the rule that decides it
Link a helper over Urls with Symfony's reference types, for omnibase's entities' __toLink()
Runtime::parameters(), ::mailer() omnibase's parameter bag (database settings over the configuration), symfony/mailer's interface

Former names

Until omnibase 4's final pass, each moved class stays reachable under its former name by Runtime::alias() (a class_alias: an alias, not a subclass - one interface, one reflection name), declared by the package that owned the name, eagerly (Composer's files):

Here Former names
Omnibridge\Contract\Urls Base\Contract\Urls, Omnibridge\Twig\Contract\Urls
Omnibridge\Contract\Translations Base\Contract\Translations, Omnibridge\Twig\Contract\Translations (which asked trans() alone)
Omnibridge\Contract\{Locales, Access, Sessions, Tokens, Files, Routes, Environment} Base\Contract\{...}
Omnibridge\SystemClock Base\Contract\SystemClock
Omnibridge\Exception\MissingContractException Base\Exception\MissingContractException

Base\Contract\Runtime is not an alias: it is omnibase's registry over this one, with omnibase's own accessors.

Tests

docker compose run --rm test

PHPUnit on PHP 8.2, the lowest this package declares: the registry (a contract missing, set, resolved once from a container, forgotten with the container, the clock, each accessor, a former name, a family's registry over the same state), the schema (the declarations read without doctrine/orm installed, the description of a joined and a single-table hierarchy, relations, rules, the tables as Doctrine's SchemaTool makes them, the naming, a MySQL schema compared) and BareTest (every class of src/ loaded in a PHP process of its own, nothing loaded but the PSR interfaces). An application that runs vendor/*/tests (lesringostars does) runs them too: the registry's cases hold there, and BareTest, which needs this package's own vendor, says so and is skipped.

Licence

MIT.