Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Fundición: Accesorios que te encantarán

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Construir accesorios es bastante sencillo, pero algo aburrido. Y sería súper aburrido crear manualmente 25 mezclas dentro del método load(). Por eso vamos a instalar una impresionante biblioteca llamada "Foundry". Para ello, ejecuta:

composer require zenstruck/foundry --dev

Ejecuta: --dev porque sólo necesitamos esta herramienta cuando estamos desarrollando o ejecutando pruebas. Cuando termine, ejecuta

git status

para ver que la receta ha habilitado un bundle y también ha creado un archivo de configuración... que no necesitaremos mirar.

Fábricas: make:factory

En resumen, Foundry nos ayuda a crear objetos de entidad. Es... casi más fácil verlo en acción. En primer lugar, para cada entidad de tu proyecto (ahora mismo, sólo tenemos una), necesitarás una clase fábrica correspondiente. Créala ejecutando

php bin/console make:factory

que es un comando Maker que viene de Foundry. Luego, puedes seleccionar para qué entidad quieres crear una fábrica... o generar una fábrica para todas tus entidades. Nosotros generaremos una para VinylMix. Y... eso creó un único archivo: VinylMixFactory.php. Vamos a comprobarlo:src/Factory/VinylMixFactory.php.

... lines 1 - 10
/**
* @extends ModelFactory<VinylMix>
*
* @method static VinylMix|Proxy createOne(array $attributes = [])
* @method static VinylMix[]|Proxy[] createMany(int $number, array|callable $attributes = [])
... lines 16 - 27
*/
final class VinylMixFactory extends ModelFactory
{
... lines 31 - 37
protected function getDefaults(): array
{
return [
// TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories)
'title' => self::faker()->text(),
'trackCount' => self::faker()->randomNumber(),
'genre' => self::faker()->text(),
'votes' => self::faker()->randomNumber(),
'slug' => self::faker()->text(),
'createdAt' => null, // TODO add DATETIME ORM type manually
'updatedAt' => null, // TODO add DATETIME ORM type manually
];
}
... lines 51 - 63
}

¡Qué bien! Encima de la clase, puedes ver que se describen un montón de métodos... que ayudarán a nuestro editor a saber qué superpoderes tiene esto. Esta fábrica es realmente buena para crear y guardar objetos VinylMix... o para crear muchos de ellos, o para encontrar uno al azar, o un conjunto al azar, o un rango al azar. ¡Uf!

getDefaults()

El único código importante que vemos dentro de esta clase es getDefaults(), que devuelve los datos por defecto que deben utilizarse para cada propiedad cuando se crea un VinylMix. Hablaremos más de eso en un minuto.

Pero antes... ¡vamos a avanzar a ciegas y a utilizar esta clase! En AppFixtures, borra todo y sustitúyelo por VinylMixFactory::createOne().

... lines 1 - 5
use App\Factory\VinylMixFactory;
... lines 7 - 9
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
VinylMixFactory::createOne();
$manager->flush();
}
}

¡Ya está! Gira y vuelve a cargar los accesorios con:

symfony console doctrine:fixtures:load

Y... ¡falla! Boo

Tipo de argumento esperado "DateTime", "null" dado en la ruta de la propiedad "createdAt"

Nos está diciendo que algo intentó llamar a setCreatedAt() en VinylMix... pero en lugar de pasar un objeto DateTime, pasó null. Hmm. Dentro deVinylMix, si te desplazas hacia arriba y abres TimestampableEntity, ¡sí! Tenemos un métodosetCreatedAt() que espera un objeto DateTime. Algo llamado así... pero que pasa null.

Esto ayuda a mostrar cómo funciona Foundry. Cuando llamamos aVinylMixFactory::createOne(), crea un nuevo VinylMix y luego le pone todos estos datos. Pero recuerda que todas estas propiedades son privadas. Así que no establece la propiedad del título directamente. En su lugar, llama a setTitle() y setTrackCount()Aquí abajo, para createdAt y updatedAt, llamó a setCreatedAt()y le pasó a null.

