Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Preventgo Bundle Laravel Package

mpp/preventgo-bundle

mpp/preventgo-bundle is a Symfony bundle that packages PreventGo services for quick installation and configuration in Symfony applications, wiring up the required services and settings so you can add its functionality with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations. mpp/preventgo-bundle is a Symfony bundle published by a single organisation (Mon Petit Placement), with 0 stars, 0 dependents, and a score of ~2.2. Laravel's package auto-discovery only understands Illuminate\Support\ServiceProvider; it will not register a Symfony bundle. So there is no php artisan vendor:publish moment here. Your job is to decide, in this order, whether you (a) talk to the PreventGo HTTP API directly, (b) reuse the bundle's PHP classes without the Symfony plumbing, or (c) skip it. In practice, (a) or (b) will be less work than (c).

What changed in v2.0.1 — and what didn't. The only shipped change is "fix: symfony 7 compatibility #3". That is a single merged PR, and it matters for exactly one reason: Laravel 11/12 pull in symfony/* v7 components, so a bundle pinned to Symfony 6 could previously fail composer require with an unsatisfiable constraint. That conflict is now gone, so you can install the package beside a modern Laravel app without --ignore-platform-reqs hacks. Nothing else changed: it is still a Symfony bundle, still not auto-discovered, still no Laravel service provider, facade, config stub, or Http::fake()-compatible transport. Note also that this is a 2.x line — the assessment below was written against 1.x, and the release notes tell you nothing about what broke in the 2.0 major. Treat the major bump as undocumented and diff it before trusting it.

Step 1 — install and inspect what you actually got.

composer require mpp/preventgo-bundle:2.0.1

Immediately open vendor/mpp/preventgo-bundle/composer.json. You are looking for four things:

  • "type": "symfony-bundle" — confirms the wiring problem.
  • "require" — does it pull symfony/http-client, symfony/serializer, symfony/event-dispatcher, symfony/cache? Each one is a bridging problem you'll have to solve in Laravel. Post-2.0.1 these will resolve to v7, which is what Laravel already ships — so the install is clean, but the runtime contracts are still Symfony's, not Laravel's.
  • "extra" — the bundle class name and any Symfony Flex recipe metadata.
  • The autoload PSR-4 root, so you know the namespace to import in your own code.

Then diff against the 1.x tag you last read, if you can: composer diff or a quick git clone of the repo. A one-line release note on a major version is a warning sign, not a reassurance.

