Skip to content

Integration Points ​

Some things a business application needs, the SDK deliberately does not own: your login screen, the address-validation service you pay for, the spreadsheet library that writes .xlsx. Each of those is a place where your installation meets something the SDK cannot ship — a vendor you licensed, a screen you designed, a table you already had.

The SDK handles all of them the same way, and the shape is worth learning once:

Own a concrete vocabulary. Declare a port. Let the host bind the concrete. No third-party library ever appears in an SDK signature.

What that buys you ​

A port is an interface plus a DTO the SDK owns. LoginProviderInterface speaks LoginRecord, never your User model. AddressProviderInterface returns NormalizedAddress, never a Google Place. ExportWriterInterface consumes ExportableInterface, never a spreadsheet Worksheet.

Three consequences follow, and they are the reason the idiom is worth the extra interface:

  • You can swap the vendor without touching the SDK. Google today, a national register tomorrow; the adapter changes, nothing else does.
  • The SDK's composer.json stays small. A licensed SDK, an API key, a 1 MB spreadsheet library — none of them is a dependency of laravelui5/sdk, because none of them is reachable from an SDK type.
  • Your code never learns the vendor's shape. Everything downstream of the seam reads the SDK's DTO, so a vendor swap is invisible past the adapter.

Bought, never owned. The library, if there is one, lives behind the writer.

The ports ​

PortShips a default?What you bindPage
LoginProviderInterfaceYes, a working oneAn adapter over your auth storeLogin
AddressProviderInterfaceNo — throws until boundYour licensed validation vendorAddresses
AddressFormatterInterfaceYes, good-enoughA country-correct formatter, per countryAddresses
ExportWriterInterfaceYes, for CSVA writer per additional formatExport
TenantResolverInterfaceYes, a single-tenant oneYour operator identityTenancy

Two more host-extension surfaces work by the same instinct but are documented with their own domains: Customizing (you contribute catalog rows in code) and Connecting your user model (you contribute the auth()->user() → Partner mapping).

The three default stances ​

Not every port can have a default, and the SDK is deliberate about which stance each one takes. Read the stance before you write an adapter — it tells you whether you are replacing something or supplying something that is missing.

A working default. The SDK binds a real implementation and a stock install works with no wiring at all. Login is the example: EloquentLoginProvider reads your users table over the users.partner_id bridge. Bound with bindIf, so your own binding always wins:

php
// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(LoginProviderInterface::class, LdapLoginProvider::class);
}

A good-enough default plus a registry. The SDK ships something honest but unambitious, and you add better ones alongside it rather than replacing it. Address formatting works this way: the DefaultAddressFormatter renders a sane Western line order for any country, and you register a country-correct formatter that claims only the countries it knows.

php
public function boot(): void
{
    $this->app->make(AddressFormatterRegistry::class)->register(new AustrianAddressFormatter());
}

No default at all. Some things the SDK cannot guess and must not fake. AddressProviderInterface is bound to a closure that throws NoAddressProviderBoundException with a message that says what to do. Resolving it without binding is a loud, immediate failure — never a silent wrong answer.

That last stance is also the opt-out idiom, and you can use it yourself. A host that manages logins entirely outside the SDK binds the port to a closure that throws, so the Partners app's login buttons fail with a domain message instead of writing to a users table that is not the real one:

php
$this->app->bind(LoginProviderInterface::class, static function () {
    throw new NoLoginProviderBoundException();
});

Where the binding goes ​

All of it is ordinary Laravel. Bindings belong in your own service provider's register(); registry calls that need other services belong in boot(). The SDK never scans for adapters and never auto-discovers them — if it is not bound, it is not there.

One port is the exception, because it is reached by a route rather than by a type: the export endpoint needs ExportArtifactResolver in your config/ui5.php resolver chain. See Configuration.