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, theappvariable, the forms, the screens' context. - Signing in:
Auth\andconfig/omniauth.phpkey by key. - Maps, shields, static export:
config/omnimap.php,config/omnishield.php,omnibridge:static:build. - Configuration:
config/omnibridge.phpkey 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(): mergesconfig/omnibridge.php, binds every contract and service (Contracts), Doctrine, Twig and the parameters; registersAuth\AuthServiceProviderwhen omnisense/omniauth is installed,Map\MapServiceProviderwhen omnisense/omnimap is,Shield\ShieldServiceProviderwhen 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; withomnibridge.mediaon, omnibase's media and their image routes; theomnibridge-configpublication;omnibridge:static:buildin 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
fris the site'sfr-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.
workbenchis Testbench's environment name. Runtime::parameters()is omnibase'sHotParametersover 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'sroute($name, $parameters, false)- the parameters that are null left out. A name Laravel does not have goes toelsewhere()'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.woff2is/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_timelineisapp_timeline.fron a French page, then the name itself, thenapp_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.item2ismenu.item2of themessagesdomain; the parameters by position ({0}) or by name ({privacy}), given both as{name}andname. - 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-icuone.translator()gives Symfony'sTranslator,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;
_controlleras FrameworkBundle names an action: the class for__invoke,Class::methodotherwise.
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 isname.<fallback>orname.default.<fallback>(a German page links to/japon/travels). Used byRouterUrls::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):
- a route of one language (
_locale) is that language's; - else the
LANGcookie's language when it is one ofomnibridge.locales.available; - else the browser's preferred language among them (
getPreferredLanguage()); - else
omnibridge.locales.default, else (null or empty) Laravel'sapp.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-asyncand-deferhalves, from the first build ofomnibridge.encore.buildsthat has any; a stylesheet comes with its breakpoint versions beside it (layout2-async-mobile.css, media fromomnibridge.encore.breakpoints); every script deferred; a file printed once per page;omnibridge.encore.optionalnames 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/ba--b,a_ba-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)andinject($html, $breakpoints): the style omnibase puts before the first stylesheet of the head, whosebody:beforenames the breakpoint in each media query.View\Companions::inject(Environment $twig, string $template, array $context, string $html, array $breakpoints = []): besideclient/timeline.html.twig,client/timeline.css.twigis the page's style (before</head>),client/timeline.mobile.css.twigits style for a breakpoint (<style media=...>),client/timeline.js.twigits 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'sstored()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 elseasset('assets/'.$image).Screen\WysiwygTexts(Base\Contract\Texts):|wysiwygand|wysiwyg_tocfor an Editor.js document (the mentions left as written, every leaf a string, the links' pass, the pictures byimages(), the headings' anchors, omnibase'sEditorJsRendererand@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, driveromniauth) keeps the account's handle in the session, reads the account back through omniauth'sAccountsat 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'ssignature_properties) and forgets it at a sign-out;attempt()asks omniauth the password.Auth\OmniauthUserProvider(driveromniauth) answers Laravel'sUserProviderover the same stores, the passwords checked by symfony/password-hasher as under Symfony (>72-byte passwords included).- The account is Laravel's
AuthenticatablethroughAuth\AccountUser, a wrapper the contracts unwrap:Omnibridge\Contract\Access::getUser(), the screens' viewer and Twig'sapp.usergive the application's own object, as under Symfony. Auth\Controllers\SignInis every way in: the password form (and its page, the site'ssecurity/login.html.twigwith 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 withenable_csrf);Auth\Controllers\Ceremonythe 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\Routesdeclares 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.routesfalse to declare them yourself.Auth\Middleware\SecondFactorPending(appended to the web group, beforeauth) keeps a visitor between two factors on the second factor's page; with omnibase's sign-in effects,Auth\Middleware\EnrolmentPromptsends a signed-in account to the second factor's enrolment page as omnibase'sSecurityEnrolmentSubscriberdoes 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\CookieDomainonbase.router.use_fallbacksandhost_fallbacks);Auth\Rolesanswers Security's attributes throughGate::before()-ROLE_*withomniauth.role_hierarchy,IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED,IS_AUTHENTICATED_2FA_IN_PROGRESS,PUBLIC_ACCESS- sois_granted()and the Access contract answer as under Symfony.- The cookies the Symfony site shares (
trusted_devicein scheb/2fa's format, the remember-me) are left out of Laravel's cookie encryption;omniauth.secretis the Symfony site'skernel.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'sAuthenticatesRequestsmiddleware: a visitor between two factors is redirected tosecond_factor.form_routefrom any route butSettings::openRoutes()(the page, its check, the sign-out andopen_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 touser_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:buildon 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'sWidgetPrinteris scoped, andShieldMailTestforgets the scoped instances as Octane does between two requests; no Octane worker was run. - PostgreSQL and SQL Server connections:
Doctrine\Connectionsmaps them, the suite uses SQLite.