mpp/generali-client-bundle
A Symfony bundle that integrates the Generali client into your application, offering ready-made service configuration and convenient access to Generali APIs. Streamlines setup and keeps Generali integrations consistent across PHP projects.
Treat the bundle as a reference implementation, not an installable dependency. In a Laravel app, composer require mpp/generali-client-bundle leaves you with a *Bundle class that never runs, a Configuration.php tree that is never loaded, and Resources/config/services.yaml definitions that are never parsed.
First three files to open, in order:
Resources/config/services.yaml — the shortest path to the real API surface. It shows which underlying classes do the HTTP work, what they're aliased to, and what's injected into them. This tells you what to extract.DependencyInjection/Configuration.php — the config schema. This is your spec for what goes in config/generali.php: base URL, credentials, timeout, retry settings, environment toggles. Port these keys verbatim, do not rename them.composer.json — confirms which base client package you should require instead (mpp/generali-client or similar). That package, not the bundle, is what you actually install.Minimal viable first use case: a read-only call against the Generali sandbox. Pick one — a quote, a product list, a contract fetch — and prove you can authenticate and receive a hydrated response before writing any domain code. If the transport is SOAP (historically likely for Generali France), confirm sandbox credentials and WSDL access exists before you spend a day on glue.
// config/generali.php — translated from Configuration.php
return [
'base_uri' => env('GENERALI_BASE_URI'),
'credentials' => [
'key' => env('GENERALI_KEY'),
'secret' => env('GENERALI_SECRET'),
],
'timeout' => 30,
];
Then bind the extracted client in a GeneraliServiceProvider:
$this->app->singleton(GeneraliClient::class, fn ($app) =>
new GeneraliClient(config('generali'))
);
That is the entire "getting started" — the rest is the day-to-day patterns below.
Anti-corruption layer first, transport second. Before wiring anything, define the interface your domain will actually call. Everything downstream depends on this being framework-neutral.
interface InsuranceGateway
{
public function quote(QuoteRequest $req): Quote;
public function subscribe(Subscriber $sub): Subscription;
public function getContract(string $id): Contract;
}
Bind a GeneraliGateway adapter behind it. Your controllers, jobs, and models never see a Symfony type, a PSR-7 request, or a Generali DTO name. When you need a second carrier, you add an adapter — you don't touch the callers.
Config translation pattern. The bundle's Configuration.php node is nested and typically uses ->children()->scalarNode(...) style. Flatten it into a Laravel config file, keep key names identical, and validate with config() defaults rather than a Symfony config tree. A one-line Config::set() in a test overrides anything you'd otherwise mock.
Service-provider binding pattern. One provider, one file:
final class GeneraliServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(GeneraliTransport::class, fn ($app) => new GeneraliTransport(
http: $app->make(\GuzzleHttp\Client::class),
config: $app->make('config')->get('generali'),
));
$this->app->bind(InsuranceGateway::class, GeneraliGateway::class);
}
}
Keep the transport (HTTP/SoapClient) and the gateway (DTO mapping) as separate bindings. You will want to swap transport for testing without rewriting mapping.
Fake adapter for local dev and CI. Bind FakeInsuranceGateway when app()->environment('local', 'testing'). Return canned DTOs that match real sandbox responses byte-for-byte. This is the single highest-value piece of glue — it decouples your dev loop from Generali's sandbox availability, which will be flaky.
Queue all write operations. Never call subscribe() synchronously in a request path. Job → gateway → idempotency key → reconciliation. Idempotency keys must be stored before the call, so partial-success is always recoverable:
$key = (string) Str::uuid();
Cache::put("generali:subscribe:{$key}", 'pending', now()->addDay());
SubscribeToGenerali::dispatch($request, $key);
Shadow-read, then cut over writes. Ship reads first (contract lookups, product catalogue) where wrong answers are visible but harmless. Only enable write paths after a reconciliation job exists that can compare your local state to Generali's contract state on demand.
Contract tests against the sandbox, scheduled. A handful of tests — one per flow you actually use — hitting sandbox in a nightly CI job. This is the only thing that catches schema drift when the insurer silently renames a field.
vendor/mpp/generali-client-bundle/ will sit on disk doing nothing. If you're debugging "why isn't my service bound?", that's the answer. Delete the bundle dependency, require the base client.implements EventSubscriberInterface or #[AsCommand] autoconfiguration, those classes are inert in Laravel. Any event listeners or console commands the bundle ships must be re-registered manually or ignored.Symfony\Component\HttpKernel\Bundle\Bundle will leak. If you try to new the bundle manually to poke at services, you pull in a class hierarchy Laravel doesn't provide. Read the source; don't instantiate.config() + a secret manager; don't hardcode paths. Watch certificate expiry — these failures are silent until the first real call fails.xsd:string → xsd:date change upstream becomes a runtime exception or, worse, a silently wrong decimal. Wrap every call site in try/catch that logs the raw payload before hydration.Contract model from the bundle appears in a controller or Eloquent relation, you've coupled your domain to the carrier and made adding a second insurer a rewrite.composer.json, not a version constraint. There is no release cadence to rely on.mon-petit-placement upstream.LICENSE and composer.json before you copy a single file.How can I help you explore Laravel packages today?