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

Data Layer Bundle Laravel Package

lexik/data-layer-bundle

Lexik DataLayerBundle provides a flexible data layer abstraction for Symfony applications, letting you organize and query business data through configurable layers. Lightweight, easy to integrate, and maintained by Lexik.

View on GitHub
Deep Wiki
Context7

Getting Started

First read the actual repo, not the attached assessments. The three assessment files bundled with this task (assessment-pm.md, assessment-tpm.md, assessment-laravel_dev.md) describe a different package — a Laravel service-container aliasing helper for auto-wiring. They do not describe lexik/data-layer-bundle, which is a Symfony bundle for Google Tag Manager's dataLayer. Do not base API decisions on them. Treat the description and the (thin) release notes as the only reliable source.

Install (Composer, Symfony 3/4-era):

composer require lexik/data-layer-bundle

Register the bundle in app/AppKernel.php (or config/bundles.php on Symfony 4+ Flex):

new Lexik\Bundle\DataLayerBundle\LexikDataLayerBundle(),

The last release is from Feb 2018, so expect Symfony 3.x/4.0-era conventions (no services.yaml autoconfigure, likely YAML service defs, @lexik_data_layer service id style).

Where to look first:

  • The bundle class and its DependencyInjection/ — this tells you the service id and config namespace.
  • The DataLayer (or similarly named) service class — its add()/set()/all() methods are the whole API surface.
  • The Twig extension — this is what renders the JSON into your page, and the function name (lexik_data_layer_render() or similar) is what you'll call in templates.

First use case — page-level data layer push:

class ProductController extends Controller
{
    public function show($id, DataLayer $dataLayer)
    {
        $product = $this->getDoctrine()->getRepository(Product::class)->find($id);

        $dataLayer->add('ecommerce', [
            'detail' => [
                'products' => [[
                    'id'       => $product->getSku(),
                    'name'     => $product->getName(),
                    'price'    => $product->getPrice(),
                    'category' => $product->getCategory(),
                ]],
            ],
        ]);

        return $this->render('product/show.html.twig', ['product' => $product]);
    }
}
{# base.html.twig, in <head>, ABOVE the GTM snippet #}
{{ lexik_data_layer_render() }}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){...})(window,document,...);</script>

That's the value proposition: typed PHP assembly in the controller, one Twig call in the layout, valid JSON in the page.

Implementation Patterns

Inject into controllers/jobs/listeners, render once in the base layout. The service is request-scoped. Accumulate data anywhere in the request lifecycle (controller, event subscriber, Twig extension), then render a single dataLayer.push({...}) in the layout. This keeps push ordering deterministic — GTM requires data be pushed before the container snippet runs, so the render call must sit above it in <head>.

Request-scoped collection via a kernel event subscriber. Rather than scattering $dataLayer->add(...) across every controller, collect from a kernel.controller or kernel.response subscriber for cross-cutting concerns (user ID, page type, experiment bucket):

class DataLayerSubscriber implements EventSubscriberInterface
{
    public function __construct(private DataLayer $dataLayer) {}

    public function onResponse(ResponseEvent $event)
    {
        $request = $event->getRequest();
        $this->dataLayer->add('user', [
            'type'     => $request->isAuthenticated() ? 'member' : 'guest',
            'pageType' => $request->attributes->get('_route'),
        ]);
    }

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::RESPONSE => 'onResponse'];
    }
}

This is where most real-world leverage lives — analytics conventions are usually cross-cutting, not per-controller.

E-commerce funnel events. The classic GTM pattern maps cleanly: ecommerce.detail (product page), ecommerce.addToCart (cart POST — set data after the mutation succeeds, redirect back), ecommerce.checkout (checkout page), ecommerce.purchase (order confirmation). The bundle's job is to serialize those structures; the tag manager does the rest.

$dataLayer->add('ecommerce', [
    'purchase' => [
        'transactionId' => $order->getReference(),
        'transactionTotal' => $order->getTotal(),
        'transactionProducts' => $order->getLines()->map(fn ($l) => [
            'sku' => $l->getSku(), 'price' => $l->getPrice(), 'quantity' => $l->getQuantity(),
        ])->toArray(),
    ],
]);

