Aller au contenu

omnibridge/laravel

The Laravel host of omnisense/omnibase: its Foundation contracts (urls, translations, locales, access, parameters with the database settings laid over Laravel's config, clock, events...) answered by Laravel's services and seeded in Base\Contract\Runtime, the persistence through omnibridge/eloquent or omnibridge/doctrine, Twig through omnibridge/twig, and the pieces an omnibase page asks of its framework (Encore tags, the app variable, simple forms).

Étage
Pont
Version
1.x
Vérifié en réel
Son README ne le dit pas
Licence
MIT
composer require omnibridge/laravel:1.x-dev
composer config allow-plugins.omnibridge/composer true
composer require omnisense/omnibase:4.x-dev omnibridge/laravel:1.x-dev
php artisan vendor:publish --tag=omnibridge-config

That hosts omnisense/omnibase in a Laravel application: the provider is discovered by Laravel, binds each contract of omnibase's Foundation to Laravel's services, and hands Laravel's container to the Foundation's registry, Base\Contract\Runtime (omnisense/omnibridge's Omnibridge\Runtime with omnibase's accessors). The host's contracts are omnisense/omnibridge's (Omnibridge\Contract\*), bound under their own names (their former names, Base\Contract\Urls... and omnibridge/twig's, were removed at omnibase 4's final pass). omnibase's framework-free code - an entity's link, its noun, a translatable's language, a screen's builder - then runs under Laravel as it runs under Symfony:

use Base\Contract\Runtime;

Runtime::urls()->path('article', ['slug' => 'le-fuji']);   // Laravel's router
Runtime::translations()->trans('@messages.menu.home');      // omnibase's ICU catalogues
Runtime::parameters()->get('base.user.delay');              // Laravel's config, the database settings over it
app(Omnibridge\Contract\Urls::class) === Runtime::urls();   // one object

omnibase asks for PHP 8.4 and the extensions its own packages need (bcmath, intl, gd, exif, ftp, imagick, igbinary). omnibase stays on Doctrine for storage and Twig for its templates: this package opens Doctrine on one of Laravel's connections (omnibridge/doctrine) and Twig over the site's templates (omnibridge/twig).

What is bound

Contracts says how each adapter answers, Configuration every key that shapes them.

Contract Answered by Class
Omnibridge\Contract\Urls Laravel's router (route()), the build's asset manifest; a route Laravel does not have may be answered elsewhere RouterUrls
Omnibridge\Contract\Translations the ICU catalogues of the folders configured, read by symfony/translation, in Laravel's current locale; exists() CatalogueTranslations
Omnibridge\Contract\Locales Laravel's locale (fr) as the site's locale (fr-FR), the default first Contract\AppLocales
Base\Contract\Nouns omnibase's own rule (EntityNouns) on those catalogues -
Omnibridge\Contract\Access the gate (Gate::allows()), the default guard's user Contract\GateAccess
Omnibridge\Contract\Sessions the guard's logout() for the user signed in Contract\GuardSessions
Omnibridge\Contract\Tokens the session's CSRF token (one for every intent) Contract\SessionTokens
Omnibridge\Contract\Environment APP_ENV, app.debug, app.key Contract\AppEnvironment
Omnibridge\Contract\Routes a route's declared path, the route of an address; no icons Contract\RouterRoutes
Omnibridge\Contract\Files the disks (omnibase's storage names mapped to disks) Contract\DiskFiles
Base\Service\ParameterBagInterface Laravel's config, the settings linked to a parameter laid over it (omnibase's HotParameters) Parameters\ConfigParameters, Parameters\DatabaseSettings
Psr\Clock\ClockInterface Laravel's dates (Date::setTestNow(), travel() move it) Contract\DateClock
Psr\EventDispatcher\EventDispatcherInterface Laravel's dispatcher Contract\DispatcherEvents
Symfony\Component\Mailer\MailerInterface the default mailer's Symfony transport Contract\TransportMailer
Doctrine\DBAL\Connection, Doctrine\ORM\EntityManagerInterface the Laravel connection configured, omnibridge/doctrine's entity manager Doctrine\Connections
Twig\Environment the site's templates and namespaces, omnibridge/twig's contract (path(), url(), asset(), |trans), its AccessExtension (is_granted() on the gate, csrf_token() the session's token: the code a Symfony page asks too, omnibridge/symfony registering it there) and helpers View\Twig
Base\Contract\Pictures |picture's rule: a public file by its thumbnail at the size asked (MediaService's, omnibridge.media on) or its path, an address as it is, else the build's asset; a stored picture where the application serves it (stored()) Screen\AddressPictures
Base\Contract\Texts |wysiwyg and |wysiwyg_toc's passes (headings, links, every leaf a string, Editor.js's renderer or Quill's HTML) through omnibase's own views (@Base among the Twig paths, or on($twig)); a picture where the application serves it (images()) Screen\WysiwygTexts
Base\Screen\ContextProvider a screen's frame: the locale as a Symfony request carries it (fr_FR), the site's settings, home, login, register, logout and each language's address, the guard's user, the colour scheme of the visitor's mode (omnibridge.theme), the session's messages Screen\ScreenContext

With omnibridge.media on (see below): Base\Contract\Media (Media\ServiceMedia, MediaService's answers), Base\Service\MediaService, Base\Service\ImageRoute, and omnibase's image routes. Off (the default), none of them.

Screens

With those three, a screen of the core is built on Laravel as under Symfony, without code of the application's:

use Base\Contract\Runtime;
use Base\Screen\Builder\Stories;

$frame = app(Base\Screen\ContextProvider::class)->frame(['destination' => $destination]);
$text  = (new Stories(Runtime::texts(), Runtime::pictures()))->text($thread->getContent(), ['headings' => true]);

An application says where it serves its stored pictures, in a provider:

$this->app->extend(Base\Contract\Pictures::class, fn ($pictures) => $pictures->stored(fn (string $image) => /* its address, or null */));
$this->app->extend(Base\Contract\Texts::class, fn ($texts) => $texts->images(fn (string $source) => /* its address, or null */));

Actions

An action written once for every host (no Request, no Response: its inputs, and a result of omnibase's - Base\Screen\Template, Json, Shell, File, a View, or null for no such page) is a route here, answered by omnibase's Base\Screen\Negotiation (HTML, JSON when the template allows it and the client prefers it, omnibridge/react's protocol and its 409) through Screen\Responder, as Base\Bridge\Symfony\ScreenResponder answers it under Symfony:

use Omnibridge\Laravel\Screen\Responder;

Route::get('/{destination}/articles/{slug}', Responder::action(App\Action\Blog\Article::class))->name('app_article');

The action's parameters are the route's by name, $query the query string's values, $body what a POST sent (its JSON object, else its form fields), the rest the container's. An action that changes something checks its form's token itself, through the Tokens contract, as under Symfony: Laravel's CSRF middleware is left off the routes of the actions that are not GET. The templates are rendered by the container's Twig; an application whose pages carry more (a page context, what it adds once rendered) binds its own Base\Screen\Negotiation, rendering Negotiation::PAGE (a page), PART (a value of a JSON answer) and ALONE (a page whole, its own markup only) each its way.

Routes and bindings from the attributes

The routes an application's actions declare - symfony/routing's #[Route] on the action, the declaration Symfony's router reads on the same class - are registered here by Routing\ActionRoutes, by symfony/routing's attribute loader's rules (not the loader itself, which stands on symfony/config): the same names, localized paths (app_timeline.fr and its _locale, which Http\Localize takes for the page's language), requirements, optional parameters, priorities (registered in the order Symfony matches them) and environments (Laravel's local, testing, production are Symfony's dev, test, prod). A route naming no method answers GET and HEAD. RouterUrls::localized() reads a name as omnibase's router does (app_timeline is app_timeline.fr on a French page, a version without a destination name.default, a language the routes lack the fallback's). The services an application aliases with Symfony's #[AsAlias] are bound by Container\Aliases (read by the attribute's name: no symfony/dependency-injection needed), the core's repositories by their interfaces from omnibase's own list (Base\Repository\Aliases):

use Omnibridge\Laravel\Routing\ActionRoutes;

// routes/web.php: the application's actions and omnibase's.
ActionRoutes::register(app('router'), ActionRoutes::load([
    'App\\Action\\' => base_path('src/Action'),
    'Base\\Controller\\Action\\' => base_path('vendor/omnisense/omnibase/src/Controller/Action'),
], ActionRoutes::environment(app()->environment())));

// a service provider: Aliases::bind($this->app, Aliases::read(['App\\Repository\\' => base_path('src/Repository')]));

What omnibase's pages ask more

app.theme.layout(1) (omnibridge.theme.layouts: the folder of the site's layoutN.html.twig, as omnibase's Themizer finds them); app.request.attributes as Symfony's (the route's name and parameters) and its uri as Symfony writes it; a template's companions (page.css.twig, page.mobile.css.twig, page.js.twig: View\Companions, as omnibase's HtmlTagRenderer adds them); get_short_class() (omnibase's ClassTwigExtension), |trans_entity and |obfuscate (View\OmnibaseFilters); a key no catalogue holds translated as nothing, as omnibase's Translator answers it; an action's Base\Screen\Redirect.

Language

omnibase's language under Laravel, as its LocalizerSubscriber and LocalizerController give it under Symfony: Http\Localize (the web group's middleware: the LANG cookie, else the browser's language among omnibridge.locales.available, else omnibridge.locales.default; a route of one language is that language's) and Http\SwitchLocale (Route::get('/locale/{_locale?}', SwitchLocale::class)->name('_switch_locale'): the choice kept as Symfony writes it, en_GB, and back to the page it came from on the site). Leave LANG out of the encrypted cookies.

Parameters

Runtime::parameters() reads Laravel's config by the same dotted path (app.name; base.user.delay for a site with config/base.php), and, when omnibridge.parameters.hot is on (or the config's own base.parameter_bag.use_hot_bag), the settings the back office links to a parameter, read once in omnibase's tables (layoutSetting, layoutSettingIntl) on the first call: a setting stored as false wins over the config's true, as under Symfony. Laravel's config binding itself is not decorated: Laravel reads and caches its configuration before any provider, everywhere, as an array; settings read from a database have no place there.

Also here

The pieces an omnibase page asks of its framework, for an application that renders omnibase's templates with Laravel: View\Encore (encore_entry_link_tags() and encore_entry_script_tags() as omnibase prints them, with the breakpoints' stylesheets), View\Breakpoints (the style omnibase puts before the first stylesheet), Page\AppVariable and its parts (the app variable with Symfony's words: app.request.getLocale(), app.request.attributes.get('_route'), app.site, app.theme, app.session; Page\Theme is omnibase's ThemizerInterface too, which its theme menus read: Base\Twig\Extension\MenuTwigExtension), Form\FormView and Form\FormRenderer (a few simple forms in the markup of omnibase's form theme), and RouterUrlGenerator (Symfony's UrlGeneratorInterface over the Urls contract, for a library that asks for one).

