Category Archives: Symfony2

Multiple class inheritance with Doctrine ODM in Symfony: One collection for multiple document types

When you use Doctrine ORM in Symfony all the data is stored in tables having every row the same columns. Storing different type of entities in the same table is not posible (AFAIK). But MongoDB can store any kind of document in the same collection making possible to have different kind of objects in the same place and thus making inheritance easy to use if you need it.

doctrine-odm-mongodb

In this example I’m going to create the documents needed to reflect an RFID system (from a real application I did sometime ago) where cards, called internally tag, can represent different things: a person, an item, an event, etc. Thus our mongodb database will have a collection of tags with documents of type person, item, event, etc.

Each time a card is used the system will know what it represents using the internal EPC of it: as you may be thinking this will be our ID.

Install MongoDB, the PHP driver and create a new database

This process depends on the operating system you use. Here are the instructions for some of them in older posts:

To create a new database in our mongodb server you have just to make a use statement and create a new collection in it with the mongo client:

> use app
switched to db app
> db.createCollection('tags')
{ "ok" : 1 }

Install MongoDB Doctrine library and DoctrineMongoDBBundle

This process is needed to be able to use MongoDB within the Symfony project. Those steps are extracted from the Symfony DoctrineMongoDBBundle Documentation.

Add the MongoDB Doctrine library and the DoctrineMongoDBBundle to composer.json:

{
    "require": {
        "doctrine/mongodb-odm": "~1.0",
        "doctrine/mongodb-odm-bundle": "~3.0"
    },
}

Now install them using composer. Using a local installation of it:

$ php composer.phar update doctrine/mongodb-odm doctrine/mongodb-odm-bundle

Or a global installation:

$ composer update doctrine/mongodb-odm doctrine/mongodb-odm-bundle

Register the annotations library by adding the following to the autoloader (below the existing AnnotationRegistry::registerLoader line):

use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
AnnotationDriver::registerAnnotationClasses();

It should look something like this:

<?php
// app/autoload.php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
use Composer\Autoload\ClassLoader;

/**
 * @var ClassLoader $loader
 */
$loader = require __DIR__.'/../vendor/autoload.php';

AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
AnnotationDriver::registerAnnotationClasses();

return $loader;

Now update AppKernel.php:

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle(),
    );

    // ...
}

Configure MongoDB ODM

Symfony must know how to connect to our mongodb instance. This is configured in app/config/config.yml being the easiest way using the auto_mapping option:

# app/config/config.yml
doctrine_mongodb:
    connections:
        default:
            server: mongodb://localhost:27017
            options: {}
    default_database: test_database
    document_managers:
        default:
            auto_mapping: true

I like using the app/config/parameters.yml to set up the host, port and database name:

# app/config/parameters.yml
parameters:
    mongodb_host: 127.0.0.1
    mongodb_port: 27017
    mongodb_name: app
# app/config/config.yml
imports:
    - { resource: parameters.yml }

doctrine_mongodb:
    connections:
        default:
            server: mongodb://%mongodb_host%:%mongodb_port%
            options: {}
    default_database: %mongodb_name%
    document_managers:
        default:
            auto_mapping: true

The document classes

Now is when we start coding. I’ll suppose you already have an AppBundle created (if not look here at the Symfony Best Practices – Creating the project).

The “parent” class: Single Collection Inheritance

All the different things we want to represent as a document in mongodb in the same collection and an object in PHP must inherit from the same class called tag. The “magic” here is the @MongoDB\InheritanceType("SINGLE_COLLECTION") annotation. This will tell MongoDB ODM that this collection will store different documents and a discrimination field will tell each other apart.

<?php
// src/AppBundle/Document/TagInterface.php

namespace AppBundle\Document;

interface TagInterface
{
}
<?php
// src/AppBundle/Document/Tag.php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document(collection="tags")
 * @MongoDB\InheritanceType("SINGLE_COLLECTION")
 * @MongoDB\DiscriminatorField("type")
 * @MongoDB\DiscriminatorMap({"person"="Person", "item"="Item"})
 */
class Tag implements TagInterface
{
    /**
     * @MongoDB\Id(strategy="NONE")
     */
    protected $epc;

    /**
     * Set epc
     *
     * @param custom_id $epc
     * @return self
     */
    public function setEpc($epc)
    {
        $this->epc = $epc;
    }

    /**
     * Get epc
     *
     * @return custom_id $epc
     */
    public function getEpc()
    {
        return $this->epc;
    }
}

The inherited classes: extends

Now that we have the parent class the only thing we need to store documents in that collection is extend it:

<?php
// src/AppBundle/Document/Person.php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document
 */
class Person extends Tag
{
    /**
     * @MongoDB\String
     */
    protected $name;

    /**
     * Set name
     *
     * @param string $name
     * @return self
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string $name
     */
    public function getName()
    {
        return $this->name;
    }
}
<?php
// src/AppBundle/Document/Item.php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document
 */
class Item extends Tag
{
    /**
     * @MongoDB\Float
     */
    protected $price;

    /**
     * Set price
     *
     * @param float $price
     * @return self
     */
    public function setPrice($price)
    {
        $this->price = $price;

        return $this;
    }

    /**
     * Get price
     *
     * @return float $price
     */
    public function getPrice()
    {
        return $this->price;
    }
}

Store and read documents to/from the collection

All the configuration and classes are done, we can start persisting and finding documents anywhere in the project: a controller, a command, etc. For example lets persist and find some Person and Item objects in a controller:

<?php
// src/AppBundle/Controller/TagsController.php

namespace AppBundle\Controller;

use AppBundle\Document\Item;
use AppBundle\Document\Person;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class TagsController extends Controller
{
    /**
     * @Route("/persist", name="app_persist")
     * @param Request $request The Request object
     * @return Response A Response instance
     */
    public function persistAction()
    {
        $item = new Item();
        $item->setEpc('0000000000000000000000AA');
        $item->setPrice('19.99');

        $person = new Person();
        $item->setEpc('0000000000000000000000AB');
        $item->setName('Eneko');

        $dm = $this->get('doctrine_mongodb')->getManager();
        $dm->persist($item);
        $dm->persist($person);
        $dm->flush();

        return new Response('Created tags');
    }

    /**
     * @Route("/find", name="app_find")
     * @param Request $request The Request object
     * @return Response A Response instance
     */
    public function findAction(Request $request)
    {
        $epc = $request->request->get('epc');
        $tag = $this->get('doctrine_mongodb')
            ->getRepository('AppBundle:Tag')
            ->find($epc);

        if (!$tag) {
            throw $this->createNotFoundException('No tag found for epc '.$epc);
        }

        switch(true) {
            case $tag instanceof Person:
                $type = 'Person';
                break;
            case $tag instanceof Item:
                $type = 'Item';
                break;
        }

        return new Response('Found '.$type);
    }

    /**
     * @Route(
     *     "/find-with-param-converter/{epc}",
     *     requirements={
     *         "epc": "[a-zA-Z0-9]{24}"
     *     },
     *     name="app_find_with_param_converter"
     * )
     * @ParamConverter("tag", class="AppBundle\Document\Tag")
     * @param Request $request The Request object
     * @param TagInterface $tag A object of class Tag or one of its children
     * @return Response A Response instance
     */
    public function findWithParamConverterAction(Request $request, TagInterface $tag)
    {
        switch(true) {
            case $tag instanceof Person:
                $type = 'Person';
                break;
            case $tag instanceof Item:
                $type = 'Item';
                break;
        }

        return new Response('Found '.$type);
    }
}

And how are all those object stored in mongodb and how can Doctrine know what class each one represents? That’s configured in the parent class with the @MongoDB\DiscriminatorField and the @MongoDB\DiscriminatorMap annotations. Each document simply has a field named type with the name of the class on it:

> db.tags.find()
{
    "_id" : "0000000000000000000000AA",
    "type" : "item",
    "price" : 19.99
},
{
    "_id" : "0000000000000000000000AB",
    "type" : "person",
    "name" : "Eneko"
}

Ref: http://doctrine-mongodb-odm.readthedocs.org/en/latest/reference/basic-mapping.html
http://doctrine-mongodb-odm.readthedocs.org/en/latest/reference/inheritance-mapping.html
http://jwage.com/post/30490180105/inheritance-and-mapped-super-classes-in-doctrine

Symfony Forms and Bootstrap Datetimepicker

In this example I’m going to use the spanish locale and the “Europe/Madrid” timezone.

First we need an entity that will hold the DateTime, for example a Booking entity:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="bookings")
 */
class Booking
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $date;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->date = new \DateTime();
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set date
     *
     * @param \Datetime $date
     * @return Booking
     */
    public function setDate($date)
    {
        $this->date = $date;

        return $this;
    }

    /**
     * Get date
     *
     * @return \Datetime 
     */
    public function getDate()
    {
        return $this->date;
    }
}