En realidad, no necesitamos establecer estas dos propiedades porque las establecerá automáticamente el comportamiento timestampable.

Si probamos esto ahora...

symfony console doctrine:fixtures:load

¡Funciona! Y si vamos a ver nuestro sitio... impresionante. Esta mezcla tiene 928.000 pistas, un título aleatorio y 301 votos. Todo esto proviene del método getDefaults().

Datos falsos con Faker

Para generar datos interesantes, Foundry aprovecha otra biblioteca llamada "Faker", cuyo único trabajo es... crear datos falsos. Así que si quieres un texto falso, puedes decir self::faker()->, seguido de lo que quieras generar. Hay muchos métodos diferentes que puedes invocar en faker() para obtener todo tipo de datos falsos divertidos. ¡Es muy útil!

Creando muchos objetos

Nuestra fábrica ha hecho un trabajo bastante bueno... pero vamos a personalizar las cosas para hacerlas un poco más realistas. En realidad, en primer lugar, tener un VinylMix sigue sin ser muy útil. Así que, dentro de AppFixtures, cambia esto por createMany(25).

... lines 1 - 11
public function load(ObjectManager $manager): void
{
VinylMixFactory::createMany(25);
... lines 15 - 16
}
... lines 18 - 19

Aquí es donde Foundry brilla. Si ahora recargamos nuestras instalaciones:

symfony console doctrine:fixtures:load

Con una sola línea de código, ¡tenemos 25 accesorios aleatorios con los que trabajar! Sin embargo, los datos aleatorios podrían ser un poco mejores... así que vamos a mejorar eso.

Personalizar getDefaults()

Dentro de VinylMixFactory, cambia el título. En lugar de text() -que a veces puede ser un muro de texto-, cambia a words()... y utiliza 5 palabras, y pasa true para que lo devuelva como una cadena. De lo contrario, el método words() devuelve una matriz. Para trackCount, sí queremos un número aleatorio, pero... probablemente un número entre 5 y 20. Para genre, vamos a buscar un randomElement() para elegir aleatoriamente pop o rock. Esos son los dos géneros con los que hemos trabajado hasta ahora. Y, vaya... asegúrate de llamar a esto como una función, ya está. Por último, para votes, elige un número aleatorio entre -50 y 50.

... lines 1 - 28
final class VinylMixFactory extends ModelFactory
{
... lines 31 - 37
protected function getDefaults(): array
{
return [
'title' => self::faker()->words(5, true),
'trackCount' => self::faker()->numberBetween(5, 20),
'genre' => self::faker()->randomElement(['pop', 'rock']),
'votes' => self::faker()->numberBetween(-50, 50),
'slug' => self::faker()->text(),
];
}
... lines 48 - 60
}

¡Mucho mejor! Ah, y puedes ver que make:factory ha añadido aquí un montón de nuestras propiedades por defecto, pero no las ha añadido todas. Una de las que falta esdescription. Añádela: 'description' => self::faker()-> y luego usa paragraph(). Por último, para slug, no la necesitamos en absoluto porque se establecerá automáticamente.

... lines 1 - 37
protected function getDefaults(): array
{
return [
... line 41
'description' => self::faker()->paragraph(),
... lines 43 - 45
];
}
... lines 48 - 62

¡Ufff! ¡Vamos a probarlo! Recarga los accesorios:

symfony console doctrine:fixtures:load

Luego dirígete y actualiza. Esto se ve mucho mejor. Tenemos una imagen rota... pero eso es sólo porque la API que estoy utilizando tiene algunas "lagunas"... nada de lo que preocuparse.

Foundry puede hacer un montón de cosas interesantes, así que no dudes en consultar su documentación. Es especialmente útil cuando se escriben pruebas, y funciona muy bien con las relaciones de la base de datos. Así que lo volveremos a ver de forma más compleja en el próximo tutorial.

