Hello everyone! As I am more and more into Symfony2 you can expect a little more about this framework from me. I’ve already added Symfony2 category on this blog, but there is only one entry, so now I’ll try to fix this a little bit. Today I would like to show you an interesting improvement to the Doctrine2 entity data import mechanism.

Photo: kopeckyk @ Fotolia.

Symfony2: Importing entity mapping data using regular expression filters.

Today’s entry title looks like scientific research report, but don’t be afraid - everything is going to be explained as simple as it is possible for me. My friends usually don’t believe me because I can elaborate several hours on the smallest topic, but when I’m writing I see how much text there is on my screen, so again, don’t worry, I’ll try to apply some self-discipline here. :)

As you know, Symfony2 comes with very handy console tool available through:

./app/console

executed in project root directory. We have a lot of options there, one of them is

doctrine:mapping:import
which enables us to import each entity data to the configuration files with specified format. Personally I like YAML files the most (mainly because I’m used to symfony 1.x “way of doing things”), so this article will rely on this format - followers of XML and annotations are kindly asked to “do no evil” to me. :)

Example of such command is as follows:

./app/console doctrine:mapping:import ThunderSampleBundle yml

It will import all database tables to entities with the same name. I won’t give any generated file examples here because this post would be too long - I highly recommend to try this on your own, because it is really nice thing to experiment. One thing I would like to mention is that the entity definition file contains fully qualified class name (with namespace). For the sample “User” table it would be:

Thunder\Bundle\SampleBundle\Entity\User

Issue.

My situation was as follows: I prepared a database structure diagram in MySQL Workbench, synchronized it with local database and then tried to import it as entites in Symfony2 project. In that moment I realized that every bundle (I had about five of them in my project) needed separate set of that entities and they need to be somewhat segregated.

Here comes “–filter” modifier of the command described above, which takes an entity name match. It can be used many times in one command to specify many names, for example:

./app/console doctrine:mapping:import ThunderSampleBundle yml --force --filter="Category" --filter="User"

The problem is that (as it will turn out later in this article) names are matched using simple strpos() function call - it means that every entity containing filtered name will be accepted, so it will include such names as Users, UserFriends, ItemCategoryImages, etc.

Solution.

Because of the fact that this “reduntant” entity name matching mechanism is rather not useful and the need to delete all unneeded files does not make things better I decided to do “something” with this issue. The solution came quickly to my mind: “what if we could filter those names by regular expressions?”. I quickly found the class implementing entity data import:
Symfony\Bundle\DoctrineBundle\Command\ImportMappingDoctrineCommand
and then I found a class:
Doctrine\ORM\Tools\Console\MetadataFilter
where I finally came across the accept() method:

    public function accept()
    {
        if (count($this->_filter) == 0) {
            return true;
        }

        $it = $this->getInnerIterator();
        $metadata = $it->current();

        foreach ($this->_filter AS $filter) {
			// echo $metadata->name.' in '.$filter."\n";
            if (strpos($metadata->name, $filter) !== false) {
            // if (strpos($metadata->name, $filter) !== false) {
                return true;
            }
        }
        return false;
    }

And guess what? I changed conditional in a foreach loop:

            if (preg_match('/'.$filter.'/', $metadata->name)) {

and… it worked like a charm! Taking to account the fact that I’ve just modified Symfony2 core, I’ve spent some time to think about possible issues and errors in other places in code, but considering two situations:

  • executing this command only with “–filter” modifier entering only entity name,
  • executing this command with regular expression filter,
I came to a conclusion that everything is going to be ok. If someone uses first approach, then match /[name]/ will be the same as strpos(’[name]‘) - that’s why there are regular expression delimiters added to filter string in modified code. If someone used that second approach… then he will use regular expression and that’s what we’ve just enabled for him. :)

Example command for the situation I described in the “Issue” section would be:

./app/console doctrine:mapping:import ThunderSampleBundle yml --force --filter="^Category" --filter="^User$"

That way it will import only entities beginning with “Category” and single entity “User”.

Of course if you see some errors here please inform me why I am wrong (and how to fix it of course :)), because I would like to submit my little “contribution” to the Symfony2 repository and I definitely don’t want to look like noob when I submit Pull Request to Fabien. :) Thanks in advance!