Делюсь решением, может кому надо.
Задачка: фиксировать коды рефералов.
Мы каким-то образом мотивируем пользователей завлекать новых пользователей. Понадобится реферальный код в аккаунте пользователя. Точнее два:
- собственный код, который мы вставим в реферальную ссылку для копирования (user.ref)
- код по которому этот пользователь зарегался (user.follow_ref)
Очевидно что ссылка с кодом будет приводить клиента не на страницу регистрации, а на "вкусную" страничку. Со временем число таких страничек будет прирастать. А код должен сохраниться от прихода пользователя до заведения нового акка. Очевидно в сессии.
Я это реализовал с учетом специфики Symfony 2 — через механизм Listener. "Пользователь" это моё расширение популярного FOSUserBundle, добавляется несколько полей. Внутри класса User с полями можно работать напрямую, а извне через геттеры/сеттеры (слава фабьену, доктрина умеет их генерить).
Собственный реферальный код пользователя генерится как производная от uniqid() в момент создания объекта.
Класс Пользователь:
<?php
namespace Acme\AcmeUserBundle\Entity;
use Another\DescendantOfFOSUserBundle\Entity\BaseUser;
class User extends BaseUser
{
/** @var string $ref This User's reference code */
protected $ref;
/** @var string $followRef Who do this user follow? */
protected $followRef;
public function __construct()
{
parent::__construct();
$this->ref = md5(uniqid());
}
// ... другие поля + сеттеры/геттеры
}
Чтобы не программировать каждую лендинг-страницу, я создаю "слушателя", который тупо проверяет get-параметры. Еще раз, параметр может быть по любому маршруту! Если волшебное имя есть — слушатель пишет значение в сессию.
Дальше я собирался сохранять это значение в классе событии сущности User::prePersist, но не получилось, т.к. оттуда проблематично достать класс сессии. Доктриновские сущности не ContainerAware. В итоге завел еще одного слушателя, который перехватывает событие prePersist.
Класс на оба события у меня один, назвал ReferralListener. Оформил как сервис:
Конфиг сервиса:
services:
sandbox_init_cms.request_listener:
class: Acme\AcmeBundle\EventListener\ReferralListener
arguments: [ @service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
- { name: doctrine.event_listener, event: prePersist }
Класс Слушатель:
<?php
namespace Acme\AcmeBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Application\Acme\UserBundle\Entity\User;
class ReferralListener
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* Instantiate listener. Inject container to access services
*
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Intercept all HTTP requests, and keep track referral
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->query->has('_ref')) {
// It should be done for guests only
$securityContext = $this->container->get('security.context');
$isLoggedIn = ($securityContext->getToken() &&
($securityContext->isGranted('IS_AUTHENTICATED_FULLY') ||
$securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')));
if (!$isLoggedIn) {
$code = $request->query->get('_ref');
$session = $this->container->get('session');
$session->set('_ref', $code);
}
}
}
/**
* Intercept all ORM prePersist events, and store referral if applicable
*
* @param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof User) {
$session = $this->container->get('session');
if ($session->has('_ref')) {
$entity->setFollowRef($session->get('_ref'));
}
}
}
}
Реквестую на каменты.
ЁМАНА! ФОРУМ ПРОГРАММИСТОВ!
Cорцы в "CODE" нифига не выглядят как сорцы. Попробую сохранить как markdown…
EDITED!!!: освоил разметку. я люблю маркдаун.
короче вот gist