├── travis.php.ini
├── tests
├── Trappar
│ └── AliceGeneratorBundle
│ │ ├── SymfonyApp
│ │ ├── config
│ │ │ ├── alice-generator
│ │ │ │ └── Userasdf.yml
│ │ │ ├── services.yml
│ │ │ └── config.yml
│ │ ├── TestBundle
│ │ │ ├── TestBundle.php
│ │ │ ├── DataFixtures
│ │ │ │ └── Faker
│ │ │ │ │ └── Provider
│ │ │ │ │ ├── ServiceProvider.php
│ │ │ │ │ ├── NonArrayReturningProvider.php
│ │ │ │ │ ├── CustomProvider.php
│ │ │ │ │ └── ArgTestProvider.php
│ │ │ ├── ObjectHandler
│ │ │ │ └── CustomHandler.php
│ │ │ └── Entity
│ │ │ │ ├── Post.php
│ │ │ │ └── User.php
│ │ └── AppKernel.php
│ │ ├── TrapparAliceGeneratorBundleTest.php
│ │ ├── FixtureGeneratorTest.php
│ │ ├── DependencyInjection
│ │ ├── Compiler
│ │ │ └── CompilerPassTest.php
│ │ └── TrapparAliceGeneratorExtensionTest.php
│ │ └── Command
│ │ └── GenerateFixturesCommandTest.php
└── bootstrap.php
├── .gitignore
├── phpunit.xml.dist
├── src
└── Trappar
│ └── AliceGeneratorBundle
│ ├── TrapparAliceGeneratorBundle.php
│ ├── Persister
│ └── DoctrinePersister.php
│ ├── Resources
│ ├── meta
│ │ └── LICENSE
│ ├── doc
│ │ └── configuration.md
│ └── config
│ │ └── services.yml
│ ├── DependencyInjection
│ ├── Compiler
│ │ └── CompilerPass.php
│ ├── Configuration.php
│ └── TrapparAliceGeneratorExtension.php
│ ├── Metadata
│ └── Resolver
│ │ └── Faker
│ │ └── ServiceFakerResolver.php
│ └── Command
│ ├── Validators.php
│ ├── Questions.php
│ └── GenerateFixturesCommand.php
├── CHANGELOG.md
├── .travis.yml
├── console
├── LICENSE
├── composer.json
├── UPGRADING.md
└── README.md
/travis.php.ini:
--------------------------------------------------------------------------------
1 | memory_limit = -1
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/config/alice-generator/Userasdf.yml:
--------------------------------------------------------------------------------
1 | Trappar\AliceGeneratorBundle\Tests\SymfonyApp\TestBundle\Entity\User:
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/*
2 | composer.lock
3 | tests/Trappar/AliceGeneratorBundle/SymfonyApp/cache
4 | tests/Trappar/AliceGeneratorBundle/SymfonyApp/logs
5 | generated.yml
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/config/services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | Trappar\AliceGeneratorBundle\Tests\SymfonyApp\TestBundle\ObjectHandler\CustomHandler:
3 | tags: [ { name: trappar_alice_generator.object_handler } ]
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/TestBundle/TestBundle.php:
--------------------------------------------------------------------------------
1 | addPsr4('Trappar\AliceGeneratorBundle\Tests\\', __DIR__ . '/Trappar/AliceGeneratorBundle');
8 |
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/TestBundle/DataFixtures/Faker/Provider/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | ';
12 | }
13 | }
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/TestBundle/ObjectHandler/CustomHandler.php:
--------------------------------------------------------------------------------
1 | value];
17 | }
18 | }
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ./tests
10 |
11 |
12 |
13 |
14 |
15 | src
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/TrapparAliceGeneratorBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new CompilerPass());
19 | }
20 | }
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/TestBundle/DataFixtures/Faker/Provider/ArgTestProvider.php:
--------------------------------------------------------------------------------
1 | Trappar\AliceGenerator\FixtureGenerator)
10 |
11 | ## Version 1.1.0
12 | * Compatibility with AliceGenerator v0.2.0
13 | * Added option to specify which Doctrine entity manager to use in Generate Fixtures Command
14 |
15 | ## Version 1.0.0
16 | * Moved code to [AliceGenerator](https://github.com/trappar/AliceGenerator) library
17 | * Added built-in command for generating Fixtures
18 |
19 | ## Version 0.1.0
20 | * Initial version
21 | * Support all baseline functionality
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.2
5 | - 7.1
6 | - nightly
7 |
8 | matrix:
9 | fast_finish: true
10 | include:
11 | - php: '7.1'
12 | env: SYMFONY_VERSION="^3.0"
13 | - php: '7.1'
14 | env: SYMFONY_VERSION="^4.0"
15 | - php: '7.2'
16 | env: SYMFONY_VERSION="^4.0"
17 | allow_failures:
18 | - php: nightly
19 |
20 | cache:
21 | directories:
22 | - $HOME/.composer/cache/files
23 |
24 | before_install:
25 | # Don't run with xdebug, and don't error when xdebug isn't installed
26 | - |
27 | phpenv config-rm xdebug.ini || true
28 | phpenv config-add travis.php.ini
29 | if [ "$SYMFONY_VERSION" != "" ]; then
30 | composer require "symfony/symfony:${SYMFONY_VERSION}" --no-update
31 | fi
32 |
33 | install: composer update --prefer-dist --no-interaction
34 |
35 | script: ./vendor/bin/phpunit --stop-on-failure --verbose
--------------------------------------------------------------------------------
/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
17 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
18 |
19 | if ($debug) {
20 | Debug::enable();
21 | }
22 |
23 | error_reporting(E_ALL & ~E_USER_DEPRECATED);
24 |
25 | $kernel = new AppKernel($env, $debug);
26 | $application = new Application($kernel);
27 | $application->run($input);
28 |
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Persister/DoctrinePersister.php:
--------------------------------------------------------------------------------
1 | getManager());
18 | $this->doctrine = $doctrine;
19 | }
20 |
21 | protected function getMetadata($object)
22 | {
23 | try {
24 | $class = $this->getClass($object);
25 |
26 | return $this->doctrine->getManagerForClass($class)->getClassMetadata($class);
27 | } catch (\Exception $e) {
28 | return false;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/AppKernel.php:
--------------------------------------------------------------------------------
1 | load(__DIR__ . '/config/config.yml');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/config/config.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: services.yml }
3 |
4 | framework:
5 | secret: test
6 | validation:
7 | enabled: true
8 | enable_annotations: true
9 | session: ~
10 | test: ~
11 |
12 | doctrine:
13 | orm:
14 | default_entity_manager: default
15 | entity_managers:
16 | default:
17 | connection: default
18 | mappings:
19 | TestBundle: ~
20 | dbal:
21 | connections:
22 | default:
23 | driver: pdo_sqlite
24 | path: "%kernel.cache_dir%/db.sqlite"
25 | charset: UTF8
26 |
27 | trappar_alice_generator:
28 | metadata:
29 | auto_detection: false
30 | directories:
31 | entities:
32 | namespace_prefix: 'Trappar\AliceGeneratorBundle\Tests\SymfonyApp\TestBundle\Entity'
33 | path: '%kernel.root_dir%/config/alice-generator'
34 |
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/TrapparAliceGeneratorBundleTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Bundle::class, $bundle);
18 |
19 | $containerBuilder = $this->getMockBuilder(ContainerBuilder::class)
20 | ->setMethods(['addCompilerPass'])
21 | ->getMock();
22 |
23 | $containerBuilder->expects($this->once())
24 | ->method('addCompilerPass')
25 | ->with(new CompilerPass());
26 |
27 | $bundle->build($containerBuilder);
28 | }
29 | }
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/TestBundle/Entity/Post.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(FixtureGenerator::class);
15 |
16 | $valueVisitor = $this->readAttribute($fg, 'valueVisitor');
17 |
18 | $metadataResolver = $this->readAttribute($valueVisitor, 'metadataResolver');
19 | $fakerResolvers = $this->readAttribute($metadataResolver, 'fakerResolvers');
20 | $this->assertCount(5, $fakerResolvers);
21 |
22 | $objectHandlerRegistry = $this->readAttribute($valueVisitor, 'objectHandlerRegistry');
23 | $handlers = $this->readAttribute($objectHandlerRegistry, 'handlers');
24 | $this->assertCount(3, $handlers);
25 | $this->assertInstanceOf(CustomHandler::class, $handlers[0]);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Resources/meta/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Trappar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trappar/alice-generator-bundle",
3 | "description": "Symfony bundle for generating Alice fixtures from Doctrine entities",
4 | "keywords": ["fixture", "data", "test", "orm", "doctrine"],
5 | "type": "symfony-bundle",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Jeff Way",
10 | "email": "jeff.way@me.com"
11 | }
12 | ],
13 | "require": {
14 | "php": "^7.1",
15 | "trappar/alice-generator": "^0.3",
16 | "doctrine/orm": "~2.4",
17 | "doctrine/doctrine-bundle": "^1.6",
18 | "symfony/finder": "^3.0|^4.0",
19 | "symfony/validator": "^3.0|^4.0",
20 | "symfony/console": "^3.0|^4.0",
21 | "symfony/framework-bundle": "^3.0|^4.0",
22 | "symfony/yaml": "^3.0|^4.0"
23 | },
24 | "require-dev": {
25 | "phpunit/phpunit": "^7.1",
26 | "symfony/phpunit-bridge": "^3.0|^4.0",
27 | "matthiasnoback/symfony-dependency-injection-test": "^3.0"
28 | },
29 | "minimum-stability": "dev",
30 | "prefer-stable": true,
31 | "autoload": {
32 | "psr-4": { "Trappar\\AliceGeneratorBundle\\": "src/Trappar/AliceGeneratorBundle" }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/SymfonyApp/TestBundle/Entity/User.php:
--------------------------------------------------------------------------------
1 | posts = new ArrayCollection();
66 | }
67 |
68 | public function blah(ValueContext $context)
69 | {
70 | return [$context->getContextObject()->roles];
71 | }
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/UPGRADING.md:
--------------------------------------------------------------------------------
1 | # Upgrade Guide
2 |
3 | ## Upgrading from 0.1 to 1.0
4 |
5 | ##### AbstractFixtureGenerationCommand is no longer provided
6 |
7 | If you wrote a command based on this you can do one of:
8 |
9 | * Reimplement your custom command
10 | * Inject the FixtureGenerator service manually into the constructor, or retrieve it from the container (this was previously handled by the abstract service)
11 | * Handle writing the yaml file yourself. This can be as simple as using `file_put_contents`.
12 | * Remove your custom command
13 | * If your command was fairly simple then it's likely that the functionality is now offered natively through the bundle's new `generate:fixtures` command.
14 |
15 | ##### Annotations have moved to the AliceGenerator library
16 |
17 | You will need to replace use statements resembling
18 |
19 | ```php
20 | hasDefinition(MetadataResolver::class)) {
20 | $taggedServices = $container->findTaggedServiceIds('trappar_alice_generator.faker_resolver');
21 | $resolverDefinition = $container->getDefinition(MetadataResolver::class);
22 | $resolvers = [];
23 | foreach ($taggedServices as $resolverId => $tags) {
24 | $resolvers[] = new Reference($resolverId);
25 | }
26 | $resolverDefinition->addMethodCall('addFakerResolvers', [$resolvers]);
27 | }
28 |
29 | // Add Object Handlers to ObjectHandlerRegistry
30 | if ($container->hasDefinition(ObjectHandlerRegistry::class)) {
31 | $taggedServices = $container->findTaggedServiceIds('trappar_alice_generator.object_handler');
32 | $handlerDefinition = $container->getDefinition(ObjectHandlerRegistry::class);
33 | $handlers = [];
34 | foreach ($taggedServices as $handlerId => $tags) {
35 | $handlers[] = new Reference($handlerId);
36 | }
37 | $handlerDefinition->addMethodCall('registerHandlers', [$handlers]);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
19 | } else {
20 | // BC layer for symfony/config 4.1 and older
21 | $root = $tb->root('trappar_alice_generator');
22 | }
23 |
24 | $root->children()
25 | ->arrayNode('metadata')
26 | ->addDefaultsIfNotSet()
27 | ->fixXmlConfig('directory', 'directories')
28 | ->children()
29 | ->booleanNode('auto_detection')->defaultTrue()->end()
30 | ->arrayNode('directories')
31 | ->prototype('array')
32 | ->children()
33 | ->scalarNode('path')->isRequired()->end()
34 | ->scalarNode('namespace_prefix')->defaultValue('')->end()
35 | ->end()
36 | ->end()
37 | ->end()
38 | ->end()
39 | ->end()
40 | ->arrayNode('yaml')
41 | ->addDefaultsIfNotSet()
42 | ->children()
43 | ->integerNode('inline')->defaultValue(3)->end()
44 | ->integerNode('indent')->defaultValue(4)->end()
45 | ->end()
46 | ->end()
47 | ->booleanNode('strictTypeChecking')->defaultValue(true)->end()
48 | ->end();
49 |
50 | return $tb;
51 | }
52 | }
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/DependencyInjection/Compiler/CompilerPassTest.php:
--------------------------------------------------------------------------------
1 | setDefinition(MetadataResolver::class, new Definition());
18 | $this->setDefinition(ObjectHandlerRegistry::class, new Definition());
19 |
20 | $testFakerResolver = new Definition();
21 | $testFakerResolver->addTag('trappar_alice_generator.faker_resolver');
22 | $this->setDefinition('custom_faker_resolver', $testFakerResolver);
23 |
24 | $testObjectHandler = new Definition();
25 | $testObjectHandler->addTag('trappar_alice_generator.object_handler');
26 | $this->setDefinition('custom_object_handler', $testObjectHandler);
27 |
28 | $this->compile();
29 |
30 | $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
31 | MetadataResolver::class,
32 | 'addFakerResolvers',
33 | [[new Reference('custom_faker_resolver')]]
34 | );
35 |
36 | $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
37 | ObjectHandlerRegistry::class,
38 | 'registerHandlers',
39 | [[new Reference('custom_object_handler')]]
40 | );
41 | }
42 |
43 | protected function registerCompilerPass(ContainerBuilder $container)
44 | {
45 | $container->addCompilerPass(new CompilerPass());
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Metadata/Resolver/Faker/ServiceFakerResolver.php:
--------------------------------------------------------------------------------
1 | container = $container;
20 | }
21 |
22 | /**
23 | * @inheritDoc
24 | */
25 | public function getType()
26 | {
27 | return 'service';
28 | }
29 |
30 | public function validate(ValueContext $valueContext)
31 | {
32 | list($serviceName, $method) = $this->getTarget($valueContext);
33 |
34 | if (!$this->container->has($serviceName)) {
35 | throw new FakerResolverException($valueContext, sprintf(
36 | 'non-existent service given: "%s".',
37 | $serviceName
38 | ));
39 | }
40 |
41 | $service = $this->container->get($serviceName);
42 | if (!is_callable([$service, $method])) {
43 | throw new FakerResolverException($valueContext, sprintf(
44 | 'service "%s" does not have a callable "%s" method',
45 | $serviceName,
46 | $method
47 | ));
48 | }
49 | }
50 |
51 | /**
52 | * @inheritDoc
53 | */
54 | public function handle(ValueContext $valueContext)
55 | {
56 | list($serviceName, $method) = $this->getTarget($valueContext);
57 |
58 | return call_user_func([$this->container->get($serviceName), $method], $valueContext);
59 | }
60 |
61 | private function getTarget(ValueContext $valueContext)
62 | {
63 | $args = $valueContext->getMetadata()->fakerResolverArgs;
64 | $service = isset($args[0]) ? $args[0] : null;
65 | $method = isset($args[1]) ? $args[1] : 'toFixture';
66 |
67 | return [$service, $method];
68 | }
69 | }
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Resources/doc/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | ## Custom Object Handlers
4 |
5 | You can register any service as an object handler by adding tag `trappar_alice_generator.object_handler`
6 |
7 | ```yaml
8 | # app/config/services.yml
9 |
10 | services:
11 | object_handler.my_handler:
12 | class: AppBundle\ObjectHandler\MyHandler
13 | tags: [ { name: trappar_alice_generator.object_handler } ]
14 | ```
15 |
16 | Any object handlers added this way will be called before the built-in object handlers. This means that, for example, if you wish to override the native behavior for handling `DateTime` objects, you can do so.
17 |
18 | For more information on Custom Object Handlers, check out the [standalone library's documentation](https://github.com/trappar/AliceGenerator/blob/master/doc/custom-object-handlers.md).
19 |
20 | ## Overriding Third-Party Metadata
21 |
22 | Sometimes you want to generate fixtures for objects which are shipped by a third-party bundle. Such a third-party bundle might not ship with metadata that suits your needs, or possibly none, at all. In such a case, you can override the default location that is searched for metadata with a path that is under your control.
23 |
24 | ```yaml
25 | trappar_alice_generator:
26 | metadata:
27 | directories:
28 | FOSUB:
29 | namespace_prefix: "FOS\\UserBundle"
30 | path: "%kernel.root_dir%/fixture/FOSUB"
31 | ```
32 |
33 | ## Extension Reference
34 |
35 | Below you find a reference of all configuration options with their default values:
36 |
37 | ```yaml
38 | # config.yml
39 | trappar_alice_generator:
40 | metadata:
41 | # Using auto-detection, the mapping files for each bundle will be
42 | # expected in the Resources/config/fixture directory.
43 | #
44 | # Example:
45 | # class: My\FooBundle\Entity\User
46 | # expected path: @MyFooBundle/Resources/config/fixture/Entity.User.yml
47 | auto_detection: true
48 |
49 | # if you don't want to use auto-detection, you can also define the
50 | # namespace prefix and the corresponding directory explicitly
51 | directories:
52 | any-name:
53 | namespace_prefix: "My\\FooBundle"
54 | path: "@MyFooBundle/Resources/config/fixture"
55 | another-name:
56 | namespace_prefix: "My\\BarBundle"
57 | path: "@MyBarBundle/Resources/config/fixture"
58 | yaml:
59 | # These settings directly control the arguments for \Symfony\Component\Yaml\Yaml::dump().
60 | inline: 3
61 | indent: 4
62 | # See documentation of AliceGenerator here: https://github.com/trappar/AliceGenerator/blob/master/doc/configuration.md#disabling-strict-type-checking
63 | strictTypeChecking: true
64 | ```
65 |
66 | [Back to the README](/README.md)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AliceGeneratorBundle [](https://travis-ci.org/trappar/AliceGeneratorBundle)
2 | ====================
3 |
4 | This bundle integrates the [AliceGenerator](https://github.com/trappar/AliceGenerator) library into Symfony.
5 |
6 | ## Introduction
7 |
8 | TrapparAliceGeneratorBundle allows you to generate Alice Fixtures from your existing data.
9 |
10 | You can learn more in the [documentation for the standalone library](https://github.com/trappar/AliceGenerator).
11 |
12 | ## Table of Contents
13 |
14 | * [Installation](#installation)
15 | * [Configuration](#configuration)
16 | * [Full Configuration Reference](src/Trappar/AliceGeneratorBundle/Resources/doc/configuration.md)
17 | * [Usage](#usage)
18 | * [Resources](#resources)
19 | * [Credits](#credits)
20 | * [License](#license)
21 |
22 | ## Installation
23 |
24 | ```bash
25 | composer require trappar/alice-generator-bundle
26 | ```
27 |
28 | Then, enable the bundle by updating your `app/AppKernel.php` file to enable the bundle:
29 |
30 | ```php
31 | getEnvironment(), ['dev', 'test'])) {
35 | // ...
36 | $bundles[] = new Trappar\AliceGeneratorBundle\TrapparAliceGeneratorBundle();
37 | // ...
38 | }
39 | ```
40 |
41 | ## Configuration
42 |
43 | TrapparAliceGeneratorBundle requires no initial configuration to get you started.
44 |
45 | For all available configuration options, please see the [configuration reference](src/Trappar/AliceGeneratorBundle/Resources/doc/configuration.md).
46 |
47 | ## Usage
48 |
49 | The main method for using this bundle is the included command line application. Use this by running:
50 |
51 | ```bash
52 | console generate:fixtures
53 | ```
54 |
55 | And simply follow along with the prompts.
56 |
57 | You can also request the FixtureGenerator as a service from the container:
58 |
59 | ```php
60 | $fixtureGenerator = $container->get('trappar_alice_generator.fixture_generator');
61 | $yaml = $fixtureGenerator->generateYaml($entities);
62 | ```
63 |
64 | Learn more in the [documentation for the dedicated library](https://github.com/trappar/AliceGenerator/blob/master/doc/usage.md).
65 |
66 | ## Resources
67 |
68 | * [Changelog](CHANGELOG.md)
69 | * [Upgrade Guide](UPGRADING.md)
70 | * [AliceGenerator](https://github.com/trappar/AliceGenerator)
71 | * [Alice](https://github.com/nelmio/alice)
72 | * [AliceBundle](https://github.com/hautelook/AliceBundle)
73 | * [Faker](https://github.com/fzaninotto/Faker)
74 |
75 | ## Credits
76 |
77 | This bundle was developed by [Jeff Way](https://github.com/trappar) with quite a lot of inspiration from:
78 |
79 | * [AliceBundle](https://github.com/hautelook/AliceBundle)
80 | * [JMSSerializerBundle](https://github.com/schmittjoh/JMSSerializerBundle)
81 | * [SensioGeneratorBundle](https://github.com/sensiolabs/SensioGeneratorBundle)
82 |
83 | [Other contributors](https://github.com/trappar/AliceGeneratorBundle/graphs/contributors).
84 |
85 | ## License
86 |
87 | [](Resources/meta/LICENSE)
88 |
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/DependencyInjection/TrapparAliceGeneratorExtension.php:
--------------------------------------------------------------------------------
1 | load('services.yml');
26 |
27 | $bundles = $container->getParameter('kernel.bundles');
28 |
29 | $directories = [];
30 | if ($config['metadata']['auto_detection']) {
31 | foreach ($bundles as $name => $class) {
32 | $ref = new \ReflectionClass($class);
33 | $directories[$ref->getNamespaceName()] = dirname($ref->getFileName()) . '/Resources/config/fixture';
34 | }
35 | }
36 |
37 | foreach ($config['metadata']['directories'] as $directory) {
38 | $directory['path'] = rtrim(str_replace('\\', '/', $directory['path']), '/');
39 | if ('@' === $directory['path'][0]) {
40 | preg_match('~^@([a-z0-9_]+)~i', $directory['path'], $match);
41 |
42 | if (isset($match[1])) {
43 | list($prefixedBundleName, $bundleName) = $match;
44 | if (!isset($bundles[$bundleName])) {
45 | throw new RuntimeException(sprintf('The bundle "%s" has not been registered with AppKernel. Available bundles: %s', $bundleName, implode(', ', array_keys($bundles))));
46 | }
47 | $ref = new \ReflectionClass($bundles[$bundleName]);
48 | $directory['path'] = dirname($ref->getFileName()).substr($directory['path'], strlen($prefixedBundleName));
49 | }
50 | }
51 | $directories[rtrim($directory['namespace_prefix'], '\\')] = rtrim($directory['path'], '\\/');
52 | }
53 |
54 | $container
55 | ->getDefinition('trappar_alice_generator.metadata.file_locator')
56 | ->addArgument($directories);
57 |
58 | $container
59 | ->getDefinition(YamlWriter::class)
60 | ->addArgument($config['yaml']['inline'])
61 | ->addArgument($config['yaml']['indent']);
62 |
63 | $container
64 | ->getDefinition(ValueVisitor::class)
65 | ->addArgument($config['strictTypeChecking']);
66 | }
67 |
68 | public function getConfiguration(array $config, ContainerBuilder $container)
69 | {
70 | return new Configuration();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/DependencyInjection/TrapparAliceGeneratorExtensionTest.php:
--------------------------------------------------------------------------------
1 | setBundles();
23 | $this->load([
24 | 'metadata' => [
25 | 'auto_detection' => false
26 | ]
27 | ]);
28 |
29 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
30 | 'trappar_alice_generator.metadata.file_locator',
31 | 0,
32 | []
33 | );
34 | }
35 |
36 | public function testWithCustomDirectory()
37 | {
38 | $this->setBundles();
39 | $this->load([
40 | 'metadata' => [
41 | 'auto_detection' => false,
42 | 'directories' => [
43 | 'myname' => [
44 | 'namespace_prefix' => 'some_prefix',
45 | 'path' => '@TestBundle'
46 | ]
47 | ]
48 | ]
49 | ]);
50 |
51 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
52 | 'trappar_alice_generator.metadata.file_locator',
53 | 0,
54 | ['some_prefix' => realpath(__DIR__ . '/../SymfonyApp/TestBundle')]
55 | );
56 | }
57 |
58 | public function testAutoDetectionEnabled()
59 | {
60 | $this->setBundles();
61 | $this->load();
62 |
63 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
64 | 'trappar_alice_generator.metadata.file_locator',
65 | 0
66 | );
67 | }
68 |
69 | public function testWithInvalidBundle()
70 | {
71 | $this->expectException(RuntimeException::class);
72 |
73 | $this->setBundles();
74 | $this->load([
75 | 'metadata' => [
76 | 'directories' => [
77 | 'myname' => [
78 | 'namespace_prefix' => 'some_prefix',
79 | 'path' => '@Blah'
80 | ]
81 | ]
82 | ]
83 | ]);
84 | }
85 |
86 | public function testWithCustomYamlOptions()
87 | {
88 | $this->setBundles();
89 | $this->load([
90 | 'yaml' => [
91 | 'indent' => 1
92 | ]
93 | ]);
94 |
95 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
96 | YamlWriter::class,
97 | 0,
98 | 3
99 | );
100 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
101 | YamlWriter::class,
102 | 1,
103 | 1
104 | );
105 | }
106 |
107 | public function testWithDisabledStrictTypeChecking()
108 | {
109 | $this->setBundles();
110 | $this->load();
111 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
112 | ValueVisitor::class,
113 | 5,
114 | true
115 | );
116 |
117 | $this->load(['strictTypeChecking' => false]);
118 | $this->assertContainerBuilderHasServiceDefinitionWithArgument(
119 | ValueVisitor::class,
120 | 5,
121 | false
122 | );
123 | }
124 |
125 | private function setBundles()
126 | {
127 | $this->setParameter('kernel.bundles', [
128 | 'TestBundle' => 'Trappar\AliceGeneratorBundle\Tests\SymfonyApp\TestBundle\TestBundle'
129 | ]);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Command/Validators.php:
--------------------------------------------------------------------------------
1 | getManagerForClass($entityAlias)->getClassMetadata($entityAlias);
33 | } catch (\Exception $e) {
34 | throw new \InvalidArgumentException(sprintf(
35 | 'Unable to fetch entity information for "%s"',
36 | $entityAlias
37 | ));
38 | }
39 | }
40 |
41 | return [$entityAlias, $metadata];
42 | }
43 |
44 | public static function validateSelectionType(array $selectionTypes, $value)
45 | {
46 | if (!in_array($value, $selectionTypes)) {
47 | throw new \InvalidArgumentException(sprintf('Invalid selection type "%s".', $value));
48 | }
49 |
50 | return $value;
51 | }
52 |
53 | public static function validateInt($value)
54 | {
55 | if (false === $result = filter_var($value, FILTER_VALIDATE_INT)) {
56 | throw new \InvalidArgumentException(sprintf('Invalid non-int given: %s.', $value));
57 | }
58 |
59 | return $result;
60 | }
61 |
62 | public static function validateID($value)
63 | {
64 | $ids = preg_split('~\s*,\s*~', $value);
65 |
66 | foreach ($ids as $key => $id) {
67 | if ($id === '') {
68 | unset($ids[$key]);
69 | continue;
70 | }
71 |
72 | $ids[$key] = self::validateInt($id);
73 | }
74 |
75 | return array_values($ids);
76 | }
77 |
78 | public static function validateWhereConditions(ClassMetadata $metadata, $value)
79 | {
80 | // Ensure surrounding brackets exist
81 | $value = preg_replace('~^\s*\{?~', '{', $value, 1);
82 | $value = preg_replace('~\}?\s*$~', '}', $value, 1);
83 |
84 | $parsed = Yaml::parse($value);
85 |
86 | if (!count($parsed)) {
87 | throw new \InvalidArgumentException('You must include at least one condition.');
88 | }
89 |
90 | $knownFields = $metadata->getFieldNames();
91 |
92 | foreach ($parsed as $field => $value) {
93 | if (!in_array($field, $knownFields)) {
94 | throw new \InvalidArgumentException(sprintf(
95 | 'Unknown field "%s".',
96 | $field
97 | ));
98 | }
99 | }
100 |
101 | return $parsed;
102 | }
103 |
104 | public static function validateYesNo($value)
105 | {
106 | switch ($value) {
107 | case 'y':
108 | case 'yes':
109 | return true;
110 | break;
111 | case 'n':
112 | case 'no':
113 | return false;
114 | break;
115 | default:
116 | throw new \InvalidArgumentException('Must be either "yes" or "no".');
117 | }
118 | }
119 |
120 | public static function validateOutputPath($path)
121 | {
122 | $ext = pathinfo($path, PATHINFO_EXTENSION);
123 |
124 | if ($ext != 'yml') {
125 | throw new \InvalidArgumentException('Output file must have .yml extension.');
126 | }
127 |
128 | return $path;
129 | }
130 | }
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Command/Questions.php:
--------------------------------------------------------------------------------
1 | input = $input;
30 | $this->output = $output;
31 | $this->helperSet = $helperSet;
32 | }
33 |
34 | public function askForRecursionDepth()
35 | {
36 | $question = $this->getQuestion('Maximum recursion depth', 5);
37 | $question->setValidator([Validators::class, 'validateInt']);
38 |
39 | return $this->ask($question);
40 | }
41 |
42 | public function askForOutputDirectory($bundleNames, $defaultOutputPath)
43 | {
44 | $question = $this->getQuestion('Output location', $defaultOutputPath);
45 | $question->setAutocompleterValues(array_map(function ($bundleName) {
46 | return "@$bundleName/DataFixtures/ORM/generated.yml";
47 | }, $bundleNames));
48 | $question->setValidator([Validators::class, 'validateOutputPath']);
49 |
50 | return $this->ask($question);
51 | }
52 |
53 | public function askIfFileOverwrite()
54 | {
55 | $question = $this->getQuestion('Overwrite', 'yes');
56 | $question->setAutocompleterValues(['yes', 'no']);
57 | $question->setValidator([Validators::class, 'validateYesNo']);
58 |
59 | return $this->ask($question);
60 | }
61 |
62 | public function askForEntity(ManagerRegistry $doctrine, $entityAutocomplete)
63 | {
64 | $question = $this->getQuestion('Entity shortcut name', false);
65 | $question->setAutocompleterValues($entityAutocomplete);
66 | $question->setValidator(Validators::createBoundValidator('validateEntity', $doctrine));
67 |
68 | return $this->ask($question);
69 | }
70 |
71 | public function askForSelectionType($selectionTypes)
72 | {
73 | $question = $this->getQuestion('Entity selection type', $selectionTypes[0]);
74 | $question->setAutocompleterValues($selectionTypes);
75 | $question->setValidator(Validators::createBoundValidator('validateSelectionType', $selectionTypes));
76 |
77 | return $this->ask($question);
78 | }
79 |
80 | public function askForIDs()
81 | {
82 | $question = $this->getQuestion('IDs to include', '1');
83 | $question->setValidator([Validators::class, 'validateID']);
84 |
85 | return $this->ask($question);
86 | }
87 |
88 | public function askForWhereConditions(ClassMetadata $entityMetadata)
89 | {
90 | $question = $this->getQuestion('Where conditions', false);
91 | $question->setValidator(Validators::createBoundValidator('validateWhereConditions', $entityMetadata));
92 |
93 | return $this->ask($question);
94 | }
95 |
96 | public function askIfAddAsEntityConstraints()
97 | {
98 | $question = $this->getQuestion('Add as object constraints', 'no');
99 | $question->setAutocompleterValues(['yes', 'no']);
100 | $question->setValidator([Validators::class, 'validateYesNo']);
101 |
102 | return $this->ask($question);
103 | }
104 |
105 | private function getQuestion($question, $default, $sep = ':')
106 | {
107 | $questionString = $default
108 | ? sprintf('%s [%s]%s ', $question, $default, $sep)
109 | : sprintf('%s%s ', $question, $sep);
110 |
111 | return new Question($questionString, $default);
112 | }
113 |
114 | private function ask(Question $question)
115 | {
116 | return $this->helperSet->get('question')->ask($this->input, $this->output, $question);
117 | }
118 | }
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Resources/config/services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | # Metadata Drivers
3 | trappar_alice_generator.metadata.file_locator:
4 | public: false
5 | class: Metadata\Driver\FileLocator
6 |
7 | Trappar\AliceGenerator\Metadata\Driver\YamlDriver:
8 | public: false
9 | arguments: ['@trappar_alice_generator.metadata.file_locator']
10 |
11 | Trappar\AliceGenerator\Metadata\Driver\AnnotationDriver:
12 | public: false
13 | arguments: ['@annotation_reader']
14 |
15 | trappar_alice_generator.metadata.chain_driver:
16 | public: false
17 | class: Metadata\Driver\DriverChain
18 | arguments:
19 | - ['@Trappar\AliceGenerator\Metadata\Driver\YamlDriver', '@Trappar\AliceGenerator\Metadata\Driver\AnnotationDriver']
20 |
21 | trappar_alice_generator.metadata.lazy_loading_driver:
22 | public: false
23 | class: Metadata\Driver\LazyLoadingDriver
24 | arguments:
25 | - '@service_container'
26 | - 'trappar_alice_generator.metadata.chain_driver'
27 |
28 | # Metadata Factory
29 | trappar_alice_generator.metadata.factory:
30 | public: false
31 | class: Metadata\MetadataFactory
32 | arguments: ['@trappar_alice_generator.metadata.lazy_loading_driver']
33 |
34 | # Metadata Resolvers Faker
35 | Trappar\AliceGenerator\Metadata\Resolver\Faker\ArrayFakerResolver:
36 | public: false
37 |
38 | Trappar\AliceGenerator\Metadata\Resolver\Faker\CallbackFakerResolver:
39 | public: false
40 |
41 | Trappar\AliceGenerator\Metadata\Resolver\Faker\NoArgFakerResolver:
42 | public: false
43 |
44 | Trappar\AliceGenerator\Metadata\Resolver\Faker\ValueAsArgFakerResolver:
45 | public: false
46 |
47 | Trappar\AliceGeneratorBundle\Metadata\Resolver\Faker\ServiceFakerResolver:
48 | public: false
49 | arguments: ['@service_container']
50 |
51 | # Metadata Resolvers
52 | Trappar\AliceGenerator\Metadata\Resolver\MetadataResolver:
53 | public: false
54 | arguments:
55 | -
56 | - '@Trappar\AliceGenerator\Metadata\Resolver\Faker\ArrayFakerResolver'
57 | - '@Trappar\AliceGenerator\Metadata\Resolver\Faker\CallbackFakerResolver'
58 | - '@Trappar\AliceGenerator\Metadata\Resolver\Faker\NoArgFakerResolver'
59 | - '@Trappar\AliceGenerator\Metadata\Resolver\Faker\ValueAsArgFakerResolver'
60 | - '@Trappar\AliceGeneratorBundle\Metadata\Resolver\Faker\ServiceFakerResolver'
61 |
62 | # Persisters
63 | Trappar\AliceGeneratorBundle\Persister\DoctrinePersister:
64 | public: false
65 | arguments: ['@doctrine']
66 |
67 | # Object Handlers
68 | Trappar\AliceGenerator\ObjectHandler\CollectionHandler:
69 | public: false
70 |
71 | Trappar\AliceGenerator\ObjectHandler\DateTimeHandler:
72 | public: false
73 |
74 | # Object Handler Registries
75 | Trappar\AliceGenerator\ObjectHandlerRegistry:
76 | public: false
77 | arguments:
78 | -
79 | - '@Trappar\AliceGenerator\ObjectHandler\CollectionHandler'
80 | - '@Trappar\AliceGenerator\ObjectHandler\DateTimeHandler'
81 |
82 | # Yaml Writer
83 | Trappar\AliceGenerator\YamlWriter:
84 | public: false
85 |
86 | # Property Namer
87 | Trappar\AliceGenerator\PropertyNamer\PropertyNamer:
88 | public: false
89 | Trappar\AliceGenerator\PropertyNamer\PropertyNamerInterface: '@Trappar\AliceGenerator\PropertyNamer\PropertyNamer'
90 |
91 | # Fixture Generator
92 | Trappar\AliceGenerator\ValueVisitor:
93 | public: false
94 | arguments:
95 | - '@trappar_alice_generator.metadata.factory'
96 | - '@Trappar\AliceGeneratorBundle\Persister\DoctrinePersister'
97 | - '@Trappar\AliceGenerator\Metadata\Resolver\MetadataResolver'
98 | - '@Trappar\AliceGenerator\ObjectHandlerRegistry'
99 | - '@Trappar\AliceGenerator\PropertyNamer\PropertyNamerInterface'
100 |
101 |
102 | Trappar\AliceGenerator\FixtureGenerator:
103 | public: true
104 | arguments:
105 | - '@Trappar\AliceGenerator\ValueVisitor'
106 | - '@Trappar\AliceGenerator\YamlWriter'
107 | -
108 |
109 | # Command
110 | trappar_alice_generator.command.generate_fixtures:
111 | class: Trappar\AliceGeneratorBundle\Command\GenerateFixturesCommand
112 | tags: [ { name: console.command } ]
113 | arguments:
114 | - '@doctrine'
115 | - '@kernel'
116 | - '@Trappar\AliceGenerator\FixtureGenerator'
117 | - '@filesystem'
118 |
--------------------------------------------------------------------------------
/tests/Trappar/AliceGeneratorBundle/Command/GenerateFixturesCommandTest.php:
--------------------------------------------------------------------------------
1 | application = new Application(self::$kernel);
33 | $this->application->setAutoExit(false);
34 | $this->doctrine = static::$kernel->getContainer()->get('doctrine');
35 | }
36 |
37 | /**
38 | * @dataProvider getInteractiveCommandData
39 | * @param Options $options
40 | */
41 | public function testCommand(Options $options)
42 | {
43 | if ($options->generator === false) {
44 | $options->generator = $this->createMockGenerator();
45 | }
46 | if ($options->filesystem === false) {
47 | $options->filesystem = $this->createMockFilesystem();
48 | }
49 | if ($options->entities === false) {
50 | $options->entities = $this->getTestEntities();
51 | }
52 | if ($options->input) {
53 | for ($i=0; $i<20; $i++) {
54 | $options->input[] = '';
55 | }
56 | }
57 |
58 | $this->prepareDatabase($options->entities);
59 |
60 | if ($options->exception) {
61 | $this->expectException($options->exception);
62 | }
63 | if ($options->exceptionRegex) {
64 | $this->expectExceptionMessageRegExp($options->exceptionRegex);
65 | }
66 |
67 | $display = $this->getOutputFromCommandForInput($options);
68 |
69 | if ($options->displayRegex) {
70 | $this->assertRegExp($options->displayRegex, $display);
71 | }
72 | if ($options->noDisplayRegex) {
73 | $this->assertNotRegExp($options->noDisplayRegex, $display);
74 | }
75 | if (!$options->anyAssertionUsed()) {
76 | $this->assertRegExp('~Written successfully with no errors\!~', $display);
77 | }
78 | }
79 |
80 | public function getInteractiveCommandData()
81 | {
82 | $data = [];
83 |
84 | /** VALIDATION SPECIFIC TESTS **/
85 | // Invalid selection type
86 | $options = new Options();
87 | $options->input = ['TestBundle:User', 'asdf'];
88 | $options->displayRegex = '~invalid selection type~i';
89 | $data[] = [$options];
90 |
91 | // Invalid ID/integer
92 | $options = new Options();
93 | $options->input = ['TestBundle:User', 'id', 'abc'];
94 | $options->displayRegex = '~invalid non-int given~i';
95 | $data[] = [$options];
96 |
97 | // ID list with extra comma
98 | $options = new Options();
99 | $options->input = ['TestBundle:User', 'id', '1,,,2'];
100 | $options->displayRegex = '~id, \[1, 2\]~i';
101 | $data[] = [$options];
102 |
103 | // Invalid where conditions
104 | $options = new Options();
105 | $options->input = ['TestBundle:User', 'where', 'test', 'username: test'];
106 | $options->displayRegex = '~malformed inline yaml string~i';
107 | $data[] = [$options];
108 |
109 | // Where condition returned empty
110 | $options = new Options();
111 | $options->input = ['TestBundle:User', 'where', '', 'username: test'];
112 | $options->displayRegex = '~at least one condition~i';
113 | $data[] = [$options];
114 |
115 | // Where condition with non-existent field
116 | $options = new Options();
117 | $options->input = ['TestBundle:User', 'where', 'test: test', 'username: test'];
118 | $options->displayRegex = '~unknown field~i';
119 | $data[] = [$options];
120 |
121 | // Attempt yes/no with garbage
122 | $options = new Options();
123 | $options->input = ['TestBundle:User', '', 'asdf'];
124 | $options->displayRegex = '~"yes" or "no"~i';
125 | $data[] = [$options];
126 |
127 | // Output path with wrong extension
128 | $options = new Options();
129 | $options->input = ['TestBundle:User', '', '', '', '', 'test.exe'];
130 | $options->displayRegex = '~must have .yml extension~i';
131 | $data[] = [$options];
132 |
133 |
134 | /** TESTS WITH NO ERRORS **/
135 | // Selecting two posts by IDs
136 | $fg = $this->createMockGenerator();
137 | $fg->method('generateYaml')->with($this->logicalNot($this->arrayHasKey(2)));
138 | $options = new Options();
139 | $options->generator = $fg;
140 | $options->input = ['TestBundle:Post', 'id', '1,2'];
141 | $data[] = [$options];
142 |
143 | // Selecting a post by where
144 | $fg = $this->createMockGenerator();
145 | $fg->method('generateYaml')->with($this->logicalNot($this->arrayHasKey(1)));
146 | $options = new Options();
147 | $options->generator = $fg;
148 | $options->input = ['TestBundle:Post', 'where', 'title: My Title'];
149 | $data[] = [$options];
150 |
151 | // Overwrite existing file
152 | $options = new Options();
153 | $options->filesystem = $this->createMockFilesystem(true);
154 | $options->input = ['TestBundle:User', '', '', '', '', '', 'no'];
155 | $options->displayRegex = '~file already exists~i';
156 | $data[] = [$options];
157 |
158 | // Ensure that "text time" command displays custom output options
159 | $options = new Options();
160 | $options->options = ['-d' => 1, '-o' => 'test.yml'];
161 | $options->input = ['TestBundle:User'];
162 | $options->displayRegex = '~-d.*-o~';
163 | $data[] = [$options];
164 |
165 | // Depth and output location are at default so the "next time" command should not display those options
166 | $options = new Options();
167 | $options->input = ['TestBundle:User'];
168 | $options->noDisplayRegex = '~-d|-o~';
169 | $data[] = [$options];
170 |
171 | // Ensure duplicate configuration doesn't result in duplicate selection
172 | $options = new Options();
173 | $options->input = ['TestBundle:User', '', '', 'TestBundle:User'];
174 | $options->displayRegex = '~--entities="\[\'TestBundle:User\', all\]"~';
175 | $data[] = [$options];
176 |
177 | // Ensure that FixtureGenerationContext is being generated correctly
178 | $entities = $this->getTestEntities();
179 | list($post1, $post2) = $entities;
180 | $fg = $this->createMockGenerator();
181 | $fg->method('generateYaml')
182 | ->with($this->anything(), $this->callback(
183 | function (FixtureGenerationContext $context) use ($post1, $post2) {
184 | return $context->getPersistedObjectConstraints()->checkValid($post1) === true
185 | && $context->getPersistedObjectConstraints()->checkValid($post2) === false
186 | && $context->getMaximumRecursion() === 100;
187 | }
188 | ));
189 | $options = new Options();
190 | $options->generator = $fg;
191 | $options->input = ['TestBundle:Post', 'id', '', 'yes'];
192 | $options->entities = $entities;
193 | $options->options = ['-d' => 100];
194 | $data[] = [$options];
195 |
196 |
197 | /** TESTS WITH ERROR OUTPUT **/
198 | // Attempt not to select anything
199 | $options = new Options();
200 | $options->input = ['', 'TestBundle:User'];
201 | $options->displayRegex = '~no entities are selected for fixture generation~i';
202 | $data[] = [$options];
203 |
204 | // Attempt invalid where conditions
205 | $options = new Options();
206 | $options->options = ['--entities' => "['TestBundle:User', [where, { nonexistent: test }]]"];
207 | $options->displayRegex = '~unrecognized field: nonexistent~i';
208 | $data[] = [$options];
209 |
210 | // Attempt to select records by invalid ID
211 | $options = new Options();
212 | $options->options = ['--entities' => "['TestBundle:User', [id, [null]]]"];
213 | $options->displayRegex = '~The identifier id is missing~';
214 | $data[] = [$options];
215 |
216 | // Selecting a non-existent post by ID
217 | $fg = $this->createMockGenerator();
218 | $fg->method('generateYaml')->with($this->logicalNot($this->arrayHasKey(0)));
219 | $options = new Options();
220 | $options->generator = $fg;
221 | $options->displayRegex = '~no result returned~i';
222 | $options->input = ['TestBundle:Post', 'id', '3'];
223 | $data[] = [$options];
224 |
225 | // Attempt to save to an invalid location
226 | $options = new Options();
227 | $options->exception = \RuntimeException::class;
228 | $options->exceptionRegex = '~invalid characters~';
229 | $options->options = [
230 | '--entities' => '"[\'TestBundle:User\', all]"',
231 | '-o' => '@../blah.yml'
232 | ];
233 | $data[] = [$options];
234 |
235 | // Attempt to select non-existent entity
236 | $options = new Options();
237 | $options->displayRegex = '~Unable to locate repository~';
238 | $options->options = ['--entities' => "['TestBundle:BadEntity', all]"];
239 | $data[] = [$options];
240 |
241 | // Attempt to select all records for entity with no records
242 | $options = new Options();
243 | $options->displayRegex = '~no results returned~i';
244 | $options->options = ['--entities' => "['TestBundle:User', all]"];
245 | $options->entities = [];
246 | $data[] = [$options];
247 |
248 | // Attempt to select entities with selection type is missing
249 | $options = new Options();
250 | $options->displayRegex = '~not formatted correctly~i';
251 | //
252 | $options->options = ['--entities' => "['TestBundle:User']"];
253 | $data[] = [$options];
254 |
255 | // Attempt to select entities with malformed yaml - should be an array
256 | $options = new Options();
257 | $options->displayRegex = '~not formatted correctly~i';
258 | $options->options = ['--entities' => "'TestBundle:User', all"];
259 | $data[] = [$options];
260 |
261 | // Invalid entity name
262 | $options = new Options();
263 | $options->displayRegex = '~Unable to fetch entity information for "badEntity"~';
264 | $options->input = ['badEntity', 'TestBundle:Post'];
265 | $data[] = [$options];
266 |
267 | // Invalid selection type
268 | $options = new Options();
269 | $options->displayRegex = '~unknown selection type~i';
270 | $options->options = ['--entities' => "['TestBundle:User', asdf]"];
271 | $data[] = [$options];
272 |
273 | // Invalid selection type
274 | $options = new Options();
275 | $options->displayRegex = '~unknown selection type~i';
276 | $options->options = ['--entities' => "['TestBundle:User', [sadf]]"];
277 | $data[] = [$options];
278 |
279 | return $data;
280 | }
281 |
282 | private function getOutputFromCommandForInput(Options $options) //$input, FixtureGenerator $fixtureGenerator, Filesystem $filesystem, array $options = [])
283 | {
284 | $generateFixturesCommand = new GenerateFixturesCommand($this->doctrine, static::$kernel, $options->generator, $options->filesystem);
285 |
286 | $application = new \Symfony\Component\Console\Application();
287 | $application->add($generateFixturesCommand);
288 |
289 | $command = $application->find('generate:fixtures');
290 | $commandTester = new CommandTester($command);
291 |
292 | if (method_exists(CommandTester::class, 'setInputs')) {
293 | $commandTester->setInputs($options->input);
294 | } else {
295 | $stream = fopen('php://memory', 'r+', false);
296 | fwrite($stream, implode("\n", $options->input));
297 | rewind($stream);
298 |
299 | $helper = $command->getHelper('question');
300 | $helper->setInputStream($stream);
301 | }
302 |
303 | $commandTester->execute(array_merge(['command' => $command->getName()], $options->options));
304 |
305 | return $commandTester->getDisplay(true);
306 | }
307 |
308 | private function createMockFilesystem($fileExists = false)
309 | {
310 | $filesystem = $this->createMock(Filesystem::class);
311 | $filesystem
312 | ->expects($this->any())
313 | ->method('exists')
314 | ->willReturn($fileExists);
315 | $filesystem
316 | ->expects($this->any())
317 | ->method('dumpFile')
318 | ->with($this->isType('string'), 'test')
319 | ->willReturn(true);
320 |
321 | return $filesystem;
322 | }
323 |
324 | private function createMockGenerator()
325 | {
326 | $fixtureGenerator = $this->createMock(FixtureGenerator::class);
327 | $fixtureGenerator
328 | ->expects($this->any())
329 | ->method('generateYaml')
330 | ->will($this->returnValue('test'));
331 |
332 | return $fixtureGenerator;
333 | }
334 |
335 | private function getTestEntities()
336 | {
337 | $post = new Post();
338 | $post->body = 'Test this';
339 | $post->title = 'My Title';
340 |
341 | $post2 = new Post();
342 | $post2->title = 'blah';
343 | $post2->body = 'test';
344 |
345 | $user = new User();
346 | $user->username = 'test';
347 |
348 | $post->postedBy = $user;
349 | $post2->postedBy = $user;
350 |
351 | return [$post, $post2, $user];
352 | }
353 |
354 | private function prepareDatabase($entities)
355 | {
356 | $this->runConsole('doctrine:database:drop', ['--force' => true]);
357 | $this->runConsole('doctrine:schema:create');
358 |
359 | $em = $this->doctrine->getManager();
360 | foreach ($entities as $entity) {
361 | $em->persist($entity);
362 | }
363 |
364 | $em->flush();
365 | }
366 |
367 | /**
368 | * Helper to run a Symfony command.
369 | *
370 | * @param string $command
371 | * @param array $options
372 | *
373 | * @return int
374 | *
375 | * @throws \Exception
376 | */
377 | private function runConsole($command, array $options = [])
378 | {
379 | $options['-e'] = 'test';
380 | $options['-q'] = null;
381 | $options = array_merge($options, ['command' => $command]);
382 |
383 | return $this->application->run(new ArrayInput($options));
384 | }
385 |
386 | }
387 |
388 | class Options
389 | {
390 | public $exception = false;
391 | public $exceptionRegex = false;
392 | public $displayRegex = false;
393 | public $noDisplayRegex = false;
394 | public $input = [];
395 | public $options = [];
396 | /** @var bool|FixtureGenerator */
397 | public $generator = false;
398 | /** @var bool|Filesystem */
399 | public $filesystem = false;
400 | public $entities = false;
401 |
402 | public function anyAssertionUsed()
403 | {
404 | return $this->exception || $this->exceptionRegex || $this->displayRegex || $this->noDisplayRegex;
405 | }
406 | }
--------------------------------------------------------------------------------
/src/Trappar/AliceGeneratorBundle/Command/GenerateFixturesCommand.php:
--------------------------------------------------------------------------------
1 | doctrine = $managerRegistry;
66 | $this->kernel = $kernel;
67 | $this->fixtureGenerator = $fixtureGenerator;
68 | $this->filesystem = $filesystem;
69 |
70 | parent::__construct();
71 | }
72 |
73 | public function configure()
74 | {
75 | $this
76 | ->setName('generate:fixtures')
77 | ->setAliases(['doctrine:fixtures:generate'])
78 | ->setDescription('Generates fixtures based on Doctrine entities.')
79 | ->addOption('entities', null, InputOption::VALUE_REQUIRED, 'The entities which fixtures will be generator for')
80 | ->addOption('depth', 'd', InputOption::VALUE_OPTIONAL, 'Maximum depth to traverse through entities relations', self::DEFAULT_DEPTH)
81 | ->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Where to write the generated YAML file', null);
82 | }
83 |
84 | public function initialize(InputInterface $input, OutputInterface $output)
85 | {
86 | $entities = [];
87 | $entityNamespaces = [];
88 | $bundleNamesWithEntities = [];
89 |
90 | foreach ($this->doctrine->getManagers() as $manager) {
91 | if ($manager instanceof EntityManager) {
92 | $configuration = $manager->getConfiguration();
93 | $entities = array_merge($entities,
94 | $configuration->getMetadataDriverImpl()->getAllClassNames()
95 | );
96 | $entityNamespaces = array_merge($entityNamespaces,
97 | $configuration->getEntityNamespaces()
98 | );
99 | $bundleNamesWithEntities = array_merge($bundleNamesWithEntities,
100 | array_keys($configuration->getEntityNamespaces())
101 | );
102 | }
103 | }
104 |
105 | $this->entityAutocomplete = array_merge(
106 | array_keys($entityNamespaces),
107 | array_filter(array_map(function ($entity) use ($entityNamespaces) {
108 | foreach ($entityNamespaces as $alias => $entityNamespace) {
109 | $shortEntityName = str_replace($entityNamespace . '\\', $alias . ':', $entity);
110 | if ($shortEntityName != $entity) {
111 | return $shortEntityName;
112 | }
113 | }
114 |
115 | return false;
116 | }, $entities))
117 | );
118 | $this->bundleNamesWithEntities = $bundleNamesWithEntities;
119 |
120 | $this->questions = new Questions($input, $output, $this->getHelperSet());
121 | if (!$input->getOption('output')) {
122 | $input->setOption('output', $this->getDefaultOutputPath());
123 | }
124 | }
125 |
126 | public function interact(InputInterface $input, OutputInterface $output)
127 | {
128 | $configuredInteractively = !$input->getOption('entities');
129 |
130 | $this->writeSection($output, 'Welcome to the Alice fixture generator');
131 | $output->writeln('This command helps generate Alice fixtures based on existing Doctrine entities.');
132 |
133 | if (!$input->getOption('entities')) {
134 | $entitiesString = $this->askForEntityConfig($output);
135 | $input->setOption('entities', $entitiesString);
136 | }
137 |
138 | if ($configuredInteractively && ($this->isDepthDefault($input) || $this->isOutputPathDefault($input))) {
139 | // We only need to display this if the user hasn't already specified these options directly
140 | $this->writeSection($output, 'Output options');
141 | }
142 |
143 | if ($configuredInteractively && $this->isDepthDefault($input)) {
144 | $output->writeln(array('', 'How deep should the fixture generator recurse through entities\' relations?'));
145 |
146 | $depth = $this->questions->askForRecursionDepth();
147 | $input->setOption('depth', $depth);
148 | }
149 |
150 | if ($configuredInteractively && $this->isOutputPathDefault($input)) {
151 | while (true) {
152 | $output->writeln(array('', 'Where should the generated YAML file be written?'));
153 |
154 | $outputPath = $this->questions->askForOutputDirectory($this->bundleNamesWithEntities, $this->getDefaultOutputPath());
155 |
156 | if ($this->filesystem->exists($this->locate($outputPath))) {
157 | $output->writeln(array('', 'File already exists, overwrite?'));
158 |
159 | if (!$this->questions->askIfFileOverwrite()) {
160 | continue;
161 | }
162 | }
163 |
164 | $input->setOption('output', $outputPath);
165 | break;
166 | }
167 | }
168 |
169 | if ($configuredInteractively) {
170 | $this->writeSection($output,
171 | 'Next time you call this command you can use the following options to rerun with the same options as ' .
172 | 'you configured interactively'
173 | );
174 |
175 | $includedOptions = [
176 | '--entities="' . $input->getOption('entities') . '"'
177 | ];
178 | if (!$this->isDepthDefault($input)) {
179 | $includedOptions[] = '-d' . $input->getOption('depth');
180 | }
181 | if (!$this->isOutputPathDefault($input)) {
182 | $includedOptions[] = '-o"' . $input->getOption('output') . '"';
183 | }
184 |
185 | $output->writeln(sprintf(
186 | 'console %s %s',
187 | $this->getName(),
188 | implode(' ', $includedOptions)
189 | ));
190 | }
191 | }
192 |
193 | private function askForEntityConfig(OutputInterface $output)
194 | {
195 | $entities = [];
196 | while (true) {
197 | $output->writeln(['', '',
198 | 'Enter an entity which will be used to generate fixtures or keep blank to exit entity selection.',
199 | 'You must use the shortcut notation like AcmeBlogBundle:Post.'
200 | ]);
201 |
202 | /** @var ClassMetadata $entityMetadata */
203 | list($entityAlias, $entityMetadata) = $this->questions->askForEntity($this->doctrine, $this->entityAutocomplete);
204 |
205 | if (!$entityMetadata) {
206 | if (count($entities) == 0) {
207 | $output->writeln(['',
208 | 'Currently no entities are selected for fixture generation, you must specify at least one.',
209 | ]);
210 | continue;
211 | } else {
212 | break;
213 | }
214 | }
215 |
216 | $entityConfig = [$entityAlias];
217 |
218 | $selectionTypesFormatted = implode(', ', array_map(function ($type) {
219 | return "$type";
220 | }, self::$selectionTypes));
221 |
222 | $output->writeln(['',
223 | 'Which records will be used for generating entities?',
224 | 'Available selection types: ' . $selectionTypesFormatted
225 | ]);
226 |
227 | $selectionType = $this->questions->askForSelectionType(self::$selectionTypes);
228 |
229 | $selection = '';
230 | switch ($selectionType) {
231 | case 'all':
232 | $selection = $selectionType;
233 | break;
234 | case 'id':
235 | $output->writeln(['',
236 | 'Enter a comma separated list of all IDs to include in fixtures for ' . $entityAlias . ''
237 | ]);
238 |
239 | $ids = $this->questions->askForIDs();
240 | $selection = [$selectionType, $ids];
241 | break;
242 | case 'where':
243 | $output->writeln(['',
244 | 'Enter YAML dictionary of DQL where conditions used to find entities for ' . $entityAlias . '',
245 | sprintf(
246 | 'Available fields: %s',
247 | implode(', ', array_map(function ($field) {
248 | return "$field";
249 | }, $entityMetadata->getFieldNames()))
250 | ),
251 | 'Example: username:test, email:test@email.com',
252 | ]);
253 |
254 | $where = $this->questions->askForWhereConditions($entityMetadata);
255 | $selection = [$selectionType, $where];
256 | break;
257 | }
258 |
259 | $entityConfig[] = $selection;
260 |
261 | $output->writeln(['',
262 | 'Would you like to add entities selected in this way as object constraints? (see AliceGenerator usage documentation)'
263 | ]);
264 |
265 | if ($this->questions->askIfAddAsEntityConstraints()) {
266 | $entityConfig[] = true;
267 | }
268 |
269 | $entities[] = $entityConfig;
270 | }
271 |
272 | $entities = array_unique($entities, SORT_REGULAR);
273 |
274 | $entitiesString = Yaml::dump($entities, 0);
275 | $entitiesString = preg_replace('~^\[|\]$~', '', $entitiesString);
276 |
277 | return $entitiesString;
278 | }
279 |
280 | /**
281 | * @see Command
282 | * @param InputInterface $input
283 | * @param OutputInterface $output
284 | * @return int|null|void
285 | */
286 | public function execute(InputInterface $input, OutputInterface $output)
287 | {
288 | $this->writeSection($output, 'Generating Fixture File');
289 |
290 | $entitiesConfig = Yaml::parse('[' . $input->getOption('entities') . ']');
291 |
292 | $entities = [];
293 | $errors = [];
294 |
295 | $fixtureGenerationContext = FixtureGenerationContext::create();
296 | $fixtureGenerationContext->setMaximumRecursion($input->getOption('depth'));
297 |
298 | foreach ($entitiesConfig as $entityConfig) {
299 | if (!is_array($entityConfig) || count($entityConfig) < 2) {
300 | $errors[] = $this->getEntitySelectionError('Not formatted correctly', $entityConfig);
301 | continue;
302 | }
303 |
304 | list($entityAlias, $selectionType) = $entityConfig;
305 | $addToEntityConstraints = (isset($entityConfig[2])) ? $entityConfig[2] : false;
306 |
307 | try {
308 | $repo = $this->doctrine->getManagerForClass($entityAlias)->getRepository($entityAlias);
309 | } catch (\Exception $e) {
310 | $errors[] = $this->getEntitySelectionError('Unable to locate repository', $entityConfig);
311 | continue;
312 | }
313 |
314 | $results = [];
315 |
316 | $selectionProcessed = true;
317 | if ($selectionType === 'all') {
318 | $results = $repo->findAll();
319 |
320 | if (!count($results)) {
321 | $errors[] = $this->getEntitySelectionError('No results returned', $entityConfig, false);
322 | }
323 | } elseif (is_array($selectionType) && count($selectionType) == 2) {
324 | if ($selectionType[0] === 'id') {
325 | foreach ($selectionType[1] as $id) {
326 | try {
327 | $result = $repo->find($id);
328 |
329 | if ($result) {
330 | $results[] = $result;
331 | } else {
332 | $errors[] = $this->getEntitySelectionError("No result returned for ID: $id", $entityConfig, false);
333 | }
334 | } catch (\Exception $e) {
335 | $errors[] = $this->getEntitySelectionError($e->getMessage(), $entityConfig);
336 | }
337 | }
338 | } elseif ($selectionType[0] === 'where') {
339 | try {
340 | $results = $repo->findBy($selectionType[1]);
341 | } catch (\Exception $e) {
342 | $errors[] = $this->getEntitySelectionError($e->getMessage(), $entityConfig);
343 | }
344 | } else {
345 | $selectionProcessed = false;
346 | }
347 | } else {
348 | $selectionProcessed = false;
349 | }
350 |
351 | if (!$selectionProcessed) {
352 | $errors[] = $this->getEntitySelectionError("Unknown selection type or format", $entityConfig);
353 | }
354 |
355 | if ($addToEntityConstraints) {
356 | foreach ($results as $entity) {
357 | $fixtureGenerationContext->addPersistedObjectConstraint($entity);
358 | }
359 | }
360 | $entities = array_merge($entities, $results);
361 | }
362 |
363 | $yaml = $this->fixtureGenerator->generateYaml($entities, $fixtureGenerationContext);
364 |
365 | $outputFile = $this->locate($input->getOption('output'));
366 |
367 | $output->writeln([
368 | sprintf(
369 | 'Writing generated fixtures to "%s"',
370 | $outputFile
371 | )
372 | ]);
373 |
374 | $this->writeFile($outputFile, $yaml);
375 |
376 | if (!count($errors)) {
377 | $this->writeSection($output, 'Written successfully with no errors!');
378 | } else {
379 | $this->writeSection($output,
380 | 'There were errors during fixture generation, check the errors below and the generated fixture file for more information.',
381 | 'bg=yellow;fg=black'
382 | );
383 | foreach ($errors as $error) {
384 | $output->writeln(sprintf('%s', $error));
385 | }
386 | }
387 | }
388 |
389 | private function writeFile($outputPath, $contents)
390 | {
391 | $outputDirectory = pathinfo($outputPath, PATHINFO_DIRNAME);
392 |
393 | if (!$this->filesystem->exists($outputDirectory)) {
394 | $this->filesystem->mkdir($outputDirectory);
395 | }
396 |
397 | $this->filesystem->dumpFile($outputPath, $contents);
398 | }
399 |
400 | /**
401 | * The implementation of this in FileLocator doesn't allow for files which don't exist, so this is a simple
402 | * reimplementation of that
403 | *
404 | * @param $file
405 | * @return string
406 | */
407 | private function locate($file)
408 | {
409 | if (isset($file[0]) && '@' === $file[0]) {
410 | if (false !== strpos($file, '..')) {
411 | throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $file));
412 | }
413 |
414 | $bundleName = substr($file, 1);
415 | $path = '';
416 | if (false !== strpos($bundleName, '/')) {
417 | list($bundleName, $path) = explode('/', $bundleName, 2);
418 | }
419 |
420 | $bundle = $this->kernel->getBundle($bundleName);
421 | $file = $bundle->getPath() . '/' . $path;
422 | }
423 |
424 | return $file;
425 | }
426 |
427 | private function isDepthDefault(InputInterface $input)
428 | {
429 | return $input->getOption('depth') == self::DEFAULT_DEPTH;
430 | }
431 |
432 | private function isOutputPathDefault(InputInterface $input)
433 | {
434 | return $input->getOption('output') == $this->getDefaultOutputPath();
435 | }
436 |
437 | private function getDefaultOutputPath()
438 | {
439 | $defaultOutputPath = false;
440 | if (count($this->bundleNamesWithEntities) > 0) {
441 | $suggestedBundle = $this->bundleNamesWithEntities[0];
442 | $defaultOutputPath = '@' . $suggestedBundle . self::DEFAULT_OUTPUT_PATH_SUFFIX;
443 | }
444 |
445 | return $defaultOutputPath;
446 | }
447 |
448 | private function writeSection(OutputInterface $output, $text, $style = 'bg=blue;fg=white')
449 | {
450 | $output->writeln(array(
451 | '',
452 | $this->getHelper('formatter')->formatBlock($text, $style, true),
453 | '',
454 | ));
455 | }
456 |
457 | private function getEntitySelectionError($message, $entityConfig, $displaySkipped = true)
458 | {
459 | $parts = [$message];
460 |
461 | if ($displaySkipped) {
462 | $parts[] = 'Selection skipped';
463 | }
464 |
465 | return
466 | sprintf('Selection %s - ', Yaml::dump($entityConfig, 0))
467 | . implode('. ', array_filter($parts))
468 | . '.';
469 | }
470 | }
--------------------------------------------------------------------------------