Our entity uses a DateTime object but the field in the form is going to be an input that can only have text. That’s why we need a DataTransformer. It will transform a DateTime object to string and vice versa when needed:

<?php

namespace AppBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class DateTimeTransformer implements DataTransformerInterface
{
    /**
     * Transforms an object (DateTime) to a string.
     *
     * @param  DateTime|null $datetime
     * @return string
     */
    public function transform($datetime)
    {
        if (null === $datetime) {
            return '';
        }

        return $datetime->format('d/m/Y H:i');
    }

    /**
     * Transforms a string to an object (DateTime).
     *
     * @param  string $datetime
     * @return DateTime|null
     */
    public function reverseTransform($datetime)
    {
        // datetime optional
        if (!$datetime) {
            return;
        }

        return date_create_from_format('d/m/Y H:i', $datetime, new \DateTimeZone('Europe/Madrid'));
    }
}

Now the form class. You can see how addModelTransformer is used to link the form date field with the DataTransformer. When defining the date field it’s important to render it as a text field and use the attr option to add a data-provide and a data-format that will be added in the HTML input field as attributes. Also some CSS classes are needed (form-control input-inline datetimepicker), you can add them with attr here or later in the Twig template. I’ll show both but only one of them is needed.

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Form\DataTransformer\DateTimeTransformer;

class BookingFormType extends AbstractType
{
    private $class;

    /**
     * @param string $class The Booking class name
     */
    public function __construct($class)
    {
        $this->class = $class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('date', 'text', array(
                'required' => true,
                'label' => 'form.label.datetime',
                'translation_domain' => 'AppBundle',
                'attr' => array(
                    'class' => 'form-control input-inline datetimepicker',
                    'data-provide' => 'datepicker',
                    'data-format' => 'dd-mm-yyyy HH:ii',
                ),
            ))
            ->add('submit', 'submit')
        ;

        $builder->get('date')
            ->addModelTransformer(new DateTimeTransformer());
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            array(
                'data_class' => $this->class,
                'intention'  => 'edit',
            )
        );
    }

    public function getName()
    {
        return 'booking_edit';
    }
}

Let’s register the form as a service:

services:
    app.form.type.booking_edit:
        class: AppBundle\Form\Type\BookingFormType
        arguments: [AppBundle\Entity\Booking]
        tags:
            - { name: form.type, alias: booking_edit }

In the template we’ll include the form and the needed JavaScript and CSS for jQuery, MomentJS, Bootstrap and Bootstrap Datetimepicker. Remember that MomentJS must be loaded BEFORE Bootstrap Datetimepicker. Also just before the body closing tag we have to call the datetimepicker method on the input field. It’s very important to add “useCurrent: false” if you want to load a datetime as we do in this example.

<html>
    <head>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker-standalone.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
        <title>Symfony and Bootstrap Datetimepicker</title>
    </head>
    <body>
        <div class="container">
            <div class="starter-template">
                <h1>Symfony and Bootstrap Datetimepicker</h1>
                <div class="row">
                    {{ form_start(form) }}
                        {{ form_errors(form) }}
                        <div class="form-group">
                            {{ form_label(form.date, null, { 'label_attr': {'class': 'col-sm-2 control-label'} }) }}{{ form_errors(form.date) }}
                            <div class='input-group date' id='datetimepicker'>
                                {{ form_widget(form.date, {'attr': {'class': 'form-control input-inline datetimepicker'}}) }}
                                <span class="input-group-addon">
                                    <span class="glyphicon glyphicon-calendar"></span>
                                </span>
                            </div>
                        </div>
                        <div class="form-group">
                            {{ form_row(form.submit, { 'label': 'OK' }) }}
                        </div>
                        {{ form_end(form) }}
                    </div>
                </div>
            </div>
        </div>

        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment-with-locales.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"></script>
        <script type="text/javascript">
            $('#datetimepicker').datetimepicker({
                locale: 'es',
                useCurrent: false,
                sideBySide: true
            });
        </script>
    </body>
</html>

The controller is very simple (the handling of the form is not shown):

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use AppBundle\Entity\Booking;
use AppBundle\Form\Type\BookingFormType;