omnibase's own Twig extensions in every page (View\OmnibaseExtensions, with omnisense/omnibase 4 from its framework-free extensions): the container's Twig\Environment gets the classes the core registers under Symfony - the menus, EnumTwigExtension (enum(), |colorify, |trans_enum), PictureTwigExtension (image(), |thumbnail, |soundify, |mimetype... with omnibridge.media on), LinkTwigExtension (|urlify), IconTwigExtension (|iconify, omnibase's adapters and the Routes contract's icons), TextsTwigExtension (|wysiwyg), NotificationTwigExtension (the bell; push on omnibridge.push), SecurityPolicyTwigExtension, DeployVersionExtension, DemoTwigExtension, and omnibase/consent's panel on omnibridge.consent.config - plus View\HostExtension, what the templates take from Symfony's bundles (Encore's tags on omnibridge.encore, stimulus_controller(), absolute_url(), |url). An application rendering a page in an environment of its own calls OmnibaseExtensions::extend($twig, $translations, $theme); admin() is app.admin (Base\Twig\Variable\AdminLinks); Form\FormExtension gives the form_*() functions over a page's FormViews. Contract\RouterRoutes::elsewhere() answers the routes Laravel does not have (their pattern, a route's icons) by another Routes, as RouterUrls::elsewhere() their addresses.

How this manual is organised

  • Installation: requirements, the provider, the configuration published, a run.
  • Contracts: every binding of OmnibridgeServiceProvider, each adapter's answers.
  • Routing and actions: Routing\ActionRoutes, Screen\Responder, Http\Localize, Http\SwitchLocale, Container\Aliases, Routing\LocalizedNames.
  • Doctrine and images: the connection, omnibase's mapping, the repositories, omnibase's images and their routes.
  • Pages: Twig, omnibase's extensions, HostExtension, Encore, the companions, the app variable, the forms, the screens' context.
  • Signing in: Auth\ and config/omniauth.php key by key.
  • Maps, shields, static export: config/omnimap.php, config/omnishield.php, omnibridge:static:build.
  • Configuration: config/omnibridge.php key by key.
  • Errors, Testing, Verified, and not.

Licence

MIT.

Proposer une modification docs/index.md

Installation

Requirements

Package Version
PHP ^8.4
Laravel (illuminate/auth, config, contracts, http, routing, support) 12 or 13
omnisense/omnibase ^4.0
omnisense/omnibridge, omnibridge/twig, omnibridge/doctrine ^1.0
omnisense/omnidrive ^1.0
doctrine/orm ^3.4, doctrine/dbal ^4.2
symfony/cache, mailer, routing, translation, yaml 7 or 8
twig/twig ^3.10
psr/clock, psr/event-dispatcher ^1.0

omnibase asks for the extensions its own packages need (bcmath, intl, gd, exif, ftp, imagick, igbinary). Suggested, each wired only when installed: omnisense/omniauth (with omniauth/webauthn, omniauth/otp, omniauth/google), omnisense/omnimap, omnisense/omnishield (with omnishield/altcha), omnibase/consent, omnibridge/static.

composer config allow-plugins.omnibridge/composer true
composer require omnisense/omnibase:4.x-dev omnibridge/laravel:1.x-dev
php artisan vendor:publish --tag=omnibridge-config     # config/omnibridge.php
php artisan vendor:publish --tag=omniauth-config       # config/omniauth.php, for the sign-in

omnibridge/composer is the Composer plugin omnisense/omnibase declares its patches and stubs through (omnibridge/composer).

The provider

Omnibridge\Laravel\OmnibridgeServiceProvider is discovered by Laravel (extra.laravel.providers).

  • register(): merges config/omnibridge.php, binds every contract and service (Contracts), Doctrine, Twig and the parameters; registers Auth\AuthServiceProvider when omnisense/omniauth is installed, Map\MapServiceProvider when omnisense/omnimap is, Shield\ShieldServiceProvider when omnisense/omnishield is (each wires nothing until its configuration turns it on).
  • boot(): Base\Contract\Runtime::setContainer($app) - the application itself is the registry's container; with omnibridge.media on, omnibase's media and their image routes; the omnibridge-config publication; omnibridge:static:build in the console when omnibridge/static is installed.

What the application adds itself, as its needs say: Http\Localize in the web group (bootstrap/app.php: $middleware->web(append: [Localize::class]), with LANG left out of the encrypted cookies), the _switch_locale route, its actions' routes (ActionRoutes::register()), its #[AsAlias] bindings (Aliases::bind()) - Routing and actions.

A run

A Testbench application with the provider, two locales, an ICU catalogue and a route (PHP 8.4.26, Laravel 13.38, omnisense/omnibase 4.x-dev, in the package's image omnibridge-laravel:php8.4, 2026-10-27):

# translations/messages+intl-icu.fr.yaml
menu:
    home: 'Accueil'
    welcome: 'Bonjour {name}'
<?php

require 'vendor/autoload.php';

use Base\Contract\Runtime;
use Illuminate\Support\Facades\Route;
use Omnibridge\Contract\Access;
use Omnibridge\Contract\Locales;
use Omnibridge\Contract\Urls;
use Omnibridge\Laravel\OmnibridgeServiceProvider;
use Orchestra\Testbench\Foundation\Application;

$app = Application::create(options: ['extra' => ['providers' => [OmnibridgeServiceProvider::class]]]);
$app['config']->set('omnibridge.locales.available', ['fr-FR', 'en-GB']);
$app['config']->set('omnibridge.translations.paths', [__DIR__.'/translations']);
$app->setLocale('fr');
Route::get('/articles/{slug}', static fn () => '')->name('article');
$app['router']->getRoutes()->refreshNameLookups();

echo Runtime::urls()->path('article', ['slug' => 'le-fuji']), "\n";
echo get_class(Runtime::urls()), ' ', var_export(app(Urls::class) === Runtime::urls(), true), "\n";
echo Runtime::locales()->getLocale(), ' ', implode(',', Runtime::locales()->getAvailableLocales()), "\n";
echo Runtime::translations()->trans('@messages.menu.welcome', ['name' => 'Ada']), ' | ', var_export(Runtime::translations()->exists('menu.home'), true), ' | "', Runtime::translations()->trans('@messages.menu.missing'), "\"\n";
echo var_export(Runtime::access()->isGranted('ROLE_ADMIN'), true), ' ', var_export(Runtime::access()->getUser(), true), "\n";
echo Runtime::environment()->name(), ' ', get_class(Runtime::clock()), ' ', get_class(Runtime::events()), "\n";
echo get_class(Runtime::parameters()), ' ', Runtime::parameters()->get('app.locale'), "\n";
/articles/le-fuji
Omnibridge\Laravel\RouterUrls true
fr-FR fr-FR,en-GB
Bonjour Ada | true | ""
false NULL
workbench Omnibridge\Laravel\Contract\DateClock Omnibridge\Laravel\Contract\DispatcherEvents
Base\Service\HotParameters fr
  • The registry and the container answer one object.
  • Laravel's fr is the site's fr-FR; the default first.
  • A key no catalogue holds is printed as nothing, as omnibase's Translator answers it.
  • Nobody signed in: no role, no user. workbench is Testbench's environment name.
  • Runtime::parameters() is omnibase's HotParameters over Laravel's config.

Proposer une modification docs/installation.md

Contracts

Every binding of OmnibridgeServiceProvider::register(), all singletons unless said otherwise. The registry (Base\Contract\Runtime, omnisense/omnibridge's Omnibridge\Runtime with omnibase's accessors) is handed Laravel's application at boot: Runtime::urls() is app(Urls::class).

The bindings

Abstract Concrete Built from
Omnibridge\Laravel\RouterUrls (alias Omnibridge\Contract\Urls) RouterUrls omnibridge.assets.manifest
Omnibridge\Laravel\CatalogueTranslations (alias Omnibridge\Contract\Translations) CatalogueTranslations omnibridge.translations.paths, .fallback (else app.fallback_locale, else en)
Omnibridge\Contract\Locales Contract\AppLocales omnibridge.locales.available, .default
Base\Contract\Nouns Base\Service\Model\EntityNouns the Translations, omnibridge.entity_namespaces
Omnibridge\Contract\Access Contract\GateAccess the gate, the auth manager
Omnibridge\Contract\Sessions Contract\GuardSessions the auth manager
Omnibridge\Contract\Tokens Contract\SessionTokens the session store
Omnibridge\Contract\Environment Contract\AppEnvironment $app->environment(), app.debug, app.key
Omnibridge\Contract\Routes Contract\RouterRoutes the router
Omnibridge\Contract\Files Contract\DiskFiles the filesystem manager, omnibridge.storages
Omnidrive\Flysystem\OperatorDrives (alias Omnidrive\Drives) each disk's Flysystem operator by its storage name (Media\DiskStorages)
Psr\Clock\ClockInterface Contract\DateClock Laravel's Date
Psr\EventDispatcher\EventDispatcherInterface Contract\DispatcherEvents Laravel's dispatcher
Symfony\Component\Mailer\MailerInterface Contract\TransportMailer the default mailer's Symfony transport
Base\Contract\Pictures Screen\AddressPictures the Urls, the public folder, MediaService when omnibridge.media is on
Base\Contract\Texts Screen\WysiwygTexts the container's Twig (lazily)
Base\Screen\ContextProvider (bind, not shared) Screen\ScreenContext the application
Base\Screen\Negotiation (bind) omnibase's Negotiation the container's Twig for every template, the context provider, omnibridge.screens.version
Omnibridge\Laravel\Screen\Responder (bind) the Negotiation
Symfony\Component\Cache\Adapter\FilesystemAdapter (aliases Psr\Cache\CacheItemPoolInterface, Symfony\Contracts\Cache\CacheInterface) namespace omnibase, in storage/framework/cache/omnibase-pool
Base\Service\ParameterBagInterface Base\Service\HotParameters over Parameters\ConfigParameters Laravel's config, the settings laid over it (Parameters)
Doctrine\DBAL\Connection, Parameters\DatabaseSettings, Doctrine\ORM\EntityManagerInterface, Base\Bridge\Doctrine\Mapping Doctrine and images
Twig\Environment, View\OmnibaseExtensions Pages

With omnibridge.media on, at boot: Media\LaravelMediaRouter, Base\Service\Obfuscator (alias ObfuscatorInterface), Base\Service\Flysystem, Base\Service\MediaService, Base\Service\ImageRoute, Base\Contract\Media (Media\ServiceMedia), MediaEnhancerInterface, and the image routes (Doctrine and images).

An application rebinds any of them in its own provider ($this->app->singleton(Urls::class, ...)), or extends one ($this->app->extend(RouterUrls::class, fn ($urls) => $urls->elsewhere(...))).

The adapters

RouterUrls

  • path($name, $parameters): Laravel's route($name, $parameters, false) - the parameters that are null left out. A name Laravel does not have goes to elsewhere()'s closure when one is given (fn (string $name, array $parameters, string $locale): string), else Laravel's error.
  • url(): the path at the request's scheme and host (an address the closure gave whole kept as it is).
  • asset($path): the manifest's entry (a webpack or Vite manifest: ./fonts/RobotoSlab-Bold.woff2 is /assets/fonts/RobotoSlab-Bold.9d4f57b2.woff2), else /<path>.
  • localized(string $fallbackLocale = 'en'): a copy that reads a name as omnibase's router does (Routing\LocalizedNames): app_timeline is app_timeline.fr on a French page, then the name itself, then app_timeline.default.fr, app_timeline.default, then the fallback language's version. Not on by default: the bound object reads names as Laravel does.
  • resolve($name): the route a name means here, null for none.

CatalogueTranslations

The ICU catalogues of the folders given - <domain>+intl-icu.<locale>.yaml, <domain>.<locale>.yaml, and XLIFF files (*.xlf: each unit's source and target, read without symfony/config) - by Symfony's Translation component, the later folders over the earlier, in Laravel's current locale unless one is given.

  • A message is named as omnibase's templates name it: @messages.menu.item2 is menu.item2 of the messages domain; the parameters by position ({0}) or by name ({privacy}), given both as {name} and name.
  • A key no catalogue holds (a dotted key) is answered '', as omnibase's Translator answers it; a text that is no key is answered as it was asked.
  • A message that is itself a key is followed, five times at most.
  • exists(): the catalogue of the locale or a fallback defines the key, in the domain or its +intl-icu one.
  • translator() gives Symfony's Translator, locale() the current locale.

AppLocales

Laravel's locale (fr, fr_FR) as the site's (fr-FR): the available locale it names, else the first of the same language, else itself with a dash. The default is omnibridge.locales.default, else the first available, else app.locale; getAvailableLocales() puts it first.

GateAccess, GuardSessions

isGranted($attribute, $subject) is Gate::allows() (an enumeration as it is, anything else as a string; the subject as the only argument). With the sign-in on, Auth\Roles answers Security's attributes through Gate::before() (Signing in). getUser() is the default guard's user, unwrapped from Auth\AccountUser to the application's own object. signOut($user) logs out a StatefulGuard whose signed-in user is that very object.

SessionTokens

Laravel has one session token for every intent: csrf($intent) is the session's token whatever the intent; isValid() compares it in constant time, never valid for a missing or empty token or session token. Both are bound to the session and renewed with it. A page then carries the same value for every intent (its forms, data-csrf, the X-CSRF-Token headers, the sign-out link).

AppEnvironment, DateClock, DispatcherEvents, TransportMailer

name() is $app->environment() (production, local, testing: Laravel's names, not Symfony's); DateClock::now() follows Date::setTestNow() and travel(); DispatcherEvents::dispatch() hands the object to Laravel's listeners of its class and returns it; TransportMailer::send() uses the default mailer's Symfony transport (Mail\LaravelMailer does the same for a named mailer, for the sign-in's e-mails).

RouterRoutes

pattern($name): / and the route's URI, else elsewhere()'s answer; match($url): the name of the route a GET of the address matches, null for none; icons(): none of Laravel's own - elsewhere($routes) gives another Routes for the routes Laravel lacks and the icons.

DiskFiles

A storage name is the disk omnibridge.storages maps it to, else the disk of that name, else omnibridge.storages.default, else filesystems.default. A disk whose driver is local is local (localPath() its path); any other is remote. publicUrl() is the disk's url() of an existing file (its path alone for a local disk), null for a local disk without url or a missing file; publicPath() that URL under public_path() for a local disk.

Parameters

Runtime::parameters() (Base\Service\ParameterBagInterface) reads Laravel's config by the same dotted path (app.name; base.user.delay for a site with config/base.php), and, when omnibridge.parameters.hot is on (or, null, the config's own base.parameter_bag.use_hot_bag) and a Doctrine connection is configured, the settings the back office links to a parameter, read once in omnibase's tables (layoutSetting, layoutSettingIntl, not the vaulted rows) on the first call, in the default language's value: a setting stored as false wins over the config's true, as under Symfony. A database that does not answer leaves the config alone. ConfigParameters::get($key, $bag) lays a bag over the value key by key, as omnibase's bags do; all() is every parameter by its full name. Laravel's config binding itself is not decorated.

Proposer une modification docs/contracts.md

Routing and actions

Actions: Screen\Responder

An action written once for every host (no Request, no Response: its inputs, and a result of omnibase's - Base\Screen\Template, Json, Shell, File, Redirect, a View, or null for no such page) is a route here, answered by omnibase's Base\Screen\Negotiation (HTML, JSON when the template allows it and the client prefers it, omnibridge/react's protocol and its 409) through Screen\Responder, as Base\Bridge\Symfony\ScreenResponder answers it under Symfony.

use Omnibridge\Laravel\Screen\Responder;

Route::get('/{destination}/articles/{slug}', Responder::action(App\Action\Blog\Article::class))->name('app_article');
Route::get('/feed', Responder::action(App\Action\Feed::class, 'page'));
Method What it does
Responder::action(string $class, string $method = '__invoke'): \Closure a route's handler: the action made by the container and called ($app->call()) with the route's parameters by name, query (the query string's values) and body (Responder::body()); the other parameters from the container; its result answered
respond(Request $request, $result, array $legacy = [], ?Frame $frame = null) the result answered; null is a 404 (NotFoundHttpException)
Responder::body(Request $request): array the request's JSON object, else its form fields, string keys only
Responder::visit(Request $request): Visit what the client asked: the URI, Accept, X-Omnibridge, X-Omnibridge-Version
Responder::response(Answer $answer) Laravel's response of omnibase's answer: a BinaryFileResponse for a file, a JsonResponse for JSON (the body already encoded), a Response otherwise; Vary, private/public, max-age, must-revalidate, no-store from the answer's cache

An action that changes something checks its form's token itself, through the Tokens contract, as under Symfony: Laravel's CSRF middleware is left off the routes of the actions that are not GET (below). The templates are rendered by the bound Negotiation: the container's Twig for every kind. An application whose pages carry more (a page context, what it adds once rendered) binds its own Base\Screen\Negotiation, rendering Negotiation::PAGE (a page), PART (a value of a JSON answer) and ALONE (a page whole, its own markup only) each its way. omnibridge.screens.version is the front end's version omnibridge/react's protocol compares (null: none).

Routes from the attributes: ActionRoutes

The routes an application's actions declare - symfony/routing's #[Route] on the action, the declaration Symfony's router reads on the same class - are registered here by Routing\ActionRoutes, by symfony/routing's attribute loader's rules (not the loader itself, which stands on symfony/config).

use Omnibridge\Laravel\Routing\ActionRoutes;

// routes/web.php: the application's actions and omnibase's.
ActionRoutes::register(app('router'), ActionRoutes::load([
    'App\\Action\\' => base_path('src/Action'),
    'Base\\Controller\\Action\\' => base_path('vendor/omnisense/omnibase/src/Controller/Action'),
], ActionRoutes::environment(app()->environment())));
Method What it does
load(array $folders, ?string $env = null): RouteCollection namespace prefix => folder (PSR-4); every non-abstract class's routes, in Symfony's order (by priority, then as declared); a route declared for other environments (env: on the attribute) left out
environment(string $laravel): string local => dev, testing => test, production => prod (ActionRoutes::ENVIRONMENTS), anything else as it is
register(Router $router, RouteCollection $routes): void each route in Laravel's router, in the collection's order
action(Route $route): array [class, method] of a route's _controller; InvalidArgumentException when it names none or no class

As symfony/routing's loader reads them:

  • a method's #[Route] under its class's (prefix, name prefix, requirements, defaults, methods, host, priority, environments); an invokable class's own #[Route] when its methods declare none;
  • a name the attribute does not give: the class name lower-cased with _, then _<method>;
  • a path per language: one route per language, name.fr, with _locale (and its requirement) and _canonical_route; a localized class prefix needs a path for each language (LogicException);
  • the defaults of the action's parameters set on the variables of their name;
  • _controller as FrameworkBundle names an action: the class for __invoke, Class::method otherwise.

Registered in Laravel: the methods declared, or GET and HEAD for a route that names none; a trailing variable with a default made optional ({page?}); the requirements of the path's variables as where(); the defaults _locale and the optional variables'; the host as domain(); the CSRF middleware (PreventRequestForgery, ValidateCsrfToken, VerifyCsrfToken) left off a route with a method other than GET, HEAD, OPTIONS. Each route runs Responder::action(). Http\Localize takes a route's _locale for the page's language.

Localized names: LocalizedNames

LocalizedNames::resolve(string $name, string $locale, iterable $names, string $fallbackLocale = 'en'): ?string

  • the route a name means in a language, as omnibase's router reads it: name.<lang>, name, name.default.<lang>, name.default, then the first of the routes, in their order, that is name.<fallback> or name.default.<fallback> (a German page links to /japon/travels). Used by RouterUrls::localized().

The language: Http\Localize, Http\SwitchLocale

omnibase's language under Laravel, as its LocalizerSubscriber and LocalizerController give it under Symfony.

Http\Localize, the web group's middleware (bootstrap/app.php: $middleware->web(append: [Localize::class]), LANG left out of the encrypted cookies):

  1. a route of one language (_locale) is that language's;
  2. else the LANG cookie's language when it is one of omnibridge.locales.available;
  3. else the browser's preferred language among them (getPreferredLanguage());
  4. else omnibridge.locales.default, else (null or empty) Laravel's app.locale.

Laravel's locale is set to the two letters. After the page, the language shown is written back to LANG as the site names it (en-GB), session cookie, not HTTP only, SameSite Lax - unless the page set LANG itself.

Http\SwitchLocale, the language menu's links:

Route::get('/locale/{_locale?}', Omnibridge\Laravel\Http\SwitchLocale::class)->name('_switch_locale');

A language of the site's: the choice kept in LANG as Symfony writes it (en_GB), back to the page the visitor came from on this site (its path), else /. No language: the choice forgotten, back. A language the site does not speak: the choice forgotten, the home.

Bindings from the attributes: Container\Aliases

The services an application aliases with Symfony's #[AsAlias] (read by the attribute's name: no symfony/dependency-injection needed), bound as Symfony's container aliases them:

use Omnibridge\Laravel\Container\Aliases;

// a service provider's register()
Aliases::bind($this->app, Aliases::read(['App\\Repository\\' => base_path('src/Repository')]));
Method What it does
read(array $folders): array interface => class for every #[AsAlias] of the folders' non-abstract classes, in the order of their files
of(string $class): array the interfaces a class is the alias of: the attribute's id, else the class's only interface (LogicException when it has none or several, or names no interface nor class)
bind(Container $container, array $aliases): void each as a singleton resolving to the container's instance of the class

With omnibase's mapping on, the core's repositories are bound by their interfaces from omnibase's own list (Base\Repository\Aliases::INTERFACES), and a repository class asked of the container is the entity manager's repository of the entity that names it (Doctrine and images).

Proposer une modification docs/routing.md

Doctrine and images

omnibase stays on Doctrine for storage: this package opens Doctrine on one of Laravel's connections (omnibridge/doctrine), so Doctrine and Eloquent, if the application uses it, read the same tables.

The connection and the entity manager

// config/omnibridge.php
'doctrine' => [
    'connection' => 'mysql',              // a connection of config/database.php
    'entities' => [app_path('Entity')],   // the folders the entity manager maps
    'omnibase' => false,
],
Binding What it is
Doctrine\DBAL\Connection Doctrine\Connections::create() of database.connections.<connection>: the drivers mysql and mariadb (pdo_mysql), pgsql, sqlite (:memory: or a path), sqlsrv; host, port, database, username, password, unix_socket, the charset (utf8mb4 for MySQL by default). No connection named: LogicException; an unknown one: InvalidArgumentException; another driver: InvalidArgumentException
Parameters\DatabaseSettings omnibase's settings (layoutSetting, layoutSettingIntl) through that connection
Doctrine\ORM\EntityManagerInterface omnibridge/doctrine's EntityManagers::create() over entities, debug from app.debug; or omnibase's (below)
a repository class asked of the container (an action's type-hint), the entity manager's repository of the entity that names it as its repositoryClass (the one named after it when several do); LogicException when none does

omnibase's Doctrine mapping

// config/omnibridge.php
'doctrine' => [
    'connection' => 'mysql',
    'entities' => [app_path('Entity')],
    'omnibase' => ['project_dir' => base_path()],   // or true; false (the default) leaves it off
],

With omnibridge.doctrine.omnibase on, the bound entity manager is omnibase's, as under Symfony: built by omnibridge/doctrine with Base\Bridge\Doctrine\Mapping::configure() (the repositories from omnibase's registry), over the core's entities beside the application's, then Mapping::register() - the class convention (App\Entity\X is Base\Entity\X where the application has none, an alias and not a subclass; an application's own class always wins), the column types and enumerations (the project's src/Enum), the naming strategy, the discriminator maps, #[Alias], the translations, the listeners and the trash and vault filters. Its options are Mapping::register()'s: project_dir (base_path()), environment (omnibase's name, the vault's: prod, dev, test for Laravel's production, local, testing), cache_dir, parameters (over Laravel's base config by full name - the entities read base.uploader.storage...), types, enumerations, attribute_paths, resolve_target_entities, entity_listeners. app(Mapping::class) gives what it registered. OmnibaseMappingTest checks the metadata against those omnisense/omnibase records from its Symfony harness, class by class.

The options are read by OmnibridgeServiceProvider::omnibase(): project_dir (base_path()), environment (the given one, else Laravel's by omnibase's name: production prod, local and development dev, testing test), cache_dir (storage/framework/cache/omnibase-doctrine/<env>), parameters (the given ones over Laravel's whole base config flattened by full name), and any other option handed to Mapping::register() as it is. Beside them the provider gives flysystem (omnibase's Flysystem, with the media on) and services (a closure answering a repository's other constructor arguments from Laravel's container by type). With the mapping on, the core's repositories are also bound by their interfaces (Base\Repository\Aliases::INTERFACES).

omnibase's images

// config/omnibridge.php
'storages' => ['local.cache' => 'omnibase-images', 'local.uploads' => 'uploads'],   // Laravel's disks
'media' => true,   // or ['routes' => false, 'secret' => ...]; false (the default) leaves it off

With omnibridge.media on, omnibase's media rule runs under Laravel as under Symfony: MediaService (thumbnails, formats, the image route's obfuscated addresses) built on Laravel's router (Media\LaravelMediaRouter, the core's Base\Contract\MediaRouter), omnibase's Obfuscator and Flysystem (its storages are Laravel's disks, Media\DiskStorages: omnibridge.storages, else the disk of the storage's name; local.cache, where the thumbnails are kept, is required) and Imagine (Imagick, else GD). The image routes (/images/..., /contents/...) are declared under their Symfony names in Symfony's order (Base\Service\ImageRoute::ROUTES) and answered by the core's Base\Service\ImageRoute (Media\ImageController, a line per request): the same addresses, thumbnails, formats, redirections and placeholder. Its parameters are the config's base: base.images.* (max_quality, enable_webp, fallback, no_image...), base.obfuscator.* (uuid, compression... and cache_dir, the map of the identifiers: share the Symfony site's to read the addresses it wrote), base.uploader.storage, base.twig.editor.storage; omnibase's defaults where it says nothing. A file has the address Symfony gives it when it has the same path on both hosts (the identifiers are made of it: the same project folder, the uploads where the Symfony site's public/ links them).

With omnibase's mapping on as well, what a site's entities and repositories ask of their host is answered here too: the attribute reader reads the stored files through that Flysystem (#[Uploader]: an avatar, an article's audio, whose /contents/... addresses ImageRoute serves), BaseTrait::getMediaService() is the bound MediaService, the editor's pictures go through omnibase's MediaEnhancer (MediaEnhancerInterface, the image route's address of each picture of a text, as Symfony's |wysiwyg gives it), and a repository taking services beside the registry (a site's CommentRepository, its cache pool first) is given them from Laravel's container by type (Mapping::register()'s services). omnibase's cache pool (Psr\Cache\CacheItemPoolInterface, Symfony's CacheInterface) is Symfony's cache component in storage/framework/cache/omnibase-pool - not Laravel's store, which unserializes no object (cache.serializable_classes).

The routes are declared at boot unless omnibridge.media.routes is false, each $router->get($path, ImageController::class)->name($name) with data matching .+; ImageController hands the route's data, identifier and extension to the ImageRoute action the route names (ImageRoute::ROUTES), the cacheless routes to ImageRoute::cacheless(). Imagine is Imagick's when the extension is loaded, GD's otherwise; SVG through omnibase's own Imagine. The obfuscator's compressions: null, zlib, gzip, deflate, and Hashids keyed by omnibridge.media.secret (else app.key). The defaults laid under the config's base once (base.images.*: max_resolution UHD, debug false, max_quality 1, timeout 60, enable_webp true, fallback false, no_image the placeholders of public/bundles/base/images; base.obfuscator.*: level -1, uuid the URL namespace, encoding null, max_length 0, compression null, cache_dir storage/framework/cache/omnibase-obfuscator; base.uploader.storage and base.twig.editor.storage null). A media parameter asked with the media off is a LogicException ("omnibase's media are off: omnibridge.media.").

First used by lesringostars' Laravel twin, which renders the Symfony site's blog and articles from the same database and templates.

Proposer une modification docs/doctrine-media.md

Pages

omnibase stays on Twig for its templates: this package opens Twig over the site's templates (omnibridge/twig) and gives a page what omnibase's pages ask of Symfony.

The Twig environment

Twig\Environment is bound when it is first asked, by View\Twig::create():

// config/omnibridge.php
'twig' => [
    'paths' => [
        [resource_path('views'), null],                                   // the main namespace
        [base_path('vendor/omnisense/omnibase/templates'), 'Base'],       // @Base
    ],
    'cache' => null,        // storage/framework/twig when null
    'timezone' => null,     // app.timezone when null
],
Added What it gives
a FilesystemLoader each [folder, namespace] pair, null for the main namespace
options auto_reload true, strict_variables false, cache as configured
omnibridge/twig's ContractExtension path(), url(), asset(), |trans on the bound RouterUrls and CatalogueTranslations
omnibridge/twig's AccessExtension is_granted() and csrf_token() on the container's Access and Tokens, each when bound
omnibridge/twig's HelperExtension the helpers, in the application's locale and omnibridge.twig.timezone (else app.timezone)
omnibridge/twig's MapExtension when config/omnimap.php names a provider (Maps)
View\OmnibaseExtensions::extend() after the environment is resolved: omnibase's own extensions, below

View\Twig::create(array $paths, Urls $urls, Translations $translations, string $locale, ?string $timezone = null, array $extensions = [], array $options = []) builds one for an application that wants an environment of its own.

omnibase's own extensions: View\OmnibaseExtensions

The classes the core registers under Symfony, built on what Laravel binds - the same functions and filters from the same code - each added once, when the core has its framework-free extensions (omnisense/omnibase 4 from audit R24) and what it stands on is bound:

Extension What it gives Stands on
View\HostExtension always: encore_entry_link_tags(), encore_entry_script_tags(), encore_entry_css_source() (with omnibridge.encore.builds), html_entry_noscript_tags() (nothing), stimulus_controller(), absolute_url(), |url Urls, the request
MenuTwigExtension render_locale(), render_theme_*() Locales, the visitor's theme (omnibridge.theme.modes)
EnumTwigExtension enum(), |colorify, |trans_enum the page's Translations (the entity manager built first, for the DBAL types)
ClassTwigExtension get_class(), class_exists(), get_short_class() -
View\OmnibaseFilters |trans_entity, |trans_entityExists, |obfuscate (with the media) Translations (EntityNouns), the Obfuscator
LinkTwigExtension |urlify, |linkify -
IconTwigExtension |iconify omnibase's Font Awesome and Bootstrap adapters over omnibridge.icons.metadata (else public/bundles/base/metadata), cached in storage/framework/cache/omnibase-icons; a route's icons by the Routes contract
PictureTwigExtension image(), |thumbnail, |soundify, |mimetype... MediaService (omnibridge.media on)
TextsTwigExtension |wysiwyg, |wysiwyg_toc the Texts contract, on this environment
NotificationTwigExtension the bell Access, Tokens, a bound Notifications repository; push with omnibridge.push's keys
SecurityPolicyTwigExtension security_policy a bound Base\Service\SecurityPolicy
DeployVersionExtension deploy_version() omnibridge.deploy.project_dir, else base_path()
DemoTwigExtension demo_mode(), demo_accounts() the application's environment
omnibase/consent's ConsentExtension consent_options(), consent_settings() omnibase/consent installed, omnibridge.consent.config (a YAML file with a consent: key, or the array)
AccountTwigExtension omnibase's account screens the policy when bound, Urls, Tokens, Access

extend(Environment $twig, ?Translations $translations = null, ?ThemizerInterface $theme = null) adds them to another environment (the page's own language, its theme); admin() is omnibase's AdminLinks on the Routes and Urls contracts (app.admin).

What the templates take from Symfony's bundles: View\HostExtension

  • encore_entry_link_tags(entry), encore_entry_script_tags(entry) (View\Encore, as omnibase's WebpackTagRenderer prints them): an entry is its files and those of its -async and -defer halves, from the first build of omnibridge.encore.builds that has any; a stylesheet comes with its breakpoint versions beside it (layout2-async-mobile.css, media from omnibridge.encore.breakpoints); every script deferred; a file printed once per page; omnibridge.encore.optional names the entries a page loads with another (form => form.editor). encore_entry_css_source(entry) the stylesheets' content (an e-mail's inlined style). An entry may be given as omnibase's {value: name}.
  • stimulus_controller(name): data-controller="name" as StimulusBundle normalises it (a/b a--b, a_b a-b, no leading @).
  • absolute_url(path): a path whole at the request's origin; |url: an address whole, a route's name by the Urls contract.

The page around the template

  • View\Breakpoints::style($breakpoints) and inject($html, $breakpoints): the style omnibase puts before the first stylesheet of the head, whose body:before names the breakpoint in each media query.
  • View\Companions::inject(Environment $twig, string $template, array $context, string $html, array $breakpoints = []): beside client/timeline.html.twig, client/timeline.css.twig is the page's style (before </head>), client/timeline.mobile.css.twig its style for a breakpoint (<style media=...>), client/timeline.js.twig its script (after </body>), each rendered in the page's context, as omnibase's HtmlTagRenderer adds them.

The app variable: Page*

Page\AppVariable::of(Request $request, string $locale, Site $site, Theme $theme, ?object $user = null, ?object $admin = null): app with Symfony's words - app.request (Page\RequestView: getLocale(), getUri() as Symfony writes it, getPathInfo(), headers.get(), attributes.get('_route'), _route_params and the route's parameters), app.user, app.session (Page\SessionView: started, flashbag.all(), the messages flashed under omnibridge.flashes by SessionView::add($session, $label, $message)), app.site (Page\Site: title(), slogan(), logo(), meta(), age(), execution_time() from the settings), app.theme (Page\Theme: the mode and filter of the THEME/MODE and THEME/FILTER cookies among omnibridge.theme, omnibase's ThemizerInterface too, getLayout(N) from omnibridge.theme.layouts' layoutN.html.twig), app.referrer (Page\Referrer, this site's page the visitor came from) and app.admin. The application builds it and hands it to its templates.

Forms: Form*

A few simple forms in the markup of omnibase's form theme, for the forms every page carries (omnibase's FormProxy: the search bar, the newsletter): Form\FormView (Symfony's FormView words: vars, children, add(), read-only array access), Form\FormRenderer (start(), widget() - html, checkbox, choice, inputs -, row(), fieldLabel(), errors() (nothing), rest(), end(), labels and placeholders translated), Form\FormExtension (form_start(), form_widget(), form_row(), form_label(), form_errors(), form_rest(), form_end(), form_proxy() / form_view() by name). What answers a submission is the application's.

The screens' context and texts

  • Screen\ScreenContext (Base\Screen\ContextProvider, not final): the locale as a Symfony request carries it (fr_FR); the host (scheme, host, base path); the site from the settings (title, slogan, logo by its asset's address, the year, the description) when a connection is configured; the links - home (app_index, else /), security_login, security_register, security_logout, each language's _switch_locale - those that resolve; the viewer (the Access contract's user: its id and name); the theme's colour scheme; the session's flashes, taken. An application binds its own to add its frame fields.
  • Screen\AddressPictures (Base\Contract\Pictures): |picture's rule - the application's stored() closure first; a file under the public folder by its thumbnail (MediaService) or its public path; an address or a root path as it is; anything else asset('assets/'.$image).
  • Screen\WysiwygTexts (Base\Contract\Texts): |wysiwyg and |wysiwyg_toc for an Editor.js document (the mentions left as written, every leaf a string, the links' pass, the pictures by images(), the headings' anchors, omnibase's EditorJsRenderer and @Base/form/wysiwyg/editor_js.html.twig) or Quill's HTML (@Base/form/wysiwyg/quill.html.twig); on($twig) renders through another environment. The Twig environment must hold omnibase's views under @Base.
$this->app->extend(Base\Contract\Pictures::class, fn ($pictures) => $pictures->stored(fn (string $image) => /* its address, or null */));
$this->app->extend(Base\Contract\Texts::class, fn ($texts) => $texts->images(fn (string $source) => /* its address, or null */));

Proposer une modification docs/views.md

Signing in (omniauth)

// config/omnibridge.php
'auth' => true,
// config/auth.php
'guards' => ['web' => ['driver' => 'omniauth', 'provider' => 'omnibase']],
'providers' => ['omnibase' => ['driver' => 'omniauth']],

With omnibridge.auth on and omnisense/omniauth installed (it is only suggested), Auth\AuthServiceProvider wires omniauth's sign-in under Laravel as omnibridge/symfony wires it under Symfony (plan "omniauth", §3.2, step AU8), from config/omniauth.php (php artisan vendor:publish --tag=omniauth-config) - the same keys as config/packages/omniauth.yaml there, so one site says the same thing under both hosts: each method's key turns it on (the password, the sign-in link, the e-mailed code, the backup codes, the trusted devices; passkeys, the authenticator application and the identity providers with their package, omniauth/webauthn, omniauth/otp and omniauth/google - the providers' mechanics are omniauth's own Omniauth\OAuth\), the stores ("omnibase": the core's Base\Auth\ on the entity manager - the user, userToken, userPasskey and userIdentity tables -, or the container's bindings by key), the throttle on a Laravel cache store, the messages. totp.secret_key (env('OMNIAUTH_TOTP_KEY'): base64 of 32 bytes, the Symfony site's very key, never in the repository) wraps the TotpSecrets store in omniauth/otp's SealedTotpSecrets over a SodiumSecretBox: the secrets sealed at rest, as omnibridge/symfony decorates its store.

  • Auth\OmniauthGuard (StatefulGuard, driver omniauth) keeps the account's handle in the session, reads the account back through omniauth's Accounts at each request, renews the session's id at a sign-in, sets the signed remember-me cookie (Auth\RememberMe: the handle, the expiry, the password hash and the address, as Security's signature_properties) and forgets it at a sign-out; attempt() asks omniauth the password. Auth\OmniauthUserProvider (driver omniauth) answers Laravel's UserProvider over the same stores, the passwords checked by symfony/password-hasher as under Symfony (>72-byte passwords included).
  • The account is Laravel's Authenticatable through Auth\AccountUser, a wrapper the contracts unwrap: Omnibridge\Contract\Access::getUser(), the screens' viewer and Twig's app.user give the application's own object, as under Symfony.
  • Auth\Controllers\SignIn is every way in: the password form (and its page, the site's security/login.html.twig with the form as omnibase's templates read it), the sign-in link, a passkey (web-auth's JSON), the second factor's code, an identity provider's return, and the sign-out (CSRF-checked as Security's logout with enable_csrf); Auth\Controllers\Ceremony the second factor's page (scheb/2fa's variables: the site's template renders unchanged), a passkey's options, the trip out to a provider. The forms' CSRF tokens are checked through the Tokens contract (Laravel's session token); Laravel's own CSRF middleware is left off these routes.
  • Auth\Routes declares them at the Symfony site's addresses and under its route names (security_login, 2fa_login, 2fa_login_check, oauth_google, oauth_google_check, security_logout, security_logoutRequest...), in the web group; omniauth.routes false to declare them yourself.
  • Auth\Middleware\SecondFactorPending (appended to the web group, before auth) keeps a visitor between two factors on the second factor's page; with omnibase's sign-in effects, Auth\Middleware\EnrolmentPrompt sends a signed-in account to the second factor's enrolment page as omnibase's SecurityEnrolmentSubscriber does under Symfony (its rule, Base\Auth\EnrolmentPrompt: the offer after a new device, a required second factor until "not now"), and the browser's PHPUSERID is written for the domain omnibase's router gives its cookies (Base\Routing\CookieDomain on base.router.use_fallbacks and host_fallbacks); Auth\Roles answers Security's attributes through Gate::before() - ROLE_* with omniauth.role_hierarchy, IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED_2FA_IN_PROGRESS, PUBLIC_ACCESS - so is_granted() and the Access contract answer as under Symfony.
  • The cookies the Symfony site shares (trusted_device in scheb/2fa's format, the remember-me) are left out of Laravel's cookie encryption; omniauth.secret is the Symfony site's kernel.secret, so the devices it trusted and the codes it sent are good here. An identity provider's return address is the path of its check route, completed with the host of each request (a long-running server keeps the providers).

With omnibase's stores, the core's Base\Auth\Listener\UpdateAccount brings the account up to date at its sign-in (a role dropped, its time zone and language kept, its kick lifted); Base\Auth\Policy decorates the second-factor rule where the application binds omnibase's SecurityPolicy; a sign-in link or an e-mailed code needs a Omniauth\Contract\Sender (omnibase's NotifierSender needs its Notifier).

What each method does, its stores and events are omniauth's own manual (omniauth's Laravel page); this page is the reference of the bridge.

Turning it on

Auth\AuthServiceProvider is registered by OmnibridgeServiceProvider when omnisense/omniauth is installed; it merges config/omniauth.php and binds its services in register(), and wires the guard, the gate, the routes and the middleware in boot() only when omnibridge.auth is true (AuthServiceProvider::on()). A method whose package is missing stops the boot: config/omniauth.php turns "<key>" on, which needs <package>: composer require <package>.

config/omniauth.php

The same keys as omnibridge/symfony's omniauth.yaml. A method's key turns it on; null (or absent) is off - but password, on unless it is null or false. The defaults below are Auth\Settings', laid under what the file says.

Key Default Effect
secret null: app.key (base64-decoded when written base64:) keys the e-mailed codes' hashes, the trusted devices' tokens and the remember-me signature: the Symfony site's kernel.secret, for its devices and codes to be good here
login_route security_login where a refused or abandoned attempt goes back to
stores omnibase omnibase: omnibase's Base\Auth\ stores on the entity manager (UserAccounts, UserPasswords, UserLinkTokens, UserEmailCodes, UserBackupCodes, UserTrustedVersion, UserTotpSecrets, UserIdentities, UserPasskeys) where the container does not bind the contract already, and omnibase's NotifierSender as the Sender; or an array key => binding (accounts, passwords, link_tokens, email_codes, backup_codes, trusted_version, totp_secrets, identities, passkeys, sender), each contract's own name for a key left out
password.path /login the sign-in page (GET shows it, POST signs in), route login_route
password.form null the form's name, its fields read under it; null: at the top of the request
password.identifier, .password, .remember _username, _password, _remember_me the fields
password.target_path_parameter, .default_target_path _target_path, / where a visitor goes once signed in
password.csrf {field: _csrf_token, intent: authenticate} checked by the Tokens contract
password.template security/login.html.twig the page, rendered by omnibase's Negotiation
password.allow_login_token true whether the page offers the sign-in link
password.hasher null: symfony/password-hasher's native hasher otherwise security.yaml's password_hashers keys (algorithm: auto, bcrypt, argon2id...; cost, time_cost, memory_cost, migrate_from), read by Symfony's PasswordHasherFactory
link null {route: security_loginWithToken, path: /login/token/{token?}, parameter: token, lifetime: 900, template: security/login_token.html.twig, form: login_token, email: email, csrf: {field: _csrf_token, intent: login_token}}: a GET with a token signs in, without one the page asks for an address, a POST sends the link (needs a Sender)
email_code null {digits: 6, lifetime: 600} (needs a Sender)
backup_codes null {count: 8}
totp null {leeway: 0, period: 30, digits: 6, issuer: null, server_name: null, secret_key: null} (omniauth/otp); secret_key seals the secrets at rest (omniauth.totp.sealed_secrets wraps TotpSecrets)
trusted_device null {lifetime: 2592000, cookie: trusted_device, firewall: main}: scheb/2fa's very cookie, its fwl claim
passkey null {rp_id: null (the request's host), rp_name: null, origins: [], timeout: null, remember: true, options_path: /login/passkey/options, options_route: passkey_login_options, path: /login/passkey, route: passkey_login} (omniauth/webauthn)
oauth null {link_by_email: null, sign_up_route: null, providers: {<name>: {client_id, client_secret, scopes, hosted_domain, redirect_uri: null, service: null, start_path: /connect/<name>, start_route: oauth_<name>, check_path: /connect/<name>/check, check_route: oauth_<name>_check}}}; omniauth/google's GoogleFactory when installed, any factory tagged omniauth.oauth_provider_factory
second_factor {after: null (password, link, oauth), form_path: /login/2fa, form_route: 2fa_login, check_path: /login/2fa/check, check_route: 2fa_login_check, template: security/login_2fa.html.twig, code: _auth_code, trusted: _trusted, csrf: {field: _csrf_token, intent: two_factor}, open_routes: []} the second factor's page and check; open_routes: the routes a visitor between two factors may still open (the page, its check and the sign-out always are)
logout {path: /logout-request, route: security_logoutRequest, page_path: /logout, page_route: security_logout, template: security/logout.html.twig, csrf: {field: _csrf_token, intent: logout}, target: /} the sign-out, CSRF-checked as Security's logout with enable_csrf (403 without the token); its page asks to confirm by a link carrying one
remember_me {cookie: REMEMBERME, lifetime: 604800} the signed remember-me cookie; null: none
role_hierarchy [] security.yaml's role_hierarchy
throttle {store: null, pool: null, limits: []} omniauth's CacheThrottle on a Laravel cache store (the default one when null), or on pool, a PSR-6 pool's binding (the Symfony site's, so both count the same tries; the used TOTP steps kept there too); limits.<scope>: [tries, seconds] over omniauth's defaults; false: none
captcha {pool: null, firewalls: [main]} with omnibase's stores and effects: the captcha after failed sign-ins (Base\Security\SignInCaptcha, its gateway config/omnishield.php's), the failures counted in that pool (Laravel's cache store without one), for these Context firewalls
mail {mailer: null, from: null, loopback: false, test_recipients: [], project_dir: null} the sign-in's e-mails (Base\Notifier\Mail\SignInMails, sent by config/mail.php's mailer): from the settings' address, else from; loopback sends them all to that address; test_recipients (patterns) in debug; project_dir where the e-mails' pictures are
messages [] a refusal's words by omniauth's failure or failure:reason, over Auth\Refusal::MESSAGES (omnibridge/symfony's)
routes true false: declare them yourself (Auth\Routes::register($router, $settings))

The routes

Auth\Routes::register(), in the web group, without Laravel's CSRF middleware (PreventRequestForgery, ValidateCsrfToken, VerifyCsrfToken, App\Http\Middleware\VerifyCsrfToken) - the forms' tokens are checked through the Tokens contract:

Name (default) Path Methods Action
security_login (login_route) /login GET, POST Controllers\SignIn::login (with the password on)
security_loginWithToken /login/token/{token?} GET; POST SignIn::link; SignIn::requestLink (with link)
2fa_login /login/2fa GET Controllers\Ceremony::secondFactorPage
2fa_login_check /login/2fa/check GET, POST SignIn::secondFactorCheck
passkey_login_options /login/passkey/options POST Ceremony::passkeyOptions (with passkey)
passkey_login /login/passkey POST SignIn::passkey
oauth_<name> /connect/<name> GET Ceremony::oauthStart
oauth_<name>_check /connect/<name>/check GET SignIn::oauthCheck
security_logout /logout GET SignIn::logoutPage
security_logoutRequest /logout-request GET, POST SignIn::logout

The pages are rendered through the bound Base\Screen\Negotiation (Auth\Pages), never cached (Cache-Control: no-store, private). The second factor's page has scheb/2fa's variables, as under Symfony.

The bindings

Abstract What it is
Auth\Settings config/omniauth.php resolved
Auth\SessionState (alias Omniauth\Contract\State) the attempt in Laravel's session
Auth\Contexts omniauth's Context of a request (the trusted-device cookie, the firewall trusted_device.firewall, else main)
Omniauth\Contract\Throttle CacheThrottle (LogicException when the throttle is off)
omniauth.second_factors, Omniauth\Method\Methods the second factors; every method
Omniauth\Contract\SecondFactorPolicy EnrolledSecondFactors, wrapped in omnibase's Base\Auth\Policy when the stores are omnibase's and Base\Service\SecurityPolicy is bound
Omniauth\Omniauth the conductor: accounts, methods, state, policy, throttle, the PSR-14 dispatcher, the clock
Symfony\Component\PasswordHasher\PasswordHasherInterface per password.hasher
Auth\RememberMe the signed cookie (LogicException when off)
Auth\Roles, Auth\Pages, Auth\OmniauthUserProvider below
Omniauth\OAuth\Provider\Providers the providers through the factories
omniauth.totp.sealed_secrets (alias TotpSecrets) with totp.secret_key

The guard and the provider

// config/auth.php
'guards' => ['web' => ['driver' => 'omniauth', 'provider' => 'omnibase']],
'providers' => ['omnibase' => ['driver' => 'omniauth']],

Auth\OmniauthGuard (driver omniauth, a StatefulGuard) and Auth\OmniauthUserProvider (driver omniauth), as the README above says. The guard follows the request (setRequest(), refreshed with it). Laravel's EncryptCookies leaves the trusted_device and remember-me cookies out (except()), as the Symfony site writes them plain.

Roles and the gate

Auth\Roles::decide() answers through Gate::before():

Ability Granted to
PUBLIC_ACCESS, IS_AUTHENTICATED_ANONYMOUSLY everybody
IS_AUTHENTICATED, IS_AUTHENTICATED_REMEMBERED any account signed in
IS_AUTHENTICATED_FULLY an account signed in by its proofs, not by its remember-me cookie
IS_AUTHENTICATED_2FA_IN_PROGRESS the visitor between two factors
ROLE_* an account holding the role, or one above it in role_hierarchy
anything else the gate's own definitions (decide() answers null)

The middleware

  • Auth\Middleware\SecondFactorPending, appended to the web group and placed before Laravel's AuthenticatesRequests middleware: a visitor between two factors is redirected to second_factor.form_route from any route but Settings::openRoutes() (the page, its check, the sign-out and open_routes).
  • Auth\Middleware\EnrolmentPrompt, appended with omnibase's sign-in effects (Auth\OmnibaseEffects::on()): a plain page asked by a signed-in account that omnibase's rule (Base\Auth\EnrolmentPrompt) sends to its second factor's enrolment is redirected to user_settings_enrolment; nothing without the rule, the policy or the route.

Proposer une modification docs/omniauth.md

Maps, shields, static export

Maps: omnimap

Where omnisense/omnimap is installed (suggested), Map\MapServiceProvider reads config/omnimap.php (not published: the application writes it) and wires nothing unless it names a provider:

// config/omnimap.php
return [
    'provider' => 'openstreetmap',
    'secret' => env('MAP_SECRET'),
    'providers' => ['openstreetmap' => ['user_agent' => 'MySite/1.0 (+https://example.org)']],
    'cache' => ['storage' => storage_path('app/omnimap'), 'directory' => 'omnimap'],
    'routes' => ['tile' => ['path' => '/omnimap/{signature}/{id}.webp', 'name' => 'omnimap_tile']],
];
Key Default Effect
provider '' (nothing wired) the page's provider, a key of providers
secret '' signs the pictures' names
providers.<name> - the provider's options, its factory's; factory names another factory than the key
geocoder the page's provider the provider Omnimap\GeocoderInterface is
cache.drive none an omnidrive drive of the bound Omnidrive\Drives (a storage's name)
cache.storage storage/app/omnimap a directory (DirectoryStorage), or the container's binding of an Omnimap\Snapshot\Storage
cache.directory, .pattern, .format omnimap, {signature}/image-{id}, png omnimap's SnapshotStore
cache.make auto the server makes a missing picture: auto when the provider makes static maps, true, false (a host reading another host's cache read only)
cache.size, .scale, .tilesize 1512x938, 1, 512 the picture made, its tiles (0: not cut)
cache.pool none: omnimap's files in storage/framework/cache/omnimap a store of Laravel's cache (config/cache.php's stores): the providers' cache (tiles fetched, geocoder answers)
cache.max_age 86400 a tile's max-age, in seconds
cache.public_directory none a directory (the public folder) where each tile served is written at the route's path, for the web server to serve it next
cache.public_size none the tile served as WebP fitted within this size, in pixels
html [] omnimap's MapHtml options (class, suffix, missing); src is the tile route's path
cache_only true a visitor never gets the live map
control ROLE_ADMIN who may change the map (the Access contract)
routes.tile {path: /omnimap/{signature}/{id}.webp, name: omnimap_tile, register: true} the tile's route; register false when the web server serves the tiles
routes.metadata, .export, .suppress /omnimap/{signature}, .../export, .../suppress, names omnimap_<name> named for the buttons only: not routed here
Binding What it is
Omnimap\Registry omnimap/google's, omnimap/openstreetmap's and omnimap/maplibre's factories when installed (MapServiceProvider::FACTORIES), and every binding tagged omnimap.provider_factory (MapServiceProvider::FACTORY_TAG), over the HTTP client bound as omnimap.http_client (else Symfony's HttpClient::create()), a PSR-18 client when nyholm/psr7 or php-http/discovery is installed, the PSR-16 cache of cache.pool
Omnimap\GeocoderInterface (bind) the registry's geocoder named by geocoder, the page's provider by default
Omnimap\Snapshot\Snapshots the page's renderer, the signer, the store, the static map per cache.make
Omnimap\MapRendererInterface (bind) the snapshots' renderer
Omnimap\MapPrinter a picture that could not be made logged as a warning
Omnibridge\Twig\MapExtension omnimap(), omnimap_export(), omnimap_suppress(), omnimap_signature() in every page's Twig (Pages)

The tile route (Map\TileController::tile, signature [A-Za-z0-9_-]{1,128}, id digits), as omnibridge/symfony's controller serves it: at a path ending in .webp, the tile as WebP (fitted within cache.public_size; GD with WebP, else Imagick, else as kept in the store's format), written in cache.public_directory at the route's path when one is set, public, max-age cache.max_age, must-revalidate, an ETag; 404 "No such map tile." when none. Capturing and deleting a picture is omnibridge/symfony's.

Shields: omnishield

Where omnisense/omnishield is installed, Shield\ShieldServiceProvider reads config/omnishield.php (php artisan vendor:publish --tag=omnishield-config, offered only once a gateway is named and ALTCHA's script is served), the keys of the Symfony bundle's omnishield.yaml, and wires nothing without a gateway:

Key Default Effect
gateways.<name> [] {factory, options}; the factories made here are those needing no HTTP client: altcha, the disposable domains, the tests' fixed one
challenge.gateway null: the first gateway the captcha a form asks by default (ShieldServiceProvider::defaultChallenge())
challenge.unreachable reject whether a captcha that does not answer lets a form through (accept), or refuses it, as the bundle's default
serve_scripts true ALTCHA's widget served by the application (route omnishield_altcha_script, cached a year), the gateways' default script then
replay.pool null: in memory a PSR-6 pool's binding keeping the tokens already used

Bindings: Omnishield\Replay\ReplayStoreInterface, Omnishield\Registry, Omnishield\WidgetPrinter (scoped: one per request, as the bundle resets its own - a long-running worker such as Octane forgets it between two requests, so each page prints a provider's script). With omnibase's sign-in effects, the captcha after failed sign-ins uses it (Signing in).

Static export: omnibridge/static

Where omnibridge/static is installed, OmnibridgeServiceProvider registers the artisan command omnibridge:static:build: omnibridge/static's own command line (Console\Cli), handed a renderer through the application's HTTP kernel (each page a visitor's GET, at --render-origin, by default app.url), the container, public/ and, with --actions, the routes the actions of those folders declare (Routing\ActionRoutes).

php artisan omnibridge:static:build static.php --out=storage/static --actions='App\Action\=src/Action'

Run on lesringostars-laravel (2026-10-24, the working copy loaded by hand): the twin's home, intro, destinations, two articles and the travels pages rendered by Laravel and written, the routes read from lesringostars' actions.

Argument or option Default Effect
site static.php relative to base_path() unless absolute
--out storage/static relative to base_path() unless absolute
--render-origin app.url, else http://localhost the origin the pages are rendered at
--actions=Namespace\=folder none, repeatable the routes the actions of those folders declare (Routing\ActionRoutes, Laravel's environment by Symfony's name), handed as the offer's routes
--verify, --strict, --quiet-progress off omnibridge/static's --verify, --strict, --quiet

Each page goes through the HTTP kernel as Illuminate\Http\Request::createFromBase() of the renderer's request; the offer also holds the container and public_path(). An invalid argument prints its message and exits with INVALID (2). omnibridge/static's manual has the site file and the export.

Proposer une modification docs/families.md

Configuration

config/omnibridge.php (php artisan vendor:publish --tag=omnibridge-config), merged under the key omnibridge by the provider: the published file holds every key below with its default.

Key Type Default Read by Effect
locales.available list<string> [] AppLocales, Http\Localize, Http\SwitchLocale the site's languages in omnibase's form (fr-FR, en-GB)
locales.default ?string null AppLocales, Http\Localize the default language; null: the first available (AppLocales)
translations.paths list<string> [] CatalogueTranslations the ICU catalogues' folders, the later over the earlier (the core's, the bundles', the site's last)
translations.fallback ?string null: app.fallback_locale, else en CatalogueTranslations the language of a missing message
assets.manifest ?string null RouterUrls::asset() a build's manifest.json
twig.paths list<array{string, ?string}> [] View\Twig [folder, namespace] pairs, null for the main namespace
twig.cache ?string null: storage/framework/twig View\Twig Twig's cache folder
twig.timezone ?string null: app.timezone HelperExtension the zone |datetime prints in
doctrine.connection ?string null Doctrine\DBAL\Connection, the settings, the screens' site a connection of config/database.php; none: Doctrine asked is a LogicException, no settings read
doctrine.entities list<string> [] the entity manager the entities' folders
doctrine.omnibase bool|array false the entity manager omnibase's own mapping: true, or Mapping::register()'s options (project_dir, environment, cache_dir, parameters, types, enumerations, attribute_paths, resolve_target_entities, entity_listeners) (Doctrine and images)
parameters.hot ?bool null: the config's base.parameter_bag.use_hot_bag HotParameters the database settings laid over the config (Parameters)
theme.modes array [] Page\Theme, ScreenContext, MenuTwigExtension id => {id, class, icon, prefers}; no colour scheme without modes
theme.filters, theme.no_filter array [] Page\Theme the filters, the one for no filter
theme.layouts ?string not in the published file Page\Theme::getLayout() the folder of the site's layoutN.html.twig (app.theme.layout(1))
storages array<string, string> [] DiskFiles, Media\DiskStorages, Omnidrive\Drives omnibase's storage name => Laravel's disk; default for a storage not named
media bool|array false the provider omnibase's media rule: true, or {routes: bool (true), secret: ?string (app.key)} (Doctrine and images)
entity_namespaces array<string, string> [] EntityNouns a bundle's entity namespace and its prefix in the "entities" catalogues (Base\Marketplace\Entity\ => marketplace\)
auth bool false Auth\AuthServiceProvider the sign-in through omniauth (Signing in)
encore.builds list<array{entrypoints: string, manifest: string}> [] View\Encore the Encore builds, in the order they are looked into; no Encore tags without
encore.breakpoints array<string, string> [] View\Encore name => media query, a stylesheet's versions
encore.optional array<string, list<string>> [] View\Encore the entries a page loads with another
consent.config string|array|null null omnibase/consent's extension a YAML file with a consent: key (the Symfony site's consent.yaml), or the array
push.public_key, push.private_key, push.subject ?string null WebPushService the VAPID keys; no push without the public key
deploy.project_dir ?string null: base_path() DeployVersionExtension the folder whose git heads deploy_version() reads
icons.metadata ?string null: public/bundles/base/metadata IconTwigExtension the icons' metadata (icons.yml, bootstrap-icons.json)
screens.version ?string not in the published file Base\Screen\Negotiation the front end's version omnibridge/react's protocol compares

Read from Laravel's own configuration: app.locale, app.fallback_locale, app.debug, app.key, app.timezone, app.url (the static command), database.connections.*, filesystems.*, the whole base array (the parameters, omnibase's mapping, the media: base.images.*, base.obfuscator.*, base.uploader.storage, base.twig.editor.storage, base.router.use_fallbacks, base.router.host_fallbacks for the sign-in cookie's domain).

The other files: config/omniauth.php (Signing in), config/omnimap.php and config/omnishield.php (Maps, shields, static export).

Proposer une modification docs/configuration.md

Errors

The package has no exception class of its own: it throws PHP's and Symfony's, with messages that name the configuration key to set.

Exception Thrown by When
LogicException Doctrine\DBAL\Connection (resolved) No Doctrine connection: omnibridge.doctrine.connection names none of config/database.php.
InvalidArgumentException the same No database connection "<name>" (omnibridge.doctrine.connection).; Doctrine\Connections: No Doctrine driver for Laravel's "<driver>" connections.
LogicException Base\Bridge\Doctrine\Mapping (resolved) omnibase's Doctrine mapping is off: omnibridge.doctrine.omnibase.
LogicException a repository class resolved No mapped entity names <class> as its repository (repositoryClass).
LogicException the media's parameters omnibase's media are off: omnibridge.media.
InvalidArgumentException (a PSR-11 NotFoundExceptionInterface) Media\DiskStorages::get() No storage "<id>": omnibridge.storages names no disk for it, and config/filesystems.php has no disk of that name.
LogicException Media\ImageController a route that is no image route
NotFoundHttpException Base\Service\ImageRoute, Map\TileController, Screen\Responder::respond() an image that does not resolve, a missing tile (No such map tile.), an action that answered null (404)
RuntimeException Page\Theme::getLayout() No layout: omnibridge.theme.layouts names no folder of layoutN.html.twig.
LogicException Container\Aliases::of() #[AsAlias] on "<class>" names no interface, and the class implements <n>.; ... names no interface nor class.
InvalidArgumentException Routing\ActionRoutes::action() The route "<path>" names no action.; ... no class "<class>".
LogicException Routing\ActionRoutes::load() The route of "<class>::<method>" has no prefix for "<locale>".
LogicException Http\Localize the next middleware gave no response
LogicException Mail\LaravelMailer::send() The mailer "<name>" gives no Symfony transport.
LogicException View\OmnibaseExtensions an optional class it names is not installed
BadMethodCallException Form\FormView writing into a form view (read only)
LogicException Auth\AuthServiceProvider (boot) config/omniauth.php turns "<key>" on, which needs <package>: composer require <package>.
LogicException Omniauth\Contract\Throttle, Auth\RememberMe resolved The throttle is off (config/omniauth.php: throttle)., The remember-me is off (config/omniauth.php: remember_me).
LogicException the Sender with omnibase's stores omnibase's NotifierSender needs Base\Notifier\Notifier or omnibase 4.x's SignInMails: bind one, or a Sender of your own
Omnibridge\Exception\MissingContractException Runtime a contract the container does not bind (Base\Contract\Media with the media off)
Laravel's RouteNotFoundException / InvalidArgumentException RouterUrls::path() a route Laravel does not have and no elsewhere()

A refused sign-in is no exception the application sees: Auth\Controllers\SignIn keeps an Auth\Refusal (its message key and data, omniauth's failure and reason) in the session for the page that follows, or answers a passkey's JSON with {"status": "failed", ...}.

Proposer une modification docs/errors.md

Testing

Testing an application

Under Laravel's TestCase (or Orchestra Testbench for a package), the provider boots with the application: Runtime is the application's container, and each test's new application replaces the previous one. Date::setTestNow() and $this->travel() move Runtime::clock(). The sign-in is tested as a visitor does it ($this->post('/login', [...]), then the second factor's page), with the Tokens contract's token (app(Tokens::class)->csrf('authenticate'), the session's token). An application that runs on both hosts compares their pages (lesringostars' parity suite: docker compose run --rm parity in lesringostars-laravel).

The package's suite

docker compose run --rm test

The service test: the image omnibridge-laravel:php8.4, the package at /package, a named volume composer-cache; it runs composer update --ignore-platform-req='ext-*' (omnisense/omnibase asks for extensions nothing here loads) then vendor/bin/phpunit. PHPUnit on Orchestra Testbench (Laravel 13), PHP 8.4, omnisense/omnibase 4.x. With the checkout's vendor/ installed, with no network:

docker run --rm --network none -v "$PWD":/package omnibridge-laravel:php8.4 vendor/bin/phpunit --do-not-cache-result
Test class What it checks
ContractsTest Runtime and the container give the same objects; an extension of a contract is what the registry gives; the clock is Laravel's dates; an event goes to Laravel's listeners and comes back; the environment is Laravel's; Access is the gate and the visitor the guard's user; a session ends for the user signed in only; a token is the session's; routes say their pattern and which an address leads to; nouns read the entities' catalogues; files are the disks
UrlsTranslationsTest a route is Laravel's and an unknown one answered elsewhere; a public file read in the manifest; Symfony's generator interface over the contract; a message in the page's language; whether the catalogues hold a key; the later folder wins; Twig renders the contract; the locales in omnibase's form
ParametersTest the config read by its dotted path; a false setting wins over the config's true; without the overlay the config alone; the config itself turns the overlay on; a bag laid over all; a database that does not answer leaves the config; set writes the config or the bag
DoctrinePageTest Doctrine opens Laravel's connection; the entity manager names tables as omnibase does; the site read in its settings; Encore tags and the breakpoints; a simple form in omnibase's markup
OmnibaseMappingTest the entity manager is omnibase's; the application's own class wins; a class the application lacks is the core's by alias; the metadata are Symfony's (recorded from omnibase's Symfony harness); off by default
RepositoriesTest a repository is the entity manager's own; one no entity names is refused
EntitiesHostTest the attribute reader reads the files through omnibase's Flysystem; the cache pool is one and a repository is given it by type; BaseTrait's MediaService is the bound one; the editor's pictures are omnibase's image routes
MediaTest the media off by default; Media bound to MediaService; the image routes declared under their Symfony names; a thumbnail made, read back and served; an image that does not resolve is not found; Pictures make thumbnails
DrivesTest the disks are the drives by the storages' names; omnibase's Flysystem reads the same drives
ScreensTest Runtime gives the screens' contracts; a picture is its address or the build's asset; a stored picture where the application serves it; an Editor.js text rendered as |wysiwyg renders it; a picture of a text where the application serves it; an older text is Quill's HTML, its headings anchored; the frame is the request's locale, site, links, viewer and theme; a flash read once; a core screen built on Laravel without the application's code
ResponderTest a template is its markup, or its data for a client preferring JSON; JSON data have their templates rendered and are encoded as Symfony's JsonResponse; no result is no page, a file sent as it is; an application renders each kind its way
ActionRoutesTest the routes are Symfony's in Symfony's order; an address answers with the same route as on Symfony; an environment's routes are there; an action posted to gets its body and checks its own token; a name read as omnibase's router reads it; the aliases are the classes' attributes
LocaleTest the page's language is the visitor's choice, else the browser's, else the default; the language shown remembered for the next page; the language menu keeps the choice and goes back
PagePiecesTest the layouts are those of the folder; the request's attributes are its route's; a template's companions added; the filters are omnibase's; a redirect answered
OmnibaseTwigTest the container's environment has omnibase's extensions; an application's own environment extended alike; the cookie answer on the site's configuration (skipped without omnibase/consent); the forms by their views; a record's back-office page where the routes are answered
TwigAccessTest the page's functions are the contracts; the theme is omnibase's Themizer
AuthTest, AuthOffTest, PasswordHasherTest, CookieDomainTest every way of signing in, the second factor, refusals, throttle, remember-me, trusted devices, TOTP sealed at rest, a simulated provider; nothing of Auth\ without omnibridge.auth; the hashers; the sign-in cookie's domain
MapTest a page has its map and the tiles are served (OpenStreetMap's tiles answered in the test; GD in the image)
ShieldMailTest a MIME e-mail sent by Laravel's mailer; the gateways of config/omnishield.php and ALTCHA's script; XLIFF catalogues read; markup given whole printed with the rest of the form

Proposer une modification docs/testing.md

Verified, and not

What the tests prove

Run on 2026-10-27 in omnibridge-laravel:php8.4 (PHP 8.4.26, PHPUnit 12.5.40, Laravel 13.38, omnisense/omnibase 4.x-dev), the checkout's own vendor/, no network: 114 tests, 736 assertions, green, 1 skipped (OmnibaseTwigTest::testTheCookieAnswerOnTheSitesConfiguration: omnibase/consent not installed there). The Installation page's script was run in the same image that day and its output pasted.

Verified elsewhere, as the README and the commits state

  • lesringostars' Laravel twin, which renders the Symfony site's blog and articles from the same database and templates, and its parity suite against the Symfony site.
  • omnibase's mapping: the metadata of 94 classes equal to Symfony's (OmnibaseMappingTest, the records from omnisense/omnibase's Symfony harness).
  • omnibridge:static:build on lesringostars-laravel (2026-10-24, the working copy loaded by hand): the twin's home, intro, destinations, two articles and the travels pages rendered by Laravel and written, the routes read from lesringostars' actions; not a whole site.

What is not verified

  • Laravel 12: composer.json admits it, the suite runs on Laravel 13.
  • A real identity provider, a real passkey authenticator: simulated in the tests.
  • omnimap providers other than OpenStreetMap drawn through this wiring (MapLibre and a tagged factory are resolved as renderers in MapTest, not drawn); the map's export and suppression (omnibridge/symfony's).
  • The bindings under a long-running worker (Octane, FrankenPHP's worker mode): singletons such as RouterUrls' manifest and the Twig environment live for the worker. omnishield's WidgetPrinter is scoped, and ShieldMailTest forgets the scoped instances as Octane does between two requests; no Octane worker was run.
  • PostgreSQL and SQL Server connections: Doctrine\Connections maps them, the suite uses SQLite.

Proposer une modification docs/verified.md