3

I'm new to symfony 4 and tried to write my own function for yml nelmio/alice, but after I ran bin/console doctrine:fixtures:load , I got this error:

In DeepCopy.php line 177:

The class "ReflectionClass" is not cloneable.

Here is my fixtures.yml file:

App\Entity\Post:
post_{1..10}:
    title: <customFunction()>

Here is my AppFixture.php file:

<?php

namespace App\DataFixtures;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Nelmio\Alice\Loader\NativeLoader;



class AppFixtures extends Fixture
{
    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();
    }

    public function customFunction() {

        // Some Calculations

        return 'Yep! I have got my bonus';
    }

}
Soheil
  • 1,201
  • 2
  • 11
  • 18
  • The "not clonable" comes from the fact that at some point, the library tries to deep clone your provider instance (`this`) but the class holds a reference to the manager (I had a similar issue but my _class_ held a reference to the application container) causes this error to be raised. I moved the code into an isolated provider class which solved the problem. – Stphane Apr 04 '18 at 10:12
  • That's the great trick but in my case the problem was I mixed the previous version way of defining a formatter with the last version. – Soheil Apr 04 '18 at 12:53
  • I am now stuck on another issue, maybe you could help me ? In that case please see: [this post _alice-fixtures-persist-and-reference_](https://stackoverflow.com/questions/49648923/alice-fixtures-persist-and-reference-a-first-set-of-entities-objects) Thank you for your time. – Stphane Apr 04 '18 at 13:00
  • Because that is an another topic, I posted an answer there. Hope that helps. – Soheil Apr 05 '18 at 04:26

3 Answers3

4

After investigating about nelmio/alice new version, I found the solution:

We should first create a provider which is a class that contains our new custom functions. Second, extend the NativeLoader class to register our new provider. Third, use our new NativeLoader (Here is CustomNativeLoader) which allows us to use our new formatters.

Here is the provider in CustomFixtureProvider.php:

<?php


namespace App\DataFixtures;
use Faker\Factory;

class CustomFixtureProvider
{
    public function title()
    {
        $faker = Factory::create();

        $title = $faker->text($faker->numberBetween(20,100));

        return $title;

    }
}

As a side note, the title function generates some dummy titles for your articles or posts or etc. which have dynamic length between 20..100 character. This function use Faker Alice built-in formatters.

Here is CustomNativeLoader.php:

<?php

namespace App\DataFixtures;

use Nelmio\Alice\Faker\Provider\AliceProvider;
use Nelmio\Alice\Loader\NativeLoader;
use Faker\Factory as FakerGeneratorFactory;
use Faker\Generator as FakerGenerator;

class CustomNativeLoader extends NativeLoader
{
    protected function createFakerGenerator(): FakerGenerator
    {
        $generator = FakerGeneratorFactory::create(parent::LOCALE);
        $generator->addProvider(new AliceProvider());
        $generator->addProvider(new CustomFixtureProvider());
        $generator->seed($this->getSeed());

        return $generator;
    }
}

Here is LoadFixture.php:

<?php
namespace App\DataFixtures;

use App\DataFixtures\CustomNativeLoader;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;

class LoadFixtures extends Fixture
{
    public function load(ObjectManager $manager)
    {
        $loader = new CustomNativeLoader();

        $objectSet = $loader->loadFile(__DIR__ . '/fixtures.yml')->getObjects();
        foreach($objectSet as $object) {
            $manager->persist($object);
        }
        $manager->flush();
    }
}

And finally an example for using our new title formatter:

App\Entity\Post:
    post_{1..10}:
        title: <title()>

I know this is a long way for writing a small function but according to my investigation, it's a standard way for extending the formatters in new version.

Soheil
  • 1,201
  • 2
  • 11
  • 18
  • you could simplify `createFakerGenerator ` by calling the parent one and just append the provider to it, but otherwise that's the way to go – Théo Apr 04 '18 at 15:24
  • Would you please provide me an example. Then I can improve the answer – Soheil Apr 05 '18 at 04:28
  • Looks like it is a bit out dated, I get this error: PHP TypeError: Argument 1 passed to Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader::addFixture() must implement interface Doctrine\Common\DataFixtures\FixtureInterface, instance of AppBundle\DataFixtures\CustomFixtureProvider given – TheKitMurkit Nov 05 '18 at 12:24
  • This is especially helpful for those of us NOT using symfony - but still needing a way to add new providers to Alice + Faker. The documentation on the readme in Alice: "Then you can add it to the Faker Generator used by Alice by either overriding the NativeLoader::createFakerGenerator() method." left me a bit confused. Either, .... or? What? But, seeing this, it's clear that yes, all you need to do is extend the Loader, and override the FakerGenerator method. – thexfactor Aug 22 '19 at 14:20
3

You can do it this simple way:

1.Create your own provider (for example, in /src/DataFixtures/Faker/CustomProvider.php):

 <?php    
    namespace App\DataFixtures\Faker;


    class CustomProvider
    {
        public static function customFunction()
        {
            // Some Calculations
            return 'Yep! I have got my bonus';
        }
    }

2.Register provider in config/services.yaml:

App\DataFixtures\Faker\CustomProvider:
    tags: [ { name: nelmio_alice.faker.provider } ]

That's it.

P.S. You can check for more details here: https://github.com/hautelook/AliceBundle/blob/master/doc/faker-providers.md

Sergiy Zaharchenko
  • 1,490
  • 1
  • 12
  • 11
  • 1
    in any case, this still needs to be loaded properly (passing the Faker\Generator), otherwise the new Faker provider won't be picked up. For example: `return (new NativeLoader($this->getService(Faker\Generator::class)))->loadFiles($files)->getObjects();` – Svenv Sep 22 '22 at 07:24
0

It's possible also like ( addition to the solution bellow https://stackoverflow.com/a/54116196/261058 ):

EntityServiceProvider.php
<?php

declare(strict_types=1);

namespace App\Infrastructure\FixtureProvider;

use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Faker\Generator;
use Faker\Provider\Base;

class EntityServiceProvider extends Base
{
    public function __construct(Generator $generator, private EntityManagerInterface $entityManager)
    {
        $this->unique(true);
        parent::__construct($generator);
    }

    /**
     * @template T of object
     * @param class-string<T> $class
     * @return T
     * @throws Exception
     */
    public function entityReference(string $class, string $columnName, string|int|bool $columnValue)
    {
        if (!class_exists($class)) {
            throw new Exception(sprintf('Class "%s" does not exist or it isn\'t loaded', $class));
        }
        $repository = $this->entityManager->getRepository($class);
        $foundEntity = $repository->findOneBy([$columnName => $columnValue]);

        if (!$foundEntity instanceof $class) {
            throw new Exception(sprintf('Entity "%s" with given criteria wasn\'t found', $class));
        }
        return $foundEntity;
    }
}
Service definition: service.yaml
App\Infrastructure\FixtureProvider\EntityServiceProvider:
    tags:
        - { name: 'nelmio_alice.faker.provider' }
Usage in fixtures yaml
App\Domain\Location\Address:
    address0:
        id: '<numberBetween(3000,99999)>'
        country: '<entityReference(App\Domain\Location\Country, code, DE)>'
loading objects from fixture yaml
protected function loadObjectsFromFixtures(array $files): array
    {
        return (new NativeLoader($this->getService(Faker\Generator::class)))->loadFiles($files)->getObjects();
    }
Svenv
  • 369
  • 1
  • 3
  • 14