Buy

Custom Alice Faker Function

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:

7 lines src/AppBundle/DataFixtures/ORM/fixtures.yml
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.

Adding a Custom Formatter (Function)

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:

46 lines src/AppBundle/DataFixtures/ORM/LoadFixtures.php
... 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:

46 lines src/AppBundle/DataFixtures/ORM/LoadFixtures.php
... 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]:

46 lines src/AppBundle/DataFixtures/ORM/LoadFixtures.php
... 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.

New Random Boolean Column

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:

95 lines src/AppBundle/Entity/Genus.php
... 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:

35 lines app/DoctrineMigrations/Version20160207083347.php
... 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:

8 lines src/AppBundle/DataFixtures/ORM/fixtures.yml
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.

Leave a comment!

  • 2016-05-04 weaverryan

    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!

  • 2016-05-04 Kosta Andonovski

    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