Skip to content

The Core seam

The Core seam is the membrane between Core and the SDK. Core is stateless: it knows about artifacts, routing, and slots, and it runs perfectly well on its own. The SDK is stateful: it adds tenant, actor, abilities, and time. The seam is where the two meet; its classes live under LaravelUi5\Sdk\Platform.

One sentence fixes the surface. The SDK plugs into Core at a fixed set of extension points and enriches the loaded registry and runtime context with state — it never writes state back into Core. The dependency arrow is SDK → Core, always.

The two registries

The SDK ships two registry implementations, and choosing between them is the host's call, made once in config('ui5.registry'):

RegistryWhenHow it works
SdkRegistrydevelopmentExtends Core's Ui5Registry; discovers roles, abilities, customizing catalogs, the Concept/Weave graph, value helps, and intents live on each request via reflection. Zero cache-staleness; pays the reflection cost every request.
CachedRegistryproductionReads the precompiled bootstrap/cache/ui5.php. Maximum performance, no reflection. Built by ui5:cache from SdkRegistry::exportToCache().
php
// config/ui5.php — development
'registry' => \LaravelUi5\Sdk\Platform\SdkRegistry::class,

// production: swap to the cached twin and run `php artisan ui5:cache`
'registry' => \LaravelUi5\Sdk\Platform\CachedRegistry::class,

This is a deliberate pair, not duplication waiting to be deduped. Both are public contracts: a consumer consciously binds one. Their cost is that the export and read shapes must stay in lock-step — and cold-boot parity is a hard requirement: the live and cached registries must return the same runtime-visible surface.

How the SDK plugs into Core

The seam with Core is exact and sanctioned. Core's Ui5Registry declares a protected afterLoad(array $config) hook and calls it after loading. SdkRegistry overrides it to run its enrichment passes, reading Core's protected registry state ($modules, $artifacts, $slots, …). That hook plus those protected members are the Ui5RegistrySdkRegistry contract. It is Stable (soft-freeze): changes are additive and announced. Enrichment layers over Core's loaded registry and never mutates it.

SdkContext is the runtime counterpart. Core's Ui5ContextInterface demands only artifact() + locale(); everything else on SdkContext is an SDK-owned public contract that providers and handlers depend on:

php
$context->actor();              // the acting Partner
$context->principal();          // the authenticated Partner (≠ actor under impersonation)
$context->tenant();             // the operating organization
$context->at();                 // the request timestamp (time-bound authz)
$context->abilities();          // the actor's AbilitySet
$context->setting('key');       // a scope-resolved setting of the artifact the request addresses (null if undeclared)
$context->appSetting('key');    // a scope-resolved setting of that artifact's app (null if undeclared)
$context->appSettings();        // every setting of the app, key => value
$context->primaryOrgPartner($at);
$context->orgPartners($at);

State flows Core → SDK and stops; nothing writes back into Core.

ui5:sync is mandatory

The registry's role, ability and customizing maps are sync-time projections: inputs for ui5:sync, not a source of authorization. Runtime authorization reads the database (AbilityGrantQuery over the synced sdk_* tables). Two consequences:

  • An un-synced install has no grants. ui5:sync is the act that moves metadata from the registry projection into the database that serves authz. Run it on every deploy. See Sync Pipeline.
  • The cache carries the runtime surface. exportToCache() exports modules, artifacts (with their class and module indexes), settings, intents, valueHelps, readAbilities, contributedScopes, the Concept/Weave graph (concepts, inboundEntries, outboundEntries) and slots. ui5:cache then adds one database-derived map, abilities (namespace → type → ability → synced id), which is why it runs after ui5:sync. The sync-only role and customizing maps are deliberately absent.

One context, two binding sites

SdkContext is built exactly once per request by SdkUi5ContextFactory::build() (the host names it as context_factory in config/ui5.php) and is a final readonly object — no setters, no re-resolve. It reaches the request on two paths:

  • UI5 routes — Core's ResolveUi5Context middleware calls the factory.
  • OData routes — the OData chain has no context-resolution step, so BindSdkContextForOData invokes the same factory. It is ordered afterResolveODataEndpoint (which binds the ODataServiceInterface) and afterEnsureODataAuthenticated (the factory throws without auth). Without this bridge, scoped OData queries fall through to 1 = 0.

Per-actor manifest extensions (suite apps)

An app's manifest.json can be finalized for the acting subject at request time. Core's ManifestController offers a narrow capability — Ui5ManifestExtensionInterface (extend(Ui5ContextInterface $context, array &$extensions): void) — and runs it on the manifest object it already resolves (getLaravelUiManifest()), mirroring its OData dataSources injection. Only the sap.ui5/extends/extensions node is exposed, never the whole manifest. Because the result is per-subject, manifests are served Cache-Control: no-store.

The SDK implements it on AbstractSdkManifest via the GatesViewVisibility trait: it rewrites the SAP-standard sap.ui.viewModifications block — flipping each role-gated control's visible from the actor's See grants (fail-closed) and stripping the server-only role. This is the delivery mechanism for See-type UI visibility (it replaces the context.json see snapshot). Core stays stateless: it offers the seam + the instanceof; all grant resolution lives in the SDK.

Suite membership is the opt-in. Extending AbstractSdkManifest is how an app joins the suite — it contributes the LeanShell fragment and gates view visibility per actor. A standalone app (e.g. a customer portal) extends Core's AbstractManifest and gets neither. The base class is the switch — a deliberate developer decision.

Public vs internal

Public (Stable, soft-freeze)Internal
SdkContext; the SdkRegistry / CachedRegistry pair; AbstractSdkManifest (the suite manifest base — shell fragment + the GatesViewVisibility manifest-extension seam); the manifest contracts (AbilityWriterInterface, AbilityNotificationInterface, Ui5ShellContributorInterface); SdkUi5ContextFactory, ShellContextArtifactResolver, BindSdkContextForODataManifestAbilityExtractor

The three context classes are public because a host names them in config/ui5.php.

See also