A continuación, ¡vamos a añadir la paginación! Porque, al final, no podremos listar todas las mezclas de nuestra base de datos de una sola vez.

Leave a comment!

15
Login or Register to join the conversation
Rufnex Avatar
Rufnex Avatar Rufnex | posted hace 4 meses | edited

Hey, great library. I need a bit of help with localization. If I want another language, how to do this? In the documentation I see

Faker\Factory::create('fr_FR');

But how to set this in Symfony? Thank you!

Reply

Hey Rufnex,

Good question! If you're talking about how to localize Faker with the Foundry bundle - here's an example: https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#faker . As you can see from the docs, you can do it via Symfony configuration:

# config/packages/zenstruck_foundry.yaml
when@dev: # see Bundle Configuration section about sharing this in the test environment
    zenstruck_foundry:
        faker:
            locale: fr_FR # set the locale

I hope this helps!

Cheers!

Reply
Rufnex Avatar

Hi Victor,

i had already tried this. unfortunately, only the standard language is used. Any further ideas?

Greetings
Rufnex

Reply

Hey Rufnex,

Hm, could you clear the cache and try again? It might be a cache issue. If not, please, make sure your current config really has that locally, you can debug it with:

bin/console debug:config zenstruck_foundry

Is the locale set there? Also, make sure you're debugging this config for the environment that your Symfony application is currently using. It might be so that you set the locale for dev but load your app in prod.

Cheers!

Reply
Rufnex Avatar

HeyHo Victor,

the configuration looks correct, but still only the default language like "et accusamus sunt ipsum non". strange behavioer .. or is it required to download somewhere the language packs?

Current configuration for extension with alias "zenstruck_foundry"
==================================================================

zenstruck_foundry:
    auto_refresh_proxies: true
    faker:
        locale: fr_FR
        seed: null
        service: null
    instantiator:
        without_constructor: false
        allow_extra_attributes: false
        always_force_properties: false
        service: null
        ```
Reply

Hey Rufnex,

Ah, ok, it seems correct to me. Well, you don't need to install any extra localization pack, it's implemented via so called Provider in the faker bundle: https://github.com/FakerPHP/Faker/tree/main/src/Faker/Provider - you just need to specify a correct provider for the "locale" option I suppose. OK, could you try that Faker\Factory::create('fr_FR') instead as described in the docs and try to generate data with it? Are those data really localized? If so, it sounds like a misconfiguration or a bug in the Foundry bundle, I'd recommend you to open an issue there.

Btw, keep in mind that not all the data might be translated. Please, try on the specific methods like name(), region(), vat(), or phoneNumber() as it's listed as translated in the docs: https://fakerphp.github.io/locales/fr_FR/ . It might be so that the method you're calling always returns Latin text.

I hope this helps!

Cheers!

Reply
Rufnex Avatar

Sorry, can you explain me, where to add Faker\Factory::create('fr_FR') ? i'm a bit confused ;)

Reply
Ruslan Avatar
Ruslan Avatar Ruslan | Rufnex | posted hace 3 meses | edited

I think Victor talk about something like this:

    protected function getDefaults(): array
    {
        $faker = Factory::create('fr_FR');
        return [
            'title' => $faker->lastName(),
//            'title' => self::faker()->words(5, true),
            'description' => self::faker()->paragraph(),
            'trackCount' => self::faker()->numberBetween(5, 20),
            'genre' => self::faker()->randomElement(['pop', 'rock']),
            'votes' => self::faker()->numberBetween(-50, 50),
        ];
    }
1 Reply
Rufnex Avatar

Thanks you! Unfortunately, only latain is produced here as well.

Reply

Hey Rufnex,

Hm, that's weird if even with the Faker\Factory::create('fr_FR') you still get latin data. What method are you. calling on that custom localized faker? Could you show a part of your code where you're using it? Because as far as I understand your code it should just work this way, probably you're missing something obvious.

Cheers!

Reply

Hey Rufnex,

Yep, exactly like Ruslan mentioned below in https://symfonycasts.com/screencast/symfony-doctrine/foundry#comment-27727

I.e. you create a faker instance manually via $faker = Factory::create('fr_FR'); passing the desired locale and then use it below.

Cheers!

Reply
Rufnex Avatar

I followed the example .. so in my factory i have this code:

    protected function getDefaults(): array
    {
        $faker = Factory::create('fr_FR');
        return [
            // TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories)
            'title' => $faker->words(5, true),
            'description' => $faker->text(),
            'hits' => self::faker()->randomNumber(),
        ];
    }
Reply

Hey Rufnex,

Oh, that's exactly what is in the docs actually, I'm not sure why it does not work. Well, the only explanation might be is that those methods you're using like words()/text() are not localized to French. I'd recommend you to change that code to something like this:

    protected function getDefaults(): array
    {
        $faker = Factory::create('fr_FR');
        // This should dump a French region and a French phone number that is started with +33
        dd($faker->region(), $faker->phoneNumber());

        return [
            // TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories)
            'title' => $faker->words(5, true),
            'description' => $faker->text(),
            'hits' => self::faker()->randomNumber(),
        ];
    }

And re-load your fixtures. In the console, you should get a dumped French region and a French number that starts with +33 - it you see it - then localization actually work, but it would mean as I said before that those method you're using like text() are not localized to French.

But if you don't see a french region and french phone number dumped - than it might be a bug in the Faker library you have installed. I'd recommend you to upgrade to the latest version and double-check if it works there. if not - probably report a bug in the Faker repository. Though, it would be a very obvious bug I suppose so other devs had to notice it already. I'm leaning towards the fact that the methods you're using are just not localizaed.

Cheers!

Reply
Rufnex Avatar
Rufnex Avatar Rufnex | Victor | posted hace 3 meses | edited | HIGHLIGHTED

Hi Victor,

I think I have found the problem. The formatters text() and word() can't seem to be localized. To create a local text you can use realText()

$faker->realText()
$faker->realText('30')

It seems that the translation only works for the following: https://fakerphp.github.io/formatters/

If someone has the same problem, the insight might be helpful. And yes RTFM ;o)

Anyway, with the translated results, I think I'd rather stay with Latin LOL.

Thanks for your help, your service is great!

1 Reply

Hey Rufnex,

That's exactly what I was trying to explain you a few times :) Yeah, not everything is localized, unfortunately... or fortunately, because it might be done on purpose, that's just a strategy of that lib.

Good catch on that realText() method, and thanks for sharing it with others!

Anyway, with the translated results, I think I'd rather stay with Latin LOL

Lol! But yeah, that's the reason why the "lorem ipsum" is so popular, it has no "semantic load", just text :)

Anyway, I'm really happy we figured this mystery out! ;)

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.7", // v3.7.0
        "doctrine/doctrine-bundle": "^2.7", // 2.7.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.12", // 2.12.3
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.2", // v6.2.6
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
        "symfony/asset": "6.1.*", // v6.1.0
        "symfony/console": "6.1.*", // v6.1.2
        "symfony/dotenv": "6.1.*", // v6.1.0
        "symfony/flex": "^2", // v2.2.2
        "symfony/framework-bundle": "6.1.*", // v6.1.2
        "symfony/http-client": "6.1.*", // v6.1.2
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
        "symfony/runtime": "6.1.*", // v6.1.1
        "symfony/twig-bundle": "6.1.*", // v6.1.1
        "symfony/ux-turbo": "^2.0", // v2.3.0
        "symfony/webpack-encore-bundle": "^1.13", // v1.15.1
        "symfony/yaml": "6.1.*", // v6.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.1.*", // v6.1.0
        "symfony/maker-bundle": "^1.41", // v1.44.0
        "symfony/stopwatch": "6.1.*", // v6.1.0
        "symfony/web-profiler-bundle": "6.1.*", // v6.1.2
        "zenstruck/foundry": "^1.21" // v1.21.0
    }
}