class MainController extends Controller
{
    /**
     * @Route("/booking/edit/{id}", name="booking_edit")
     * @ParamConverter("id", class="AppBundle:Booking")
     * @param Request $request The Request object
     * @param Booking $booking The Booking object
     * @return Response A Response instance
     */
    public function formAction(Request $request, Booking $booking)
    {
        $form = $this->createForm(new BookingFormType('\AppBundle\Entity\Booking'), $booking);

        $form->handleRequest($request);

        if ($form->isValid()) {
            ...
        }

        return $this->render('form.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

symfony-bootstrap-datetimepicker

Ref: https://eonasdan.github.io/bootstrap-datetimepicker/
http://ajaxray.com/blog/symfony2-forms-bootstrap-3-datepicker-for-date-field
http://symfony.com/doc/current/reference/forms/types/datetime.html
http://symfony.com/doc/current/cookbook/form/data_transformers.html

Translate Symfony TimezoneType Field Type

The TimezoneType Field Type

Symfony has an specific Field Type to create a Timezone select widget in forms called TimezoneType. Very handy because it’s a pretty usual need in web applications.

select

It uses \DateTimeZone::listIdentifiers() to create the texts and as you can see those texts are in english:

    public static function getTimezones()
    {
        if (null === static::$timezones) {
            static::$timezones = array();

            foreach (\DateTimeZone::listIdentifiers() as $timezone) {
                $parts = explode('/', $timezone);

                if (count($parts) > 2) {
                    $region = $parts[0];
                    $name = $parts[1].' - '.$parts[2];
                } elseif (count($parts) > 1) {
                    $region = $parts[0];
                    $name = $parts[1];
                } else {
                    $region = 'Other';
                    $name = $parts[0];
                }

                static::$timezones[$region][$timezone] = str_replace('_', ' ', $name);
            }
        }

        return static::$timezones;
    }

¿But what if you want to translate all those texts to your language?

Note: I’m going to assume that you are storing the users timezone in the database as it’s defined in PHP. For example “Europe/Madrid” for Spain (without the Canary Islands) or “America/North_Dakota/New_Salem” for the Morton County in USA. This is the timezone we are going to use as an example.

As you can see the timezones are divided in different parts using the “/” character. The first one designates an area (more or less a continent but not exactly) and then a city. In some cases it has 3 parts for a more specific area. We also have some special ones like UTC for the Universal Time Clock.

We are going to start by creating our form with the TimezoneType for the user profile edit page:

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;

class ProfileFormType extends AbstractType
{
    private $class;

    public function __construct($class)
    {
        $this->class = $class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $constraint = new UserPassword();

        $this->buildUserForm($builder, $options);

        $builder
            ->add('timezone', 'timezone', array(
                'label' => 'form.timezone',
                'translation_domain' => 'AppBundle',
                'choice_translation_domain' => true,
            ))
            ->add('current_password', 'password', array(
                'label' => 'form.current_password',
                'translation_domain' => 'FOSUserBundle',
                'mapped' => false,
                'constraints' => $constraint,
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => $this->class,
            'intention'  => 'profile',
        ));
    }

    public function getName()
    {
        return 'app_user_profile_form_type';
    }

    protected function buildUserForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', 'email', array(
                'label' => 'form.email',
                'translation_domain' => 'FOSUserBundle',
            ))
        ;
    }
}

It’s VERY important to use the translation_domain and choice_translation_domain options as seen above or this won’t work.

Then we have to create the translation files. Assuming you are following the Symfony Best Practices you have one bundle called AppBundle and its translation files, in YAML format, are in app/Resources/translations. The file name uses the locale code to designate the language, for example for spanish it would be AppBundle.es.yml. You can download a list of all the texts for english and spanish in those AppBundle.en.yml and AppBundle.es.yml files so you can use it as a template. This are some of those texts translated to spanish with our example in bold:

## app/Resources/translations/AppBundle.es.yml
# Timezone areas
Africa: "África"
America: "América"
Antarctica: "Antártida"
Arctic: "Ártico"
Asia: "Asia"
Atlantic: "Atlántico"
Australia: "Australia"
Europe: "Europa"
Indian: "India"
Pacific: "Pacífico"
# Specific timezones
Abidjan: "Abidjan"
Accra: "Accra"
Addis Ababa: "Addis Ababa"
Algiers: "Algiers"
# ... more and more timezones here ...
North Dakota - New Salem: "Dakota del Norte - New Salem"
# ... more and more timezones here ...
UTC: "UTC"

All we have to do now to see our TimezoneType Field Type translated to spanish is clear the cache ($ php app/console cache:clear --env=dev) and open our form page using the spanish locale. To force our application the spanish locale, if you aren’t already using multilanguage in your app, you can edit app/config/config.yml:

framework:
    translator:      { fallbacks: ["es"] }
    default_locale:  "es"

This is how my translated widget looks like:

translate_timezonetype

But we also want to translate the timezone stored in the database when showing it in the users profile page. Remember that we store the users timezone in its original PHP timezone string: “America/North_Dakota/New_Salem“. If we want to reuse all the translations we did before we can use this approach in Twig:

{{ (user.timezone|split("/")|first)|trans({}, 'AppBundle') }}

{{ (user.timezone|split("/", 2)|last|replace({'_': ' '})|replace({'/': ' - '}))|trans({}, 'AppBundle') }}

The first line will translate the timezone area (Africa, America, etc.) by spliting the timezone using the “/” character and then getting the first element. Becareful and remember using the opening and closing parenthesis before “|trans()” or this won’t work.

The second line is a little bit tricky. It splits the timezone string using the “/” character but limits the amount of parts to 2 and then gets the last item so we can discard the timezone area. If we take as an example “America/North_Dakota/New_Salem” we’ll have “North_Dakota/New_Salem” in this first step. Now we replace “_” with spaces and “/” with “ - “. This will convert “North_Dakota/New_Salem” to “North Dakota - New Salem” and if you look up there in the translation written in bold that’s exactly the key for the translation.

translated_timezone_text

¡HEY! ¡Don’t forget to clear your cache everytime you change or add a translation! They are not created on every request.

$ php app/console cache:clear --env=dev

Manual install and configuration of blackfire.io for Mac Ports in Mac OS X

blackfire.io is an awesome profiler for PHP done by SensioLabs, the company behind Symfony2. It automatically instruments your code to gather data about consumed server resources like memory, CPU time, and I/O. It’s very useful to find bottlenecks in your code.

blackfire.io is made of five main components:

  • The Agent is a server-side daemon that aggregates and forwards profiles to blackfire.io
  • The Probe is a PHP extension that gathers the raw performance profiles
  • The Client is a CLI tool used to trigger profiling
  • The Companion is a web browser extension used to trigger profiling
  • The Website is used to visualize the profiles

blackfire

To use it in Mac OS X with Mac Ports you have to do a manual installation. Here is the different components installation process.

Agent

Download and install it’s files:

$ curl -O http://packages.blackfire.io/binaries/blackfire-agent/1.5.1/blackfire-agent-darwin_amd64.tar.gz
$ tar xzvf blackfire-agent-darwin_amd64.tar.gz
$ sudo mv etc/blackfire /opt/local/etc/
$ sudo chown -R root:admin /opt/local/etc/blackfire
$ sudo mv usr/share/man/man1/blackfire-agent.1.gz /opt/local/share/man/man1/
$ sudo chown root:admin /opt/local/share/man/man1/blackfire-agent.1.gz
$ sudo mv usr/bin/blackfire* /opt/local/bin/
$ sudo chown root:admin /opt/local/bin/blackfire*
$ sudo mkdir -p /opt/local/var/log/blackfire
$ sudo ln -s /opt/local/etc/blackfire /usr/local/etc/blackfire

Create an agent configuration file in /opt/local/etc/blackfire/agent changing the log/socket files to the paths inside Mac Ports installation (/opt/local) and with your server id and token from https://blackfire.io/account/credentials#server:

[blackfire]
ca-cert=
collector=https://blackfire.io
log-file=/opt/local/var/log/blackfire/agent.log
log-level=1
server-id=d8108598-7a7a-4c5e-8f03-d0bccadc0931
server-token=91bde3fa9350479ba84f90acab46b680142c0f6fe8154a649e82d0d2ddadfa93
socket=unix:///opt/local/var/run/blackfire-agent.sock
spec=

Now you can run it with this command:

$ sudo blackfire-agent

But it’s better to register a service using launchctl. Create a file called /opt/local/etc/LaunchDaemons/com.sensiolabs.blackfire-agent/com.sensiolabs.blackfire-agent.plist with this content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Disabled</key>
        <false/>
        <key>KeepAlive</key>
        <true/>
        <key>Label</key>
        <string>com.sensiolabs.blackfire-agent</string>
        <key>ProgramArguments</key>
        <array>
            <string>/opt/local/bin/blackfire-agent</string>
        </array>
        <key>RunAtLoad</key>
        <false/>
    </dict>
</plist>

Make sure this file is owned by root and create an executable link to it in /Library/LaunchDaemons/com.sensiolabs.blackfire-agent.plist:

$ sudo chown root:admin /opt/local/etc/LaunchDaemons/com.sensiolabs.blackfire-agent/com.sensiolabs.blackfire-agent.plist
$ sudo ln -s /opt/local/etc/LaunchDaemons/com.sensiolabs.blackfire-agent/com.sensiolabs.blackfire-agent.plist /Library/LaunchDaemons/com.sensiolabs.blackfire-agent.plist
$ sudo chmod 755 /Library/LaunchDaemons/com.sensiolabs.blackfire-agent.plist

Load the service configuration file:

$ sudo launchctl load /Library/LaunchDaemons/com.sensiolabs.blackfire-agent.plist

And from now on you’ll be able to start and stop it as any other service:

$ sudo launchctl start com.sensiolabs.blackfire-agent
$ sudo launchctl stop com.sensiolabs.blackfire-agent

You can check if it’s really running using :

$ sudo ps x | grep blackfire
  751   ??  Ss     0:00.09 /opt/local/bin/blackfire-agent

Probe

This is the extension used in the HTTP server. You need a different binary file depending on your PHP version. This script will download and configure it for you, you only need to change the server id and token:

$ PHP_VER=`php -v | head -n 1 | awk -F ' ' '{print $2}' | tr -d . | cut -c1-2`
$ EXT_DIR=`php -i | grep 'extension_dir' | awk -F ' => ' '{print $2}'`
$ sudo curl -o ${EXT_DIR}/blackfire.so http://packages.blackfire.io/binaries/blackfire-php/1.9.2/blackfire-php-darwin_amd64-php-${PHP_VER}.so
$ sudo chmod 755 ${EXT_DIR}/blackfire.so
$ PHP_INI=`php --ini | grep "Loaded Configuration File" | awk -F ' ' '{print $4}'`
$ echo "

[blackfire]
extension=\"${EXT_DIR}/blackfire.so\"
blackfire.agent_timeout=0.25
blackfire.agent_socket=unix:///opt/local/var/run/blackfire-agent.sock
blackfire.log_file=/opt/local/var/log/blackfire/agent.log
blackfire.server_id=d8108598-7a7a-4c5e-8f03-d0bccadc0931
blackfire.server_token=91bde3fa9350479ba84f90acab46b680142c0f6fe8154a649e82d0d2ddadfa93" | sudo tee -a $PHP_INI

Restart your HTTP server (Apache in my case) and also test with this command if the extension is correctly loaded by PHP:

$ php -m | grep blackfire
blackfire

Client

The client binary file is installed with the Agent in /opt/local/bin/blackfire but you can also download and install it separately:

$ curl http://packages.blackfire.io/binaries/blackfire-agent/1.5.1/blackfire-cli-darwin_amd64 > blackfire
$ sudo chown root:admin blackfire
$ sudo mv blackfire /opt/local/bin/blackfire

Now configure the client file with the credentials from https://blackfire.io/account/credentials#client:

$ blackfire config

This will create a file in your home folder called ~/.blackfire.ini. You’ll have to update its socket value so it’s the same as the one used in the Agent configuration file:

[blackfire]
agent-socket=unix:///opt/local/var/run/blackfire-agent.sock
ca-cert=
client-id=543425c4-cb4f-3aff-c543-34d5aeeff4fd
client-token=ec843b4356997915cabb0a1b7be129e73d7c22a587103e523ea8b9a1e541b2d4
endpoint=https://blackfire.io
timeout=15s

Companion

The companion is only available for Google Chrome at the moment from here: https://chrome.google.com/webstore/detail/blackfire-companion/miefikpgahefdbcgoiicnmpbeeomffld. Install it and you’ll see a new icon in your browser that displays the different profiler slots.

Once everything is installed, configured and running just go to the URL in your server you want to profile and click “Profile!” in the Companion.

blackfire-companion

Ref: https://blackfire.io/getting-started

Doctrine ORM: Exception “Duplicate entry ‘2147483647’”

I was storing Facebook and Twitter profile IDs and I got this error:

  [Doctrine\DBAL\Driver\PDOException]
  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2147
  483647' for key 'PRIMARY'

  [PDOException]
  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2147
  483647' for key 'PRIMARY'

When I went back to my data there was no ID with 2147483647 value. Then I realized that 2147483647 is the maximun value you can store in MySQL with a Doctrine integer type. So I just changed integer to bigint in my entities and the problem was solved.

class TwitterProfile
{
    /**
     * @ORM\Id
     * @ORM\Column(type="bigint")
     */
    protected $id;
    ...
}

doctrine-mysql-duplicate-entry-2147483647