Buy

Tip

A newer version of HauteLookAliceBundle has been released and portions of this tutorial won't apply to that new version.

There's a little issue. These two kittens are the exact same filename. The first is kitten2.jpg and so is the second. That's fine for us, but imagine if we could delete characters, and if doing that deleted the image. If we deleted this character, it would delete the image for the second one too. To be more realistic, each character needs a unique image.

NO problem. Setup a new $targetFilename instead of using the original filename. Set it to fixtures_ then mt_rand() and .jpg:

49 lines src/AppBundle/DataFixtures/ORM/AvatarProcessor.php
... lines 1 - 15
public function preProcess($object)
{
... lines 18 - 26
$targetFilename = 'fixtures_'.mt_rand(0, 100000).'.jpg';
... lines 28 - 36
}
... lines 38 - 49

Copy the file to this filename. And make sure that the avatarFilename is our new, random thing:

49 lines src/AppBundle/DataFixtures/ORM/AvatarProcessor.php
... lines 1 - 15
public function preProcess($object)
{
... lines 18 - 26
$targetFilename = 'fixtures_'.mt_rand(0, 100000).'.jpg';
... lines 28 - 29
$fs->copy(
$projectRoot.'/resources/'.$object->getAvatarFilename(),
$projectRoot.'/web/uploads/avatars/'.$targetFilename,
true
);
$object->setAvatarFilename($targetFilename);
}
... lines 38 - 49

Time to reload those fixtures:

php app/console doctrine:fixtures:load

In web/uploads/avatars, we see a bunch of random filenames. And when we refresh, they're all using different filenames.

Accessing the container in a Processor

You can do whatever you want inside a Processor, but with a glaring limitation so far: you don't have access to the container or any of your services.

Let's try to log the random filenames being used for each Character. That means we'll need the logger service, and right now we don't have access to anything. To get it, we'll treat AvatarProcessor like any other service and use dependency injection. Create a __construct() function, and type-hint the argument with LoggerInterface from PSR. That'll add my use statement. Now, set that on a logger property:

63 lines src/AppBundle/DataFixtures/ORM/AvatarProcessor.php
... lines 1 - 6
use Psr\Log\LoggerInterface;
... lines 8 - 9
class AvatarProcessor implements ProcessorInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
... lines 18 - 61
}

Before worrying about how we'll pass in the logger, go down below and log a debug message. Fill in the placeholders with the object's name, the $targetFilename and then the original avatarFilename:

63 lines src/AppBundle/DataFixtures/ORM/AvatarProcessor.php
... lines 1 - 9
class AvatarProcessor implements ProcessorInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
... lines 18 - 23
public function preProcess($object)
{
... lines 26 - 43
$this->logger->debug(sprintf(
'Character %s using filename %s from %s',
$object->getName(),
$targetFilename,
$object->getAvatarFilename()
));
$object->setAvatarFilename($targetFilename);
}
... lines 52 - 61
}

This class is not registered as a service - we just create it manually in AppFixtures:

56 lines src/AppBundle/DataFixtures/ORM/AppFixtures.php
... lines 1 - 7
class AppFixtures extends DataFixtureLoader
{
... lines 10 - 48
protected function getProcessors()
{
return array(
new AvatarProcessor()
);
}
}

Passing the logger in is simple. The base DataFixturesLoader class has the container and puts it on a $container property, just like a Controller. So we can say $this->container->get('logger'):

56 lines src/AppBundle/DataFixtures/ORM/AppFixtures.php
... lines 1 - 7
class AppFixtures extends DataFixtureLoader
{
... lines 10 - 48
protected function getProcessors()
{
return array(
new AvatarProcessor($this->container->get('logger'))
);
}
}

To test this out, open up a new tab and let's tail the app/logs/dev.log directory, because app/console runs in the dev environment by default. And let's grep it for the word Character:

tail -f app/logs/dev.log | grep "Character"

Now reload the fixtures!

php app/console doctrine:fixtures:load

No errors, AND we get our log messages. Btw, you can also see log messages directly when running a command by passing the -vvv option:

php app/console doctrine:fixtures:load -vvv

This can be pretty handy.

This means that there's nothing you can't do with a Processor. Need a service? Just use normal dependency injection, pass it in, do awesome things with your fixtures, then celebrate.

Cheers!

Leave a comment!

  • 2015-06-03 weaverryan

    BEST COMMENT EVER :).

    Yea, it's ignored by VCS, so I wasn't too worried about it. But an alternative to letting the directory fill up with time would be to automate the proverbial burlap sack, and delete the directory automatically before loading your fixtures (the Filesystem class can remove the directory, then you can re-create it). You could do that in getFixtures() or override load(), do this, then call parent::load() if that feels a bit better.

    Cheers!

  • 2015-06-03 Michael Sypes

    What about all the built-up cruft you'd end generating over time in that files directory, i.e., the "kindle" of kitten photos?
    Or is this of no concern because such a directory would be ignored by your VCS, and you can easily just overwrite as you go? The alternative would be the need to periodically go through the directory with a burlap sack and cinder block, which would be as horrid as the image I've just used.