elcodi/referral-program-bundle
Elcodi bundle that adds a referral program to your e-commerce store: generate invite codes and links, track which customer referred each signup or order, and credit rewards to advocates. Integrates with Elcodi's user and order components.
The package lives inside the elcodi/elcodi monorepo but is published to Packagist as a split package, so a legacy app pulls it directly:
composer require "elcodi/referral-program-bundle:^1.0"
You will also need elcodi/core, elocdi/user, elocdi/mailer and Doctrine ORM — the bundle is not standalone and its entities reference Elocodi\Component\User\Model\UserInterface. Register Elocodi\Bundle\ReferralProgramBundle\ElocodiReferralProgramBundle in app/AppKernel.php (Symfony 2.8/3.x era) and let the bundle's AcmeDemoBundle-style config block load. Budget an hour for dependency resolution alone: the constraint set targets PHP ~5.5–7.0 and Symfony 2.8/3.0, so on anything newer expect to pin symfony/* to ^3.4 at most.
Where to look first, in this order:
Elocodi\Component\ReferralProgram\Services\ReferralProgramManager and its siblings (ReferralInvitationManager) — this is the actual API surface, everything else is wiring.Elocodi\Bundle\ReferralProgramBundle\DependencyInjection\Configuration — the whole configurable surface, ~20 lines.Resources/config/directors.yml — Elocodi's ObjectDirector pattern exposes elocodi.director.referral_hash and elocodi.director.referral_invitation; that is what you should inject, not the repositories.Resources/config/doctrine/ — mapping files, so you know what columns you are migrating.First use case — the only one the bundle really solves: give a logged-in user a stable referral token and record who they invited. Render the token as ?ref=<hash> on your signup URL, capture it into the session in a request listener, and when the invited account is created, create a ReferralInvitation back to the referrer. Everything about what the referrer wins is your code, not the bundle's.
Use directors, never repositories. Elocodi's core convention wraps factory + repository + object manager in one ObjectDirector. Follow it — bypassing it means your flush ordering diverges from the rest of the application.
class InviteController
{
public function __construct(
private ObjectDirector $referralHashDirector,
private ObjectDirector $referralInvitationDirector,
private ReferralProgramManager $referralProgramManager
) {}
public function inviteAction(UserInterface $user, string $email)
{
$hash = $this->referralProgramManager->getReferralHashForUser($user); // creates if absent
$invitation = $this->referralInvitationDirector->create();
$invitation
->setReferralHash($hash)
->setEmail($email)
->setDate(new \DateTime());
$this->referralInvitationDirector->save($invitation); // flushes
return $this->render('ReferralProgramBundle:Invitation:sent.html.twig', ['hash' => $hash]);
}
}
Hash capture belongs in a listener. Elocodi does not ship the "read ?ref= from the request" step; it only owns the hash entity. Add a kernel.request subscriber that stores $request->query->get('ref') in the session, and a user.registered/registration.success listener that looks up the ReferralHash by string and creates the invitation. This split is the intended seam and the reason the bundle looks thin.
Reward logic goes in an event subscriber on your own domain events. Listen to order.paid or your registration event, resolve the referrer through ReferralHash::getUser(), and issue a coupon through elocdi/coupon — reuse its CouponManager rather than writing discount logic. The bundle has no notion of "conversion", so don't look for ReferralLine semantics that don't exist in this package.
Override by service id, not by class. Every piece (elocodi.referral_program.generator.hash, the invitation mailer, the invitation template) is defined in Resources/config/*.yml. To change hash generation (e.g. UUID vs. random string), redeclare that service id in your app config with a compiler pass; that is the supported extension point, and it survives bundle upgrades better than __construct overrides.
Templates. The invitation email renders from ReferralProgramBundle:Email:invitation.html.twig. Override in app/Resources/ElocodiReferralProgramBundle/views/Email/ rather than editing vendor/, and keep the hash and user variables the bundle passes in — the mailer will fail silently (no exception, blank body) if you drop them.
src/ if you still need referral hashes.hash; if you write a custom generator, guard against collisions in a retry loop before save(), otherwise you get a UniqueConstraintViolationException on flush with no retry.save() flushes. Directors call ObjectManager::flush() by default, so a controller that saves an invitation and then persists an order will interleave flushes. For batch work, wrap in $em->beginTransaction() or use the director's repository directly with your own flush().elocdi/user is hard. ReferralHash holds a UserInterface from that component. If your User is a FOSUserBundle entity, you need the Elocodi mapping/bridge; there is no adapter.accepted/converted flag — you add it. A common trap is writing to ReferralInvitation::setDate() to mean "used"; add a migration and a real column instead.bin/console debug:container | grep referral and debug:container elocodi.director.referral_hash. Almost every "it doesn't save" bug is a missing director alias or the wrong entity manager in a multi-EM setup.Resources/config/doctrine/; enabling both gives duplicate-mapping errors on cache warmup in Symfony 3.x. Pick one in config.yml and leave it.Definition::setFactory array syntax, and the app/AppKernel.php docs all assume pre-Flex. If you are modernising, shim the services manually — it is faster than trying to make the bundle install.How can I help you explore Laravel packages today?