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

Lander Tracking House Bundle Laravel Package

exs/lander-tracking-house-bundle

Symfony bundle that detects tracking parameters in incoming requests, stores them in cookies, and appends them to any URL through Twig filters such as appendTracking and getTracking. Includes built-in extractors for cmp, cup, cu, exid, and visit parameters, with configurable default values.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check. exs/lander-tracking-house-bundle is a Symfony bundle — it registers via AppKernel::registerBundles(), reads YAML config under exs_lander_tracking_house, and ships a Twig extension. It will not install or run in Laravel as-is. Do not composer require it into a Laravel app expecting it to work. Treat it as a reference implementation of a ~150-line pattern you re-implement.

Where to look first (in this order):

  1. Service/TrackingParameterManager/ — the three extractor interfaces (TrackingParameterQueryExtracterInterface, TrackingParameterCookieExtracterInterface, TrackingParameterInitializerInterface) and TrackingParameterFormatterInterface. This is the entire mental model.
  2. The builtin extractors (CmpTrackingParameterExtracter, ExidTrackingParameterExtracter, CuTrackingParameterExtracter, etc.) — they show exactly how cup/cu/cuv compound strings are split.
  3. The Twig extension/appendTracking filter — it shows how formatters are keyed by name and how {cmp}/{exid}/{visit} placeholders are substituted.
  4. The DI compiler pass — it shows how tagged services (exs_tracking.parameter_extracter, exs_tracking.parameter_formatter) get collected.

The core flow to replicate:

Request ──▶ Extractors (query / cookie / initializer)
              │
              ▼
        merged ParameterBag  ──▶ written to cookies (one per returned key)
              │
              ▼
        Formatters (name-keyed)  ──▶ appendTracking(url, formatterName)
              │
              ▼
        Outbound URL with query params + {placeholder} substitution

First use case: a landing page that receives ?cup=cmp1~exid42~99, persists cmp/exid/product_id to cookies, and re-appends cu=cmp1~exid42 to links going to your app.

Minimal Laravel shape to build:

// A single value object that holds the tracking bag
class TrackingParameters extends \Illuminate\Support\Collection {}

// Middleware: capture + persist
class CaptureTrackingParameters
{
    public function handle($request, Closure $next)
    {
        $bag = app(TrackingManager::class)->extract($request);
        app(TrackingManager::class)->persist($bag, response());  // cookies
        return $next($request); // read the bag back in views via a helper
    }
}

Once you have a TrackingManager with an extract(), persist(), and format() method, you have the whole bundle.

Implementation Patterns

1. Middleware for capture, a singleton for state. Symfony's bundle works because the extractors run once per request and the result is reused by the Twig filter. In Laravel, capture in middleware and bind the resulting bag as a scoped singleton so views/controllers can reuse it:

$this->app->scoped(TrackingParameters::class, fn () => new TrackingParameters());

2. Tagged bindings mirror the compiler pass. The bundle's extensibility comes from exs_tracking.parameter_extracter tags. In Laravel, use a config-driven list plus app()->tagged():

// config/tracking.php
'extracters' => [
    \App\Tracking\Extracters\CupExtracter::class,
    \App\Tracking\Extracters\ExidExtracter::class,
    \App\Tracking\Extracters\VisitExtracter::class,
],
'formatters' => [
    'cu'  => \App\Tracking\Formatters\CuFormatter::class,
    'cuv' => \App\Tracking\Formatters\CuvFormatter::class,
],

3. Compound-string extractors — copy them verbatim. These are the only genuinely non-obvious logic in the package. cup parses {cmp}~{exid}~{product_id}, cuv parses {cmp}~{exid}~{visit}, etc. Port the split-and-map logic directly, and make the separator configurable (~ by default) so you can adapt to affiliate networks that use _ or -:

private function parseCompound(string $raw, array $keys): array
{
    $parts = explode(config('tracking.separator', '~'), $raw);
    if (count($parts) !== count($keys)) {
        return []; // reject malformed rather than guessing
    }
    return array_combine($keys, array_map('trim', $parts));
}

4. Formatters as a keyed strategy. The appendTracking('foo') formatter name is just a lookup. In Laravel, a __call-style macro on UrlGenerator or a plain helper works:

Blade::directive('tracking', fn ($expr) => "<?php echo app(TrackingManager::class)->append($expr); ?>");
// or a fluent helper:
url()->to('/checkout')->appendTracking('cu');

5. Placeholder replacement is a second, independent feature. appendTracking on https://foo/bar?cmp={cmp}&exid={exid} is doing text substitution, not query-param assembly. Keep them separate — don't overload one method to do both. In Laravel use strtr() with a map, not str_replace(), so a param containing {cmp} isn't double-substituted.

6. Defaults via config, applied only at init. The bundle's initializer extractors fill cmp/exid/visit when nothing is present. Model this as a final "fill defaults" pass after query and cookie extraction — never as a formatter-time fallback, or you'll re-append defaults to URLs where the visitor never actually arrived with a campaign.

7. Cookie writing is the persistence layer. Every key returned by an extractor becomes a cookie with that name. In Laravel, batch this with Cookie::queue():

foreach ($bag as $name => $value) {
    Cookie::queue(cookie($name, $value, config('tracking.ttl'), '/', config('tracking.domain'), true, true, false, 'lax'));
}

Gotchas and Tips

The Laravel/Symfony mismatch is the gotcha. Everything else is small. Budget a half-day for the port, not a sprint. The bundle's README says appendParameter in the "Adding a formatter" example but appendTracking everywhere else — a real inconsistency in the source docs; don't trust the copy-paste examples.

Documented typo: the priority tag example reads priotiry: 100. If you're copying the YAML, that's how it's spelled in the README. Laravel's tagged() doesn't support priorities the same way, so encode ordering as array order in config.

Every returned key becomes a cookie. This is stated in the README ("All keys from the array returned...") and is easy to miss. If a custom extractor returns ['_token' => ...], you have just invented a cookie. Prefix your tracking cookies and validate names against an allowlist before writing.

Cookies are written without consent handling. The package has zero discussion of GDPR/CCPA/ePrivacy, SameSite, Secure, HttpOnly, or lifetime. You are inheriting an attribution mechanism that sets cookies on inbound requests. Before shipping in the EU/UK, gate persistence behind a consent check and set SameSite=Lax; Secure at minimum. This is the single biggest reason to port rather than depend.

Cookie collision with your app's own cookies. visit, cmp, exid are generic names. If any other part of your app uses a visit cookie, you'll silently clobber it. Namespace with trk_ or document the reserved names.

Extractor ordering matters. If cup and cu both arrive in the same request, whichever runs second can overwrite the first's cmp/exid. Define precedence explicitly (compound cup/cuvp first, then cu/cuv, then bare exid/visit), and short-circuit once a compound value is accepted.

Compound parsing rejects or silently truncates. explode('~', 'cmp~exid') for a cup (which expects three parts) yields a two-element array; if you array_combine with three keys it throws. Decide explicitly: reject the whole param, or fill missing with null and let initializers backfill. Don't let it 500 the landing page.

Placeholder replacement only fires for known tokens. {cmp}, {exid}, {visit} are the three the README documents. If a marketer writes {product_id} into a URL, it passes through literally. Use strtr() with a map of exactly the keys you support, and add a test asserting unknown {...} tokens survive untouched (or decide to strip them).

exid has aliases. The README says exid also reads u and uuid from the request, "first match" wins. That's undocumented precedence beyond a sentence. Pin it down in your port — query param order is not guaranteed.

Empty/whitespace compound values. cup=~exid~99 should not produce a cmp of empty string and a cookie set to "". Trim and null-check each segment; treat all-empty as no-op.

Debugging. In the Symfony original, dump the merged ParameterBag in the Twig filter to see what formatters receive. In your port, add a dedicated middleware that logs the extracted bag at debug level and inspect Set-Cookie headers in the response — most "tracking doesn't work" reports are either a cookie never set (extractor returned empty) or a cookie overwritten by a later extractor.

Extension points worth keeping. The three-interface split (query / cookie / initializer) is genuinely good design and worth preserving in your port — it's what lets you add a new network's params without touching the manager. Keep the formatter-as-strategy pattern too, because it's how marketing will ask you to "add one more param to links" without a code review of the URL-building logic.

When to escalate. If the ask grows to cross-device attribution, event-based tracking, or ad-platform integration, stop porting — the bundle never addressed those and a purpose-built tool (or a small internal CDP) is the correct answer. This package covers only the first-party, cookie-based, single-tab-journey case.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style