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

Ab Bundle Laravel Package

jm/ab-bundle

jm/ab-bundle is a Symfony bundle for running A/B tests directly in your PHP application. Define experiments and variants, assign visitors consistently, render versions in Twig, and track conversions to compare performance without relying on an external testing service.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: jm/ab-bundle is a Symfony 2.x bundle, not a Laravel package. There is no service provider, no facade, no config publish command. If you are evaluating it for a Laravel codebase, you are evaluating the pattern (session-sticky weighted template selection), not something you can composer require and use. Score 0.265 with 23 stars and 0 dependents confirms it: treat it as reference material, not a dependency.

If you are on Symfony (2.x/3.x era):

  1. composer require jm/ab-bundle
  2. Register in AppKernel: new Jm\ABBundle\JmABBundle()
  3. Declare your tests in app/config/config.yml:
jm_ab:
    tests:
        homepage_hero:
            versions:
                - { template: "AcmeBundle:Homepage:hero_a.html.twig", weight: 50 }
                - { template: "AcmeBundle:Homepage:hero_b.html.twig", weight: 50 }
  1. Use it in Twig:
{% include ab_template('homepage_hero') %}

That is the entire first use case: swap one partial 50/50, stick the choice to the session, move on.

Where to look first in the source:

  • DependencyInjection/Configuration.php — the exact config schema and weight semantics
  • Twig/ — the ab_template() function and what it returns (template path vs. rendered output)
  • Manager/ (service id jm_ab.manager) — the selection + session persistence logic

If you are on Laravel, spend 45 minutes reading the manager and Twig extension, then reimplement in ~80 lines: a AbTestManager singleton, a Blade directive or @include($ab->template('homepage_hero')), and session()->put("ab.{$test}", $variant).

Implementation Patterns

Config-driven variants, weighted. Weights are relative, not percentages — 1 and 1 is 50/50, 3 and 1 is 75/25. Keep the totals arbitrary but readable; do not normalise to 100 in config or you will fight rounding.

Twig include is the primary interface. Use it for leaf partials (hero, CTA, pricing table layout), never for the whole page. The moment you A/B the full layout you inherit every caching and session problem at once.

{# good: one isolated partial #}
{% include ab_template('pricing_table') %}

{# bad: whole-page swap, cache-hostile #}
{% extends ab_template('layout') %}

Service-level access when you need the variant name, not the template. You almost always need both — the template to render and the identifier to log:

$manager = $this->get('jm_ab.manager');
$template = $manager->getTemplate('homepage_hero');   // e.g. AcmeBundle:Homepage:hero_b.html.twig

$this->get('logger')->info('ab.exposure', [
    'test'    => 'homepage_hero',
    'variant' => $template,
]);

Without this exposure log you have a test you cannot measure. The bundle deliberately does not ship analytics; that is your job.

Wrap it, don't call it directly. Even in Symfony, put a thin ExperimentService around the manager on day one. It costs nothing and means the eventual migration (to your own implementation, or to a paid platform) is one file:

class ExperimentService
{
    public function __construct(private $manager, private LoggerInterface $logger) {}

    public function variant(string $test): string
    {
        $v = $this->manager->getTemplate($test);
        $this->logger->info('ab.exposure', compact('test', 'v'));
        return $v;
    }
}

Template naming convention. Namespace variants so they are greppable and deployable together: ab/homepage_hero/a.html.twig, ab/homepage_hero/b.html.twig. When the test ends you delete one directory and promote the other — no archaeology.

Retire tests deliberately. There is no expiry mechanism. Keep a tests: block that only contains live tests, and a ticket/calendar reminder for each. Stale config entries are the #1 source of "why is this page still different for some users" bugs six months later.

Gotchas and Tips

Session-sticky, not user-sticky. Assignment lives in the session. A logged-out visitor gets a new variant on every cookie clear, and a logged-in user gets a different variant on a different device. If your metric is per-user (revenue, retention), this invalidates the test. Log the variant against the user ID server-side if that matters.

HTTP cache will silently break your test. If the page is cached by Symfony's HTTP cache, Varnish, or Cloudflare, the first variant that hits the cache is served to everyone — and your session logic never runs. Either exclude A/B routes from caching, vary on the session cookie (kills cache hit rate), or switch to client-side assignment for cached pages.

Session fixation on login. If your app regenerates the session ID on login (it should), verify the A/B keys survive. Depending on the Symfony version and how the bundle stores data, a naive session->migrate() can drop assignment, giving you a mid-funnel variant switch — which shows up as inexplicable conversion noise.

Weight semantics. Confirm in Configuration.php whether weights are relative or normalised before you publish results. Getting this wrong means your "50/50" test was actually 60/40 and your significance calculation is garbage.

Template typos fail at render time, not config time. A bad path in versions throws a Twig loader exception at request time, potentially on only one variant — i.e. for half your traffic only. Add a smoke test that resolves every configured template name via the Twig loader in CI.

No built-in stats or goals. The bundle picks a template. That is all. Pair it with GA4 custom dimensions, Matomo, or your own exposure table, and decide your primary metric before launch.

Debugging assignment: dump the session ($session->all() in a controller, or the profiler's Session panel) and look for the jm_ab-prefixed keys. If the key is absent after a request, the manager never ran — which usually means the Twig function was not called on that code path (e.g. an early-return controller or a cached fragment).

Extension points: the manager is a normal service, so override it with a compiler pass and swap in your own selection strategy (hash-on-user-ID instead of session, for cache-friendly stickiness). That is a genuinely useful five-line change and the best reason to read the source at all.

Version constraints. The bundle's composer.json targets old Symfony. Expect conflicts with Symfony 5+/6+/7+. Do not vendor it into a modern app — port the idea instead.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource