Symfony2 and Gearman: Parallel Processing and Background Processing

On a few of our projects we have a few different needs to either queue items to be processed in the background or we need a single request to be able to process something in parallel. Generally we use Gearman and the GearmanBundle. Let me explain a few different situations where we’ve found it handy to have Gearman around.

Background Processing

Often we’ll need to do something which takes a bit more time to process such as sending out a couple thousand push notifications to resizing several images. For this example lets use sending push notifications. You could have a person sit around as each notification is sent out and hope the page doesn’t timeout, however after a certain number of notifications, not to mention a terrible user experience, this approach will fail. Enter Gearman. With Gearman you are able to basically queue the event that a user has triggered a bunch of notifications that need to be processed and sent.

<?php
// In controller
public function triggerSendNotificationsAction(Request $request)
{
    // Code to get that user triggered a bunch of notifications to an array of user ids.
    $userIds = array(.......);
    $message = 'some message from the frontend user';
    
    //Get Gearman and tell it to run in the background a 'job'
    $this->get('gearman')->doBackgroundJob('MyDemoBundleWorker~sendNotificationsToUsers',
                                           json_encode(array('user_ids'=>$userIds,'message'=>$message)));
    
    return $this->renderText('Your notifications are being sent now!');
}

What we’ve done above is sent to the Gearman server a job to be processed in the background which means we don’t have to wait for it to finish. At this point all we’ve done is queued a job on the Gearman server, Gearman itself doesn’t know how to run the actual job. For that we create a ‘worker’ which reads jobs and processes them:

<?php
<?php
namespace My\DemoBundle\Gearman\Worker;
use Mmoreram\GearmanBundle\Driver\Gearman;
use Symfony\Component\DependencyInjection\Container;

/**
 * @Gearman\Work(
 *     iterations = 30,
 *     description = "Test worker",
 *     service = "my.test_worker"
 * )
 */
class TestWorker
{
    private $container;
    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    /**
     * Test method to run as a job
     *
     * @param \GearmanJob $job Object with job parameters
     *
     * @return boolean
     *
     * @Gearman\Job(
     *     name = "sendNotificationsToUsers",
     *     description = "Sends a list users an awesome notification"
     * )
     */
    public function sendNotificationsToUsers(\GearmanJob $job)
    {
        // Get the data we put in for the job previously.
        $data = json_decode($job->workload(),true);
        
        foreach($data['user_ids'] as $userId)
        {
          // Some task that takes a long, long time to do.
          
          $notification->send();
        }

    }
}

The worker will consume the job and then process it as it sees fit. In this case we just loop over each user ID and send them a notification.

Parallel Processing

One one of our applications users can associate their account with multiple databases. From there we go through each database and create different reports. On some of the application screens we let users poll each of their databases and we aggregate the data and create a real time report. The problem with doing this synchronously is that you have to go to each database one by one, meaning if you have 10 databases and each one takes 1 seconds to get the data from, you have at least ten seconds the user is waiting around; this doesn’t go well when you have 20 databases and so on. Instead, we use Gearman to farm out the task of going to each database and pull the data. From there, we have the request process total up all the aggregated data and display it. Now instead of waiting 10 seconds for each database, we farm out the work to 10 workers, wait 1 second and then can do any final processing and show it to the user. In the example below for brevity we’ve just done the totaling in a controller.

<?php
// In controller...
public function getReportAcrossDatabasesAction(Request $request)
{
  // Listen for Gearman to notify that it has data from a completed job.
  $this->get('event_dispatcher')
       ->addListener('gearman.client.callback.complete',array($this,'gearmanCallback'));
  
  $gearman = $this->get('gearman');
  
  //Assume $request->request->get('connectionIds') is list of connections.
  foreach($request->request->get('connectionIds') as $connID)
  {
    $gearman->addTask('MyDemoBundleWorker~gatherVastAmountsOfData',array(json_encode('connection_id'=>$connId)));
  }
  
  $gearman->runTasks();
  
  $totalRowCount = 0;
  
  foreach($this->callbackData as $data)
  {
    $totalRowCount+= $data['total'];  
  }
  
  return array('totalRowCount'=>$totalRowCount);
}


public function gearmanCallback(GearmanClientCallbackCompleteEvent $e)
{
    $this->callbackData[]  = json_decode($e->getGearmanTask()->data(),true);
}

What we’ve done here is created a job for each connection. This time we add them as tasks, which means we’ll wait until they’ve completed. On the worker side it is similar to except you return some data, ie return json_encode(array('total'=&gt;50000)); at the end of the the function.

What this allows us to do is to farm out the work in parallel to all the databases. Each worker runs queries on the database, computes some local data and passes it back. From there you can add it all together (if you want) and then display it to the user. With the job running in parallel the number of databases you can process is no longer limited on your request, but more on how many workers you have running in the background. The beauty with Gearman is that the workers don’t need to live on the same machine, so you could have a cluster of machines acting as ‘workers’ and be able to process more database connections in this scenario.

Anyways, Gearman has really made parallel processing and farming out work much easier. As the workers are also written in PHP, it is very easy to reuse code between the frontend and the workers. Often, we’ll start a new report without Gearman; getting logic/fixing bugs in a single request without the worker is easier. After we’re happy with how the code works, we’ll move the code we wrote into the worker and have it just return the final result.

Good luck! Feel free to drop us a line if you need any help.

Doctrine2: Using ResultSetMapping and MySQL temporary tables

Note: I haven’t actually tried this in production, it’s probably a terrible idea.

We’ve been using MySQL temporary tables to run some analytics lately and it got me wondering how difficult would it be to hydrate Doctrine2 objects from these tables? We’ve primarily been using MySQL temporary tables to allow us to break apart complicated SQL queries, cache intermediate steps, and generally make debugging analytics a bit easier. Anyway, given that use case this is a bit of a contrived example but it’s still an interesting look inside Doctrine.

For arguments sake, lets say we’re using the FOSUserBundle and we have a table called “be_user” that looks something like:

"id","username","enabled","first_name"
"161965","8857049215","1","Tom"
"161964","7783806403","1","Larry"
"161963","1702340214","1","Mark"
"161962","6140583379","1","Nick"
"161961","5474626482","1","Wes"
"161960","2335083853","1","Mark"
"161959","6807578353","1","Mark"
"161958","5928840701","1","Mark"
"161957","1695583227","1","Mark"
"161956","1276759998","1","Mark"
"161955","1714401966","1","Mark"
"161954","9967684636","1","Mark"
"161953","4778383968","1","Mark"
"161952","9616931158","1","Mark"
"161951","8597344302","1","Mark"
"161950","4661522024","1","Mark"
"161949","8085406700","1","Mark"
"161948","1766644457","1","Mark"
"161947","3052034756","1","Mark"
"161946","2151235620","1","Mark"
"161945","6028556862","1","Mark"
"161944","4428949926","1","Mark"
"161943","8650207427","1","Mark"
"161942","6847827313","1","Mark"
"161941","9572768113","1","Mark"
"161940","3230071069","1","Mark"
"161939","7336484313","1","Mark"
"161938","9209189107","1","Mark"
"161937","8708069230","1","Mark"
"161936","7537175130","1","Mark"

Now, for some reason we’re going to end up creating a separate MySQL table (temporary or otherwise) with a subset of this data but identical columns:

<?php

$sql = "CREATE TEMPORARY TABLE active_users AS (SELECT * FROM be_user WHERE enabled = 1 AND LOCATE('968', username) > 0)";
$doctrine->getEntityManager()->getConnection()->query( $sql );

