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!

  • 2017-10-16 Victor Bocharsky

    Hey Mesut,

    Thanks for confirming that solution works for you, I'm glad to hear it.

    Cheers!

  • 2017-10-14 Mesut Hazen

    Matt O'Toole can check the codes which Syed 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

  • 2017-09-27 Diego Aguiar

    Thanks for sharing it!

    We added a note to the script so people don't get confused anymore

    Cheers!

  • 2017-09-26 Syed

    Hey!

    As Diego Aguiar 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/k.... 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).

  • 2017-09-26 Diego Aguiar

    Hey Matt O'Toole

    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!

  • 2017-09-26 Matt O'Toole

    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"
  • 2017-09-21 weaverryan

    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!

  • 2017-09-20 Syed

    If anyone is using Alice 3.0, you will need to create a GenusProvider class based off https://github.com/nelmio/a.... 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.

  • 2017-07-26 Victor Bocharsky

    Hey Simon,

    Glad you got it working so fast :)

    Cheers!

  • 2017-07-26 Simon Carr

    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

  • 2017-07-26 Simon Carr

    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

  • 2017-02-21 Victor Bocharsky

    Hey maxii123 ,

    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!

  • 2017-02-20 maxii123

    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;
    }

  • 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