Step 2 — read in this order (the bundle's own source, not the docs, which are likely thin):

  1. README.md — usually just an install snippet, but check for auth header names and the base URI.
  2. DependencyInjection/*Extension.php — this is the documentation. It enumerates every config key the bundle accepts (base_uri, api_key, timeout, sandbox flag, etc.) and shows how each service is constructed. This is the file you replicate in a Laravel provider.
  3. Resources/config/services.* — the service graph and constructor arguments.
  4. Client/, Api/, or Service/ — the actual HTTP call surface.
  5. Model/, Dto/, Response/ — what you get back.
  6. EventSubscriber/ or Event/ — whether the bundle dispatches Symfony events you'd need to bridge.

Step 3 — your first use case. The realistic day-to-day call is screening an entity at signup or at a scheduled re-screen: send an identity (name, DOB, country) and receive a verdict plus matched records. Model it as your interface first, so the package stays at arm's length:

// app/Contracts/Screener.php
namespace App\Contracts;

interface Screener
{
    public function screen(ScreeningRequest $request): ScreeningResult;
}

Then a Laravel provider that constructs the bundle's client by hand, mirroring the Extension file:

// config/preventgo.php  — keys copied from DependencyInjection/*Extension.php
return [
    'base_uri' => env('PREVENTGO_BASE_URI'),
    'api_key'  => env('PREVENTGO_API_KEY'),
    'timeout'  => env('PREVENTGO_TIMEOUT', 10),
];
// app/Providers/PreventGoServiceProvider.php
public function register(): void
{
    $this->app->singleton(Screener::class, function ($app) {
        $config = $app['config']['preventgo'];

        // No Symfony container, so build the client manually.
        // Verify the real constructor signature against the Extension class —
        // it may well have changed between 1.x and 2.x.
        return new PreventGoScreener(
            client: new \Mpp\PreventGo\Client\PreventGoClient(
                httpClient: \Symfony\Component\HttpClient\HttpClient::create([
                    'base_uri' => $config['base_uri'],
                    'timeout'  => $config['timeout'],
                ]),
                apiKey: $config['api_key'],
            ),
        );
    });
}

If the constructor is a mess of Symfony-only dependencies, stop and write a thin Illuminate\Support\Facades\Http client instead — you'll have consumed 30 minutes instead of three days.

Implementation Patterns

Always wrap, never call the vendor client from controllers. The adapter pattern above pays for itself the moment you want to swap to a raw HTTP implementation or a fake in tests.

Queue every screening call. These are third-party network calls with compliance consequences; they should never block a web request. A job gives you retries, backoff, and a natural place to persist the raw payload:

class ScreenApplicant implements ShouldQueue
{
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public int $applicantId) {}

    public function handle(Screener $screener): void
    {
        $applicant = Applicant::findOrFail($this->applicantId);

        $result = $screener->screen(ScreeningRequest::forApplicant($applicant));

        ScreeningRecord::create([
            'applicant_id' => $applicant->id,
            'status'       => $result->status(),     // clear / potential_match / blocked
            'score'        => $result->score(),
            'raw'          => $result->raw(),        // store verbatim — audit requirement
            'requested_at' => now(),
        ]);

        if ($result->requiresReview()) {
            event(new ScreeningRequiresManualReview($applicant, $result));
        }
    }

    public function failed(Throwable $e): void
    {
        // Fail *closed* for compliance: flag, don't silently pass.
        Applicant::find($this->applicantId)?->update(['screening_state' => 'errored']);
    }
}

Decide fail-open vs fail-closed explicitly and document it. For AML/KYC-style screening, an unreachable API usually means "hold for manual review," not "approve." Encode this in the job's failed() method and in a screening_state column, not in a try/catch you'll forget.

Persist the raw response and the request you sent. Regulators and support both ask "what did we send on 3 March?" Store the serialised request and response JSON, plus the API's correlation/request ID if it returns one. This also makes debugging possible without re-running calls against production data.

Use an idempotency/correlation key if the API supports one — pass your own ScreeningRecord UUID so a retried job doesn't create duplicate screenings or double-bill.

Bridge events with an adapter, not a rewrite. If the bundle dispatches via symfony/event-dispatcher, don't try to make Laravel's dispatcher pretend to be one. Register a small subscriber that maps Symfony events onto Laravel events:

$dispatcher->addListener(ScreeningCompleted::class, function ($event) {
    event(new \App\Events\ScreeningCompleted($event->getRecordId(), $event->getStatus()));
});

Call this from a booted provider once you've constructed the bundle's dispatcher — but only if you need it. For a first pass, poll/return the result from the call itself and skip the event layer entirely.

Per-tenant credentials via a factory. If you screen for multiple entities, don't singleton the client on one API key; bind a ScreenerFactory that caches clients keyed by tenant, reading from a preventgo_credentials table rather than config/.

Testing. Because the client almost certainly uses symfony/http-client or Guzzle rather than Laravel's HTTP client, Http::fake() will not intercept it. The Symfony 7 compatibility fix does not change this — a Symfony HTTP client is still a Symfony HTTP client. Fake at your boundary instead:

$this->instance(Screener::class, new class implements Screener {
    public function screen(ScreeningRequest $r): ScreeningResult {
        return ScreeningResult::potentialMatch(score: 87, raw: []);
    }
});

Then write exactly one integration test against a sandbox environment, tagged @group external, excluded from CI.

Gotchas and Tips

The container tags and compiler passes will never run. Symfony bundles register services via CompilerPass and tag-based autoconfiguration (e.g. kernel.event_subscriber). Laravel has no equivalent phase. Anything the bundle wires "magically" — event subscribers, HTTP client decorators, serializer normalizers — is silently absent. Read services.* and re-register the pieces you need by hand. Nothing in 2.0.1 touches this; a compatibility fix for Symfony 7 makes the bundle more Symfony-native, not less.

%env(VAR)% and parameter bag resolution do not exist in Laravel. Symfony's config resolves %env(PREVENTGO_API_KEY)% at container compile time; Laravel's config() returns the literal string. If you copy a Symfony config file verbatim you will send the string "%env(PREVENTGO_API_KEY)%" as your API key and get a 401 that makes no sense. Always resolve with env() in config/*.php.

Serialization is the usual landmine. If the bundle depends on symfony/serializer with PHP attributes for DTO hydration, you inherit a large dependency tree, and hydration errors surface as opaque NotNormalizableValueExceptions. Check composer show mpp/preventgo-bundle --tree before committing. Symfony 7 tightened serializer deprecations (the attribute-based Context/SerializedName handling and named-argument constructor behaviour), so if you were relying on 6.x hydration quirks, that's the most likely place a 2.x regression lands — and it will land as a runtime error on an edge-case payload, not a type error you'd catch in review. If serializer is in there and you only need two endpoints, a hand-rolled array response is cleaner.

PSR-3 logging works; PSR-6/PSR-16 caching usually doesn't. Laravel's logger() returns something implementing Psr\Log\LoggerInterface, so inject it directly. But the bundle's cache dependency will typehint Psr\SimpleCache\CacheInterface or symfony/cache contracts, which Laravel's cache repository doesn't satisfy — wrap with Symfony\Component\Cache\Adapter\Psr16Adapter or don't pass a cache at all if it's optional. (Symfony 7 dropped symfony/cache's legacy AdapterInterface; if v2.0.1 bumped cache to v7, check which interface the constructor actually wants.)

No profiler, no dump(). Symfony's web debug toolbar isn't there. When debugging an auth or 4xx issue, pass the underlying HTTP client a LoggerInterface and log at debug level, or temporarily set 'max_redirects' => 0 and dump the response body. The single most useful debugging move is logging the fully-built request (URL, headers minus the secret, body).

Pin the version exactly — more so now. 0 stars, 0 dependents, a single maintaining org, a major version whose release notes you were never given, and a 2.0.1 patch that is one merged issue. Use "mpp/preventgo-bundle": "2.0.1" rather than ^2.0, and keep a composer.patches entry or a vendored fork ready. ^2.0 today means "whatever the next 2.x does to a Symfony 8 upgrade." If you need a fix, a decorated class in your own namespace is preferable to a fork; a fork is preferable to waiting.

A green install is not an endorsement. The 2.0.1 fix means composer require succeeds on a current Laravel app — resist reading that as "it works with Laravel." Composer will happily install a bundle whose service definitions, compiler passes, and event tags are dead weight. Validate by calling an endpoint in tinker before you tell a colleague it's supported.

Set explicit timeouts and retries on the HTTP client, not the job alone. A 30-second default connect timeout inside a queued job with 3 tries is a 90-second worst case per applicant — fine. Inside a synchronous controller, it's an outage. Also confirm whether the API is idempotent before enabling automatic retries; if it isn't, your $tries = 3 can create duplicate screening records and duplicate charges.

Data residency and GDPR. Screening payloads contain names and dates of birth — personal data leaving your jurisdiction. Confirm the base URI's region, whether the vendor offers an EU endpoint, and check your DPA covers this subprocessor. This is the kind of thing that gets discovered in an audit, not in code review.

Decide early whether you need the bundle at all. Given the score and adoption signals, the honest evaluation is unchanged by 2.0.1: read the Extension file to learn the config shape, read the Client class to learn the endpoints, then implement 80 lines against Http::withToken()->post(). You keep Http::fake() for tests, Laravel's retry and timeout semantics, and no Symfony dependency tree. Take the bundle only if it contains non-trivial logic — pagination, signature verification, response normalisation across many list types — that you'd otherwise rewrite.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core