So now how do we load data from this secondary table into Doctrine2 entities? Turns out it’s relatively straightforward. By using Doctrine’s createNativeQuery along with ResultSetMapping you’ll be able to pull data out of the alternative table and return regular User entitites. One key point, is that by using DisconnectedClassMetadataFactory it’s actually possible to introspect your Doctrine entities at runtime so that you can add the ResultSetMapping fields dynamically.

Anyway, my code inside a Command to test this out ended up looking like:

<?php

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Tools\Console\MetadataFilter;
use Doctrine\ORM\Tools\EntityGenerator;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
use Doctrine\ORM\Query\ResultSetMapping;

protected function execute(InputInterface $input, OutputInterface $output) {

  $doctrine = $this->getContainer()->get("doctrine");
  $em = $doctrine->getEntityManager();

  // This lets us introspect Doctrine entities
  $cmf = new DisconnectedClassMetadataFactory();
  $cmf->setEntityManager($em);
  
  // Get the entity data
  $classMetadata = $cmf->getMetadataFor("CT\\BEBundle\\Entity\\User");

  $rsm = new ResultSetMapping();
  $rsm->addEntityResult('CT\\BEBundle\\Entity\\User', 'u');
  
  // Add the field info for each column/field       
  foreach( $classMetadata->fieldMappings as $id => $obj ){
    $rsm->addFieldResult('u', $obj["columnName"], $obj["fieldName"]);
  }	    

  $res = $doctrine->getEntityManager()
                ->getRepository("CTBEBundle:User")
                ->createQueryBuilder("u")
                ->select("COUNT(u.id) AS u_cnt")
                ->getQuery()
                ->getResult();

  $output->writeln("Total users: " . $res[0]["u_cnt"]);

  // Select some data into the other table
  $sql = "CREATE TEMPORARY TABLE active_users AS (SELECT * FROM be_user WHERE enabled = 1 AND LOCATE('968', username) > 0)";
  $doctrine->getEntityManager()->getConnection()->query( $sql );

  $query = $doctrine->getManager()->createNativeQuery('SELECT * FROM active_users', $rsm);
  $users = $query->getResult();

  $output->writeln("Active users: " . count($users));
}

Symfony2: Using FOSUserBundle with multiple EntityManagers

Last week, we were looking to setup one of our Symfony2 projects to use a master/slave MySQL configuration. We’d looked into using the MasterSlaveConnection Doctrine2 connection class, but unfortunately it doesn’t really work the way you’d expect. Anyway, the “next best” way to set up master/slave connections seemed to be creating two separate EntityManagers, one pointing at the master and one at the slave. Setting up the Doctrine configurations for this is pretty straightforward, you’ll end up with YAML that looks like:

# Doctrine Configuration
doctrine:
    dbal:
      connections:
        default:
          driver: %database_driver%
          host: %database_host%
          port: %database_port%
          dbname: %database_name%
          user: %database_user%
          password: %database_password%
          charset: UTF8
        slave:
          driver: %database_driver%
          host: %database_slave_host%
          port: %database_slave_port%
          dbname: %database_slave_name%
          user: %database_slave_user%
          password: %database_slave_password%
          charset: UTF8

    orm:
        auto_generate_proxy_classes: %kernel.debug%
        default_entity_manager: default
        entity_managers:
          default:
            connection: default
            mappings:
              SetfiveBundle:
                dir: Entity
              FOSUserBundle: ~
          slave:
            connection: slave
            mappings:
              SetfiveBundle:
                dir: Entity
              FOSUserBundle: ~

At face value, it looked like everything was working fine but it turns out they weren’t - the FOSUserBundle entities weren’t getting properly setup on the slave connection. Turns out, because FOSUserBundle uses Doctrine2 superclasses to setup it’s fields there’s no way to natively use FOSUserBundle with multiple entity managers. The key issue is that since the UserProvider checks the class of a user being refreshed, you can’t just copy the FOSUserBundle fields directly into your entity:

<?php
// From https://github.com/FriendsOfSymfony/FOSUserBundle/blob/6fc28e41805868c6c635de12266c40595c2e7ce8/Security/UserProvider.php

    public function refreshUser(SecurityUserInterface $user)
    {
        if (!$user instanceof User && !$user instanceof PropelUser) {
            throw new UnsupportedUserException(sprintf('Expected an instance of FOS\UserBundle\Model\User, but got "%s".', get_class($user)));
        }

        if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) {
            throw new UsernameNotFoundException(sprintf('User with ID "%d" could not be reloaded.', $user->getId()));
        }

        return $reloadedUser;
    }

So how do you get around this? Turns out, you need to add a custom UserProvider to bypass the instance class check. My UserProvider ended up looking like:

<?php

namespace Setfive\AcmeBundle\Security;

use FOS\UserBundle\Security\UserProvider as BaseUserProvider;
use Symfony\Component\Security\Core\User\UserInterface as SecurityUserInterface;

class UserProvider extends BaseUserProvider {

    /**
* Constructor.
*
* @param UserManagerInterface $userManager
*/
    public function __construct($userManager)
    {
        parent::__construct($userManager);
    }

    public function refreshUser(SecurityUserInterface $user)
    {

        if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) {
            throw new UsernameNotFoundException(sprintf('User with ID "%d" could not be reloaded.', $user->getId()));
        }

        return $reloadedUser;
    }

}

And then the additional YAML configurations you need are:

# services.yml

setfive.security.userProvider:
   class: Setfive\AcmeBundle\Security\UserProvider
   arguments: [@fos_user.user_manager] 


# security.yml

providers:
  fos_userbundle:
    id: fos_user.user_provider.username
  setfive_user:
    id: setfive.security.userProvider

The last step is copying all the FOSUserBundle fields directly into your User entity and update it to not extend the FOSUserBundle base class. Anyway, that’s it - two EntityManagers and one FOSUserBundle.

Symfony2: Logging users in programatically with Doctrine2 and FOSUserBundle

Recently I was doing a fairly common task on Symfony2, logging in a user programatically. Often applications do this on registration, via auto login links, complex login forms, etc. This time I was using an auto login link that expires that users get via email. I came across the issue that it seemed the first time the page loaded I was logged in properly but then as soon as I redirect or navigated anywhere I was logged out.

Here is the basic workflow we were using:

  1. Create an auto login link, basically just an entity which had an expiration date and a special url hash
  2. When clicked forward to action which gets the related entity, and then retrieves the user.
  3. Login the user programatically
  4. Redirect to whatever we want them to see

The issue was somewhere between step 3 and 4 something was amiss, if I eliminated step 3 the profiler toolbar showed I was properly logged in as expect, as soon as I redirect it showed me as unauthenticated. Here is the code for the most part:

<?php

    /**
     * @Route("/login-token/{token}/{userHash}", name="participant_autologin_token")
     * @ParamConverter("autologinToken", class="MyBundle:AutologinToken")
     * @Template()
     */
    public function autoLoginAction(Request $request, AutologinToken $autologinToken, $userHash){
        
        $csrf_token = $this->container->get('form.csrf_provider')->generateCsrfToken('authenticate');
        $now = new \DateTime();
        
        if( $autologinToken->getExpiresAt() != NULL && $autologinToken->getExpiresAt() < $now ){
            return array("csrf_token" => $csrf_token, "last_username" => "", "nextUrl" => $autologinToken->getUrl());
        }        
        
        if( $autologinToken->getUser()->getAutologinToken() != $userHash ){
            return array("csrf_token" => $csrf_token, "last_username" => "", "error" => "Sorry! There is a problem with your link. Please contact support.");    
        }
        
        $user = $autologinToken->getUser();

        $providerKey = $this->container->getParameter('fos_user.firewall_name');
        $token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
        $this->get('security.context')->setToken($token);
        
        $event = new InteractiveLoginEvent($request, $token);
        $this->get("event_dispatcher")->dispatch("security.authentication", $event);

        return $this->redirect( $autologinToken->getUrl() );     
    }

Fairly simple, used a ParamConverter to convert the incoming request to an entity. After a while of troubleshooting, I noticed that the ‘$user’ in this case wasn’t an actual Entity, it was a ProxyClass that Doctrine2 had generated. I had read that Doctrine2 ProxyClasses when serialized don’t properly bring over some of their attributes, namely the ID. This caused an issue with FOSUserBundle as the UserProvider looks up the user by their ID. Since the ID was blank this kept causing it to not find my user on the next page load.

There are a number of ways you can fix this, two that come to mind is to override the ‘refreshUser’ method of the UserProvider to look up by username as that is properly serialized from Proxy objects. Instead, as this was only for this one action and I wanted to be more efficient I switched the query to do a join to the user from the get go. This means when you do getUser Doctrine will return the actual Entity and not a Proxy class. Here is my update annotation:

<?php
    /**
     * @Route("/login-token/{token}/{userHash}", name="participant_autologin_token")
     * @ParamConverter("autologinToken", class="MyBundle:AutologinToken", options={"repository_method" = "findByTokenJoinWithUser"})
     * @Template()
     */
    public function autoLoginAction(Request $request, AutologinToken $autologinToken, $userHash){
    ....
    }

For more on how to use joins and entity repository specific consult the current manual.

Good luck!

Symfony2: Using kernel events like preExecute to log requests

A couple of days ago, one of our developers mentioned wanting to log all the requests that hit a specific Symfony2 controller. Back in Symfony 1.2, you’d be able to easily accomplish this with a “preExecute” function in the specific controller that you want to log. We’d actually set something similar to this up and the code would end up looking like:

<?php

public function preExecute(){
  $this->jsonVariables = strlen($this->getRequest()->getContent()) 
                           ? json_decode( $this->getRequest()->getContent(), true ) : array();
      
   if( strlen(trim($this->getRequest()->getContent())) && $this->getUser()->getUserId() ){
      $uid = $this->getUser()->getUserId();
      $log = new JsonApiLog();
      $log->setUserId( $uid );
      $log->setRequestBody( $this->getRequest()->getContent() );
      $log->save();
   }
      
}

Symfony2 doesn’t have a “preExecute” hook in the same fashion as 1.2 but using the event system you can accomplish the same thing. What you’ll basically end up doing is configuring an event listener for the “kernel.controller” event, inject the EntityManager (or kernel) and then log the request.

The pertinent service configuration in YAML looks like:

    ctbe.apirequest_listner:
        class: CT\BEBundle\Event\ApiControllerRequest
        arguments: [@kernel]
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }

And then the corresponding class looks something like:

<?php

namespace CT\BEBundle\Event;

use CT\BEBundle\Entity\ApiRequest;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Kernel;

class ApiControllerRequest {
    
    private $kernel;
    
    public function __construct(Kernel $kernel){
        $this->kernel = $kernel;
    }
    
    public function onControllerRequest(FilterControllerEvent $event){
        
        $controller = $event->getRequest()->attributes->get('_controller');

        if( count(explode("::", $controller)) == 1 ){
            return;
        }
        
        list($className, $methodName) = explode("::", $controller);
        $className = explode("\\", $className);
        $className = $className[count($className) - 1];
        
        if( strpos($className, "Api") === false ){
            return;
        }

        $user = $this->kernel->getContainer()->get('security.context')->getToken()->getUser();
        
        $req = new ApiRequest();
        $req->setUser($user);
        $req->setRequest( $event->getRequest()->getContent() );
        $req->setUrl( $event->getRequest()->getRequestUri() );
        
        $this->kernel->getContainer()->get("doctrine")->getEntityManager()->persist($req);
        $this->kernel->getContainer()->get("doctrine")->getEntityManager()->flush($req);
    }
}

And thats about it.