If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
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
:
... 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:
... 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.
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:
... 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
:
... 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
:
... 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')
:
... 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!
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!
Is there an easy thing to only have it load fixtures for certain environments?
I.E. My app has fixtures that it needs to add in order to function. When I load it on prod (or even stage) I'll need to add those fixtures, but I'd also like to be able to load a bunch of fixtures for testing in the dev/test environment.
Thanks!
Hey Matt,
Loading fixtures in production is not a good practice, well, because you probably have a real data in the DB, so overwriting it would be fatal :) That's why fixtures bundle usually load for dev/test environments only in kernel.
Anyway, not sure it's possible with Alice bundle, but it's possible with DoctrineFixturesBundle, where you can easily access container from the fixtures: https://symfony.com/doc/mas... - which means you can get current environment like: $env = $this->container->getParameter("kernel.environment"); . So with simple if and return statements in the beginning of the fixtures file you can skip some fixtures for some environment.
Cheers!
Haha Yeah I know. Maybe what I'm referring to isn't fixtures? It's data that has to be there for the app to function...
Hey Matt,
Yeah, it sounds like fixtures... but this data already should be on production. What I mean is that you probably do not want to reload those data on production because you'll lose real data. The only valid case I see is when you want to do the first deploy to production. But on the second, 3rd, etc. deploy those data already *should* be there. But if you need to inject more data with further deploys - you probably talking about some kind of migrations.
Cheers!
> The only valid case I see is when you want to do the first deploy to production.
Yeah,t hat's the case I'm thinking about. Migrations is an interesting idea...
Hey Matt,
So yes, most of the time it's easier to manually load those data, probably you can just export / import the data. It'd not be 100% automated, but I think it's OK to start with it. Or, you can even create a one-time migration command, that you'll execute on the production after the first deploy and then remove it on the next deploy, but once again I do not think it's worth it. I'd prefer to manually import initial data after the first deploy.
Cheers!
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "2.5.*", // v2.5.5
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.6
"doctrine/doctrine-bundle": "~1.2", // v1.2.0
"twig/extensions": "~1.0", // v1.1.0
"symfony/assetic-bundle": "~2.3", // v2.5.0
"symfony/swiftmailer-bundle": "~2.3", // v2.3.7
"symfony/monolog-bundle": "~2.4", // v2.6.1
"sensio/distribution-bundle": "~3.0", // v3.0.6
"sensio/framework-extra-bundle": "~3.0", // v3.0.2
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"hautelook/alice-bundle": "~0.1" // 0.1.5
},
"require-dev": {
"sensio/generator-bundle": "~2.3" // v2.4.0
}
}
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.