AliceBundle

repository·main·Indexed 41 days ago

https://github.com/theofidry/alicebundle

A Symfony bundle for managing test fixtures using nelmio/alice and Faker. It provides fixture discovery, CLI loading via hautelook:fixtures:load, and specialized PHPUnit traits for database testing including RefreshDatabaseTrait, ReloadDatabaseTrait, and RecreateDatabaseTrait.

Tokens
6.8K
Snippets
22
Records
25
Agent score
52%

What's inside AliceBundle

  1. Organize environment-specific fixtures

    main

    You can organize fixtures into subdirectories based on the environment. When running the load command with a specific environment flag, AliceBundle will load both the base fixtures and the environment-specific ones.

    Directory Structure Example:

    └── fixtures
        ├── environmentless-fixture1.yml
        ├── dev
        │   └── dev-fixture1.yml
        └── prod
            └── prod-fixture1.yml

    Usage: Running php app/console hautelook:fixtures:load --env=prod will load environmentless-fixture1.yml AND prod/prod-fixture1.yml.

    php app/console hautelook:fixtures:load --env=prod
  2. Migrate from AliceBundle 0.2 to 1.x

    main

    When upgrading from version 0.2 to 1.x, apply the following changes:

    Data Loaders

    • Implementation: Data loaders must now either extend Hautelook\AliceBundle\Doctrine\DataFixtures\AbstractLoader or implement Hautelook\AliceBundle\Doctrine\DataFixtures\LoaderInterface.
    • Simplified Logic: If you were overriding the ::load() function, you likely no longer need to. Custom Faker providers and Alice processors can now be registered directly.
    • Bundle Referencing: Instead of using long paths to refer to fixtures in other bundles, use the @Bundlename annotation.
    • Environment Management: Instead of using multiple data loaders to manage different fixture sets per environment, use environment-specific fixtures.

    Doctrine Commands

    • Replace the standard doctrine:fixtures:load command with the bundle-specific command: hautelook_alice:doctrine:fixtures:load (or the alias h:d:f:l).

    DoctrineFixturesBundle Compatibility

    • HautelookAliceBundle is compatible with DoctrineFixturesBundle, but it is recommended to use only one of them to avoid redundancy.
  3. Install AliceBundle with Symfony Flex

    main

    If you are using Symfony Flex, you can install the bundle using composer. It is recommended to have doctrine-orm installed first if it is not already present in your project.

    # If you do not have Doctrine installed yet:
    composer require doctrine-orm
    
    composer require --dev hautelook/alice-bundle
  4. Register custom Alice Processors

    main

    To use a custom processor (e.g., App\DataFixtures\Processor\UserProcessor) within AliceBundle, you must register it as a service in your Symfony configuration.

    If autoconfigure: true is enabled in your services.yaml (the default in most Symfony projects), AliceBundle will automatically detect and register your processor. If autoconfigure is disabled, you must manually add the fidry_alice_data_fixtures.processor tag to the service definition.

    # config/services.yaml
    
    # Option 1: With autoconfigure enabled (Recommended)
    services:
        _defaults:
            autoconfigure: true
    
        App\DataFixtures\Processor\UserProcessor: ~
    
    # Option 2: Without autoconfigure
    services:
        App\DataFixtures\Processor\UserProcessor:
            tags: [ { name: fidry_alice_data_fixtures.processor } ]
  5. Register custom Faker providers in AliceBundle

    main

    To use custom data generation logic in your fixtures, you can create a custom Faker Provider class and register it as a service.

    There are two ways to register these services:

    1. Manual Tagging: Explicitly add the nelmio_alice.faker.provider tag to your service definition in config/services.yaml.
    2. Autoconfiguration: If autoconfigure is enabled in your Symfony configuration, you can omit the tag if your provider class extends Faker\Provider\Base.

    Once registered, you can call your provider's methods within your fixture files using the <method_name(arguments)> syntax.

    <?php
    
    namespace AppBundle\DataFixtures\Faker\Provider;
    
    class FooProvider
    {
        public static function foo($str)
        {
            return 'foo'.$str;
        }
    }
    
    // In config/services.yaml
    services:
        AppBundle\DataFixtures\Faker\Provider\FooProvider:
            tags: [ { name: nelmio_alice.faker.provider } ]
    
    // In your fixtures (e.g., fixtures/orm/dummy.yml)
    AppBundle\Entity\Dummy:
        brand{1..10}:
            name: <foo('a string')>
  6. Migrate from AliceBundle 1.x to 2.x

    main

    When upgrading from version 1.x to 2.x, several breaking changes and architectural shifts occur:

    • Data Loaders: Remove custom DataLoaders. To reference files in different folders, use a "dummy" file containing an include statement instead.
    • Persistence Layer: The persistence layer has moved to FidryAliceDataFixtures. Database-related configuration should now follow that library's documentation.
    • Configuration Migration: Because Alice now ships with a Symfony bundle, some configuration elements have moved from HautelookAliceBundle to NelmioAliceBundle.
    • Fixture Locations: The default and recommended location for fixtures has changed from src/AppBundle/DataFixtures{/ORM} to Resources/fixtures{/orm} (relative to your app or bundle).
    • Service Tags:
      • Faker providers: Use nelmio_alice.faker.provider (previously hautelook_alice.faker.provider).
      • Processors: Use fidry_alice_data_fixtures.processor (previously hautelook_alice.alice.processor).
  7. Install AliceBundle without Symfony Flex

    main

    For non-Flex architectures, install doctrine/orm manually and register the following bundles in your Kernel class (e.g., app/AppKernel.php). Note that NelmioAliceBundle and FidryAliceDataFixturesBundle are required dependencies for the full functionality of AliceBundle.

    <?php
    // app/AppKernel.php
    
    public function registerBundles(): iterable
    {
        $bundles = [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            // ...
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
        ];
    
        if (in_array($this->getEnvironment(), ['dev', 'test'])) {
            //...
            $bundles[] = new Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle();
            $bundles[] = new Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle();
            $bundles[] = new Hautelook\AliceBundle\HautelookAliceBundle();
        }
    
        return $bundles;
    }
  8. Use Alice parameters in fixtures

    main

    You can define custom parameters within your fixture files to avoid repetition or to pass values to functions. These parameters are scoped to the fixtures and are not injected into your application's ParameterBag.

    1. Define parameters in a dedicated YAML file:
    # fixtures/parameters.yaml
    parameters:
        app.alice.parameters.parameter1: something
    1. Reference parameters in your entity fixtures using the <{parameter_name}> syntax:
    # fixtures/dummy.yaml
    AppBundle\Entity\Dummy:
        dummy_0:
            name: <{app.alice.parameters.parameter1}>
    1. Pass parameters to functions:
    # fixtures/dummy.yaml
    AppBundle\Entity\Dummy:
        dummy_0:
            name: <foo(<{app.alice.parameters.parameter1}>)>
    # fixtures/parameters.yaml
    parameters:
        app.alice.parameters.parameter1: something
    
    # fixtures/dummy.yaml
    AppBundle\Entity\Dummy:
        dummy_0:
            name: <{app.alice.parameters.parameter1}>
  9. Use service factories for entity instantiation

    main

    If an entity requires a specific service to be instantiated (rather than just a constructor with arguments), you can use the __factory key to call a service method. This allows you to leverage existing application services during fixture loading.

    Example: To use a service named dummy_factory with a create method, passing a generated username:

    # fixtures/dummy.yaml
    AppBundle\Entity\Dummy:
        dummy_0:
            __factory: { '@dummy_factory::create': ['<username()>'] }
    AppBundle\Entity\Dummy:
        dummy_0:
            __factory: { '@dummy_factory::create': ['<username()>'] }
  10. Access application parameters in fixtures

    main

    AliceBundle allows you to access your application's existing parameters directly within fixture files using the <{parameter_name}> syntax. This is useful for syncing fixture data with application configuration (e.g., validation settings).

    Example:

    # fixtures/dummy.yaml
    AppBundle\Entity\Dummy:
        dummy_0:
            locale: <{framework.validation.enabled}>
    AppBundle\Entity\Dummy:
        dummy_0:
            locale: <{framework.validation.enabled}>
  11. Validate fixtures using a custom processor

    main

    By default, fixtures are not validated. To trigger validation (e.g., using Symfony's Validator) during the fixture loading process, implement the ProcessorInterface and use the preProcess method.

    Implementation:

    1. Implement ProcessorInterface.
    2. Inject ValidatorInterface into your constructor.
    3. In preProcess, validate the $object and throw a DomainException if violations are found.

    Example:

    final class ArticleProcessor implements ProcessorInterface
    {
        public function __construct(private readonly ValidatorInterface $validator) {}
    
        public function preProcess(string $id, object $object): void
        {
            $violations = $this->validator->validate($object);
            if ($violations->count() > 0) {
                throw new DomainException("Error when validating fixture $id: $violations");
            }
        }
    
        public function postProcess(string $id, object $object): void {}
    }
  12. Order the persistence of classes

    main

    To control the order in which objects are actually persisted to the database, you can decorate the fidry_alice_data_fixtures.loader.simple service by implementing a custom LoaderInterface.

    Implementation Steps:

    1. Create a class implementing LoaderInterface that decorates the existing loader.
    2. In the load method, call the decorated loader to get the $objects array.
    3. Re-order the $objects array as needed.
    4. Register the class in services.yaml using the decorates attribute.

    Example:

    final class CustomOrderLoader implements LoaderInterface
    {
        use IsAServiceTrait;
        private $decoratedLoader;
    
        public function __construct(LoaderInterface $decoratedLoader)
        {
            $this->decoratedLoader = $decoratedLoader;
        }
    
        public function load(array $fixturesFiles, array $parameters = [], array $objects = [], PurgeMode $purgeMode = null): array
        {
            $objects = $this->decoratedLoader->load($fixturesFiles, $parameters, $objects, $purgeMode);
            // TODO: re-order $objects
            return $objects;
        }
    }
    // services.yaml
    services:
        Acme\Alice\Loader\CustomOrderLoader:
            decorates: 'fidry_alice_data_fixtures.loader.simple'
            arguments:
                - '@Acme\Alice\Loader\CustomOrderLoader.inner'
    services:
        Acme\Alice\Loader\CustomOrderLoader:
            decorates: 'fidry_alice_data_fixtures.loader.simple'
            arguments:
                - '@Acme\Alice\Loader\CustomOrderLoader.inner'