Twig-side augmentation. If the bundle exposes Twig-side setters, use them for view-only data (e.g. per-widget exposure) but keep the structure (nested taxonomy: ecommerce.impressions[]) in PHP where you have type safety. Twig is for small scalar values, not the cart.

Config as default values. Put environment-agnostic defaults (currency, site section, GTM container ID if the bundle handles it) in config.yml. This makes the base push identical in dev/staging/prod and diffable at review time.

Testing. Twig-render a request and assert against the emitted JSON: assertStringContainsString('"transactionId":"ORD-123"', $response->getContent()). The bundle is thin, so functional tests on the rendered HTML are more valuable than unit tests on the DataLayer class.

Gotchas and Tips

This is a Symfony bundle — you are a Laravel developer. There is no ServiceProvider, no config/data-layer.php, no vendor:publish. If you're trying to use it in a Laravel app, you need a bridge (a ServiceProvider wrapping the DataLayer class + a Blade directive replacing the Twig call). Unless you have a strong reason to reuse this exact class, that bridge is more code than just writing view()->share('dataLayer', [...]) and rendering JSON in Blade. For Laravel-native apps, don't adopt this dependency — roll the equivalent in ~50 lines.

Order in <head> is the whole ballgame. GTM reads window.dataLayer when the container script executes. If lexik_data_layer_render() is placed below the GTM snippet, or via defer/async, your push is lost for the initial event. Also do not overwrite window.dataLayer — the render must push, not replace (dataLayer.push(...)), so multiple calls in one page work.

XSS in the JSON block. The output goes inside <script>. A product name containing </script> or <!-- will break out. If the bundle uses json_encode without flags, wrap or verify it uses JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP. If not, render yourself with those flags and a |raw Twig filter. This is a real security issue, not a nitpick.

Twig autoescaping. lexik_data_layer_render() must return Markup (Twig\Markup) or be called with |raw. If you see HTML-escaped &quot;ecommerce&quot; in the source, that's an autoescape miss — the JSON is inert and GTM sees nothing.

Empty data layer. Rendering window.dataLayer = [] (or push({})) on pages with no data is usually harmless but produces noise in GTM Preview. Guard the render call: {% if lexik_data_layer_has_data() %} if the bundle exposes a check, otherwise render only in templates that need it and keep defaults minimal.

Long-running workers. Under RoadRunner / Swoole / FrankenPHP, the DataLayer service can leak between requests if it's a singleton holding state. The service must be shared: false or reset per request. In 2018-era Symfony this was a non-issue; today it is a real failure mode. Verify the service definition's scope; if it's a default singleton, add a kernel.request listener that calls reset()/clear().

Cache warmup. After changing config.yml, bin/console cache:clear in dev — in prod, deploy with cache warmup. Standard Symfony pain, but it surfaces here as "my default values disappeared."

Unmaintained (2018, 14 stars). No PHP 8.x / Symfony 6/7 compatibility work has happened. On modern Symfony, the bundle's services.yml likely uses old alias syntax and will fail to boot on ^6.0. Before adopting, run composer require against your actual PHP/Symfony matrix in a throwaway branch. If it doesn't install, don't fight it — register a DataLayer-shaped service manually (a plain class + one Twig extension) and skip the bundle. You lose nothing but the autoloader registration.

Debuggability. Use bin/console debug:container lexik_data_layer to confirm the service id and see its scope; bin/console debug:twig (or debug:twig --filter) to confirm the render function name. Both are faster than grepping the source.

Extension points. The bundle's structure is small; if it doesn't expose a DataLayer::reset() or a hook, fork or wrap it — the API is a handful of array mutations plus a JSON encoder, and either is trivial to vendor. Prefer wrapping (class MyDataLayer { __construct(DataLayer $inner) }) so a future Symfony-6-compatible replacement is a swap rather than a rewrite.

Reality check on leverage. This package saves you maybe ~30 lines of json_encode + a service definition. Its real value is convention — giving a team a shared "where analytics data lives" place. If you're starting fresh in 2025 on any framework, write that convention yourself; the bundle is a reference implementation, not infrastructure.

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.
ippanelcom/laravel-sdk
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