If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
The Faker name()
function gives us a poor genus name. Yea, I know - this is just
fake data - but it's so wrong that it fails at its one job: to give us some
somewhat realistic data to make development easy.
Here's our goal: use a new <genus()>
function in Alice and have this return the
name of a random ocean-bound genus:
AppBundle\Entity\Genus: | |
genus_{1..10}: | |
name: <genus()> | |
... lines 4 - 7 |
This shouldn't work yet - but try it to see the error:
./bin/console doctrine:fixtures:load
Unknown formatter "genus"
Faker calls these functions "formatters". Can we create our own formatter? Absolutely.
In LoadFixtures
, break the load()
call onto multiple lines to keep things short
and civilized. Now, add a third argument - it's sort of an "options" array. Give
it a key called providers
- these will be additional objects that provide formatter
functions - and set it to an array with $this
:
... lines 1 - 9 | |
class LoadFixtures implements FixtureInterface | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
$objects = Fixtures::load( | |
__DIR__.'/fixtures.yml', | |
$manager, | |
[ | |
'providers' => [$this] | |
] | |
); | |
} | |
... lines 22 - 45 | |
} |
And we're nearly done! To add a new genus
formatter, add public function genus()
.
I've already prepared a lovely list of some fantastic genuses that live in the ocean:
... lines 1 - 9 | |
class LoadFixtures implements FixtureInterface | |
{ | |
... lines 12 - 22 | |
public function genus() | |
{ | |
$genera = [ | |
'Octopus', | |
'Balaena', | |
'Orcinus', | |
'Hippocampus', | |
'Asterias', | |
'Amphiprion', | |
'Carcharodon', | |
'Aurelia', | |
'Cucumaria', | |
'Balistoides', | |
'Paralithodes', | |
'Chelonia', | |
'Trichechus', | |
'Eumetopias' | |
]; | |
... lines 41 - 44 | |
} | |
} |
Finish this with $key = array_rand($genera)
and then return $genera[$key]
:
... lines 1 - 9 | |
class LoadFixtures implements FixtureInterface | |
{ | |
... lines 12 - 22 | |
public function genus() | |
{ | |
... lines 25 - 41 | |
$key = array_rand($genera); | |
return $genera[$key]; | |
} | |
} |
Let's give it a try:
./bin/console doctrine:fixtures:load
No errors! Refresh! Ah, so much better.
Now, hold on, we have a new requirement: we need the ability to have published and unpublished genuses - for those times when we create a new genus, but we're still trying to think of a fun fact before it shows up on the site. With our beautiful migration and fixtures systems, this will be a breeze.
First, open Genus
and add a new private property
- call it $isPublished
. Next,
use the "Code"->"Generate" shortcut - or Ctrl
+Enter
- to generate the annotations:
... lines 1 - 10 | |
class Genus | |
{ | |
... lines 13 - 39 | |
/** | |
* @ORM\Column(type="boolean") | |
*/ | |
private $isPublished = true; | |
... lines 44 - 89 | |
public function setIsPublished($isPublished) | |
{ | |
$this->isPublished = $isPublished; | |
} | |
} |
Hey that was cool! Because the property started with is
, PhpStorm correctly guessed
that this is a boolean
column. Go team!
At the bottom, generate just the setter function. We can add a getter function later... if we need one.
We need to update the fixtures. But first, find the command line and generate the migration:
./bin/console doctrine:migrations:diff
Be a responsible dev and make sure the migration looks right:
... lines 1 - 10 | |
class Version20160207083347 extends AbstractMigration | |
{ | |
/** | |
* @param Schema $schema | |
*/ | |
public function up(Schema $schema) | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.'); | |
$this->addSql('ALTER TABLE genus ADD is_published TINYINT(1) NOT NULL'); | |
} | |
/** | |
* @param Schema $schema | |
*/ | |
public function down(Schema $schema) | |
{ | |
// this down() migration is auto-generated, please modify it to your needs | |
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.'); | |
$this->addSql('ALTER TABLE genus DROP is_published'); | |
} | |
} |
Actually, it looks perfect. Run it:
./bin/console doctrine:migrations:migrate
Last step: we want to have a few unpublished genuses in the random data set. Open
the Faker documentation and search for "boolean". Perfect! There's a built-in boolean()
function and we can control the $chanceOfGettingTrue
. In the fixtures file,
add isPublished
and set that to boolean(75)
- so that most genuses are published:
AppBundle\Entity\Genus: | |
genus_{1..10}: | |
... lines 3 - 6 | |
isPublished: <boolean(75)> |
Re-run the fixtures!
./bin/console doctrine:fixtures:load
Hey, no errors! Now, to only show the published genuses on the list page, we need a custom query.
Hi back to you mate! :) And thanks for the nice words - I love it!
Ok, the error itself is easy to read, but the cause is not so easy - you are correctly "confused" about why this would suddenly happen :). As you can see by looking at the query, a null value *is* being passed to the name field in the query (this is the "with params [null...]" part).
I can also see the is_published is receiving a value of 1 - which is totally normal. Ok, so why is this happening? Obviously, if you simply added a new isPublished key to the fixtures.yml file, it should *not* be happening - the changes should be unaffected. So, that's the mystery. Here's what I would do: add a var_dump($name) inside your setName() function. If you see the correct value dumped (a string, not null) , then we know that this method is being called (and that the problem is elsewhere). If you see null dumped, we know that we have a problem in fixtures.yml - something is telling Alice to call this with null. If you do *not* see the method called, then the problem is probably also in fixtures.yml - we are missing something that would cause setName to be called. Obviously, this would happen if the "name" key were missing from fixtures.yml, but I'm guessing you checked for that ;).
Anyways, do a little digging and let me know what you find out - I'm happy to help.
Cheers!
Hey guys, quick doctrine migration question. Is there a way to specify executing the last created diff? If there are old diffs in my DoctrineMigrations directory and I don't delete them it tries to run those before the latest diff. Is that normal behavior?
Hey Todd,
Quick answer: to know what diff is the latest use: "bin/console doctrine:migrations:latest" which give you YYYYMMDDHHMMSS ID of the latest migration, and then with "bin/console doctrine:migrations:execute YYYYMMDDHHMMSS" execute it.
But seriously, it sounds as normal behavior. You need to understand how migrations work: Doctrine stores all the IDs of executed migrations in your DB, that's how Doctrine know what migrations were executed in your system. So when you add more migrations and run "bin/console doctrine:migrations:migrate", Doctrine look into that table and execute only those which are not in that table yet, i.e. execute fresh migrations which have not been executed yet. But if you want to skip a migration for some reasons, you can do it with "bin/console doctrine:migrations:version YYYYMMDDHHMMSS" - use "--add" or "--delete" options to add the current migration to or delete from the table.
This docs could be useful for you I think: https://symfony.com/doc/mas... . Also, executing "bin/console list doctrine:migrations" will show you available commands and some description about each command - good way to quickly understand capabilities of the bundle.
Cheers!
I need to generate a value that is less than the value faker has generated in another field. If I create a custom provider, how do I pass in the value that faker has created for the other field? i.e
stock: <numberbetween(100,5000)> //Produces 823
reorderQty: <customreorderqty($stock)> //Would return a value less than 823
Thanks
Sorry, I just answered my own question. The above code works, just use a $fieldName to refer to a field value in the same record
Hi Guys. First of all great work; love your tutorials. I was trying to code along using latest Alice version and obviously run into buch of problems. I tried to use there solution, however I couldn't use <i>$this->container</i> inside <em>LoadFixtures.php</em>; duno why :/ So I played with it for couple hours and came up with this:
<strong>1.</strong> I read <a>https://github.com/nelmio/alice/blob/master/doc/customizing-data-generation.md#custom-faker-data-providers</a> and based on docs created my <i>GenusProvider</i> class like this:
namespace AppBundle\DataFixtures\ORM;
class GenusProvider
{
public function genus()
{
$genera = [
'Octopus',
'Baleana',
'Orcinus',
'Hippocampus',
'Asterias',
'Amphiprion',
'Carcharodon',
'Aurelia',
'Cucumaria',
'Balistoides',
'Paralithodes',
'Chelonia',
'Trichechus',
'Eumetopias'
];
$key = array_rand($genera);
return $genera[$key];
}
}
If you look carefully, you can see I didn't extend BaseProvider. I tried but it didn't work in my case.
<strong>2.</strong> Then you should:
<blockquote>Then you can add it to the Faker Generator used by Alice by either overriding the NativeLoader::createFakerGenerator() method.</blockquote>
This was the hardest part because in tutorial we don't mess with the loader class. This is where I spent most of the time. My solution is to extend the <i>NativeLoader</i> class like this:
namespace AppBundle\DataFixtures\ORM;
use Nelmio\Alice\Loader\NativeLoader;
use Faker\Factory as FakerGeneratorFactory;
use Faker\Generator as FakerGenerator;
class NewNativeLoader extends NativeLoader
{
protected function createFakerGenerator(): FakerGenerator
{
$generator = FakerGeneratorFactory::create(parent::LOCALE);
$generator->addProvider(new GenusProvider());
$generator->seed($this->getSeed());
return $generator;
}
}
As you can see this the place where all the add-provider-magic happens. I overrode <i>createFakerGenerator</i> method and used new fance <i>GenusProvider</i>
<strong>3. </strong> Now let's go back to original <i>LoadFixtures.php</i>. Instead of using <i>NativeLoader</i> i used my <i>NewNativeLoader</i>. Code looks like this:
namespace AppBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class LoadFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
$loader = new NewNativeLoader();
$objectSet = $loader->loadFile(__DIR__.'/fixtures.yml')->getObjects();
foreach($objectSet as $object) {
$manager->persist($object);
}
$manager->flush();
}
}
I bet there is a better way to solve this; mine just works ;)
Hey Maciek,
Thanks for sharing it with others. Also, keep in mind that we're using "nelmio/alice" of version v2.1 in this tutorial. If you install the latest bundle - it has some BC breaks and its usage may be different than that we're showing in this screencast.
Cheers!
Why is my phpstorm adding comments for the return/parameter type when I create a getter or setter using Code Generation? Abd its only calling the type "mixed". Not boolean, for example even though it's an "isPublished" type format for the name. I see your PHPStorm doesn't produce these php doc type comments. eg
/**
* @param mixed $isPublished
*/
public function setIsPublished($isPublished)
{
$this->isPublished = $isPublished;
}
Hey Richard ,
Well, it depends on PhpStorm version. Btw, do you use @var
notation for class properties? PhpStorm based on var type, try to generate setter/getter with this code:
class YourEntityName
{
/**
* @var bool
* @ORM\Column(...)
*/
private $isPublished;
}
When you specify var types explicitly, PhpStorm will use it in annotation for setters/getters.
Cheers!
If anyone is using Alice 3.0, you will need to create a GenusProvider class based off https://github.com/nelmio/alice/blob/master/doc/customizing-data-generation.md#custom-faker-data-providers. I stuck it in the same folder as my LoadFixtures.php so it had the namespace AppBundle\DataFixtures\ORM
and dropped the genus()
function in there.
You also need to changed services.yml
to get Alice to pick up the new provider. Under the services
:
AppBundle\DataFixtures\ORM\GenusProvider:
tags: [nelmio_alice.faker.provider]
After that run ./bin/console doctrine:fixtures:load
again and it should be working OK.
Yo Syed!
This is awesome! Thanks for posting this! We're going to use your suggestions here to make an official note that we can add to the tutorial to help others :).
Cheers!
Ok, I've adjusted the services.yml file as above and created the GenusProvider class. However, I'm getting an error.
Tried this:
public function load(ObjectManager $manager)
{
$loader = new NativeLoader(null, ['providers' => [$this]]);
$objectSet = $loader->loadFile(__DIR__.'/fixtures.yml')->getObjects();
foreach($objectSet as $object) {
$manager->persist($object);
}
$manager->flush();
}
..and this:
public function load(ObjectManager $manager)
{
$loader = new NativeLoader();
$objectSet = $loader->loadFile(__DIR__.'/fixtures.yml',
['providers' => [$this]])->getObjects();
foreach($objectSet as $object) {
$manager->persist($object);
}
$manager->flush();
}
Same result:
[InvalidArgumentException]
Unknown formatter "genus"
Hey!
As Authoritas already mentioned, it's probably best to just stick to Alice 2 for now.
However, I have uploaded my working version to GitHub - https://github.com/sjhuda/knp-aqua_note. Everything should be in the same place as the videos so far, it might be worth comparing my code to what you have.
I just ran it right now and I get
➜ aqua_note ./bin/console doctrine:fixtures:load
Careful, database will be purged. Do you want to continue y/N ?y
> purging database
> loading Doctrine\Bundle\FixturesBundle\EmptyFixture
> loading AppBundle\DataFixtures\ORM\LoadFixtures
Oh, I also forgot to mention that I switched over to using the service that comes with Alice 3 (as opposed to the NativeLoader class), that might be causing your issue (see my comment on the previous video).
Thanks for sharing it!
We added a note to the script so people don't get confused anymore
Cheers!
I tried your code for Alice 3.1, but it threw strange error.
A "tags" entry must be an array for service "AppBundle\DataFixtures\ORM\GenusProvider" in /home/dd/dd/start/app/config/services.yml. Check your YAML syntax.
Do you have idea how to solve this?
Hey Danijela
How did you define the Provider service? I think you only have to sorround the tags property with `[` `]`
Cheers!
Authoritas can check the codes which Authoritas shared on git. its working with Alice 3. just copy the codes from fixtures.yml GenusProvider.php and LoadFixtures.php and paste them urs. and add the service. its working for me
Hey Authoritas
I assume you are using Alice 3. That version contains many changes, actually the developers said that it shares nothing to version 2, even the persistent layer was removed
I can advice you to use in addition AliceBundle (hautelook/alice-bundle) so you can persist your objects
I hope it helps you :)
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}
hey mate. Thanks alot for this video series, its really great! during the very last step after i add the private $isPublished colums, i add the setter public function setIsPublished($isPublished). wehn i run the php bin/console doctrine:fixtures:load command i get the following error.
PS D:\xampp\htdocs\aqua_note> php bin/console doctrine:fixtures:load
Careful, database will be purged. Do you want to continue y/N ?y
> purging database
> loading AppBundle\DataFixtures\ORM\LoadFixtures
[Doctrine\DBAL\Exception\NotNullConstraintViolationException]
An exception occurred while executing 'INSERT INTO genus (name, sub_family, species_count, fun_fact, is_published)
VALUES (?, ?, ?, ?, ?)' with params [null, "Culpa consequatur.", 93263, "Accusamus nihil repellat vero omnis volupt
ates id amet et.", 1]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
[Doctrine\DBAL\Driver\PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
[PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
doctrine:fixtures:load [--fixtures [FIXTURES]] [--append] [--em EM] [--shard SHARD] [--purge-with-truncate] [--multiple-
transactions] [-h|--help] [-q|--quiet] [-v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [
-e|--env ENV] [--no-debug] [--] <command>
why does is it trying to make name null at all, we only added that nullable=true stipulation to the $funFact column
also it did not do this until i added the isPublished column, before that it worked fine