Passed
Pull Request — master (#173)
by
unknown
02:42
created

MakeFactory::interact()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.1406

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 3
dl 0
loc 15
ccs 9
cts 12
cp 0.75
crap 3.1406
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Zenstruck\Foundry\Bundle\Maker;
4
5
6
use Doctrine\Persistence\ManagerRegistry;
7
use Symfony\Bundle\MakerBundle\ConsoleStyle;
8
use Symfony\Bundle\MakerBundle\DependencyBuilder;
9
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
10
use Symfony\Bundle\MakerBundle\Generator;
11
use Symfony\Bundle\MakerBundle\InputConfiguration;
12
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Zenstruck\Foundry\Bundle\Extractor\Property;
18
use Zenstruck\Foundry\ModelFactory;
19
20
/**
21
 * @author Kevin Bond <[email protected]>
22
 */
23
final class MakeFactory extends AbstractMaker
24
{
25 20
    /** @var ManagerRegistry */
26
    private $managerRegistry;
27 20
28 20
    /** @var string[] */
29
    private $entitiesWithFactories;
30 4
    private Property $propertyExtractor;
31
32 4
    public function __construct(ManagerRegistry $managerRegistry, \Traversable $factories, Property $propertyExtractor)
33
    {
34
        $this->managerRegistry = $managerRegistry;
35 20
        $this->entitiesWithFactories = \array_map(
36
            static function(ModelFactory $factory) {
37
                return $factory::getEntityClass();
38 20
            },
39 20
            \iterator_to_array($factories)
40 20
        );
41
        $this->propertyExtractor = $propertyExtractor;
42
    }
43 20
44 20
    public static function getCommandName(): string
45
    {
46 20
        return 'make:factory';
47
    }
48 20
49 12
    public static function getCommandDescription(): string
50
    {
51
        return 'Creates a Foundry model factory for a Doctrine entity class';
52 8
    }
53 4
54 4
    public function configureCommand(Command $command, InputConfiguration $inputConfig): void
55
    {
56
        $command
57 8
            ->setDescription(self::getCommandDescription())
58 8
            ->addArgument('entity', InputArgument::OPTIONAL, 'Entity class to create a factory for')
59
            ->addOption('namespace', null, InputOption::VALUE_REQUIRED, 'Customize the namespace for generated factories', 'Factory')
60 8
            ->addOption('test', null, InputOption::VALUE_NONE, 'Create in <fg=yellow>tests/</> instead of <fg=yellow>src/</>')
61 8
        ;
62
63 20
        $inputConfig->setArgumentAsNonInteractive('entity');
64
    }
65 20
66
    public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
67 20
    {
68 4
        if ($input->getArgument('entity')) {
69
            return;
70
        }
71 20
72 4
        if (!$input->getOption('test')) {
73
            $io->text('// Note: pass <fg=yellow>--test</> if you want to generate factories in your <fg=yellow>tests/</> directory');
74
            $io->newLine();
75 16
        }
76 16
77 16
        $argument = $command->getDefinition()->getArgument('entity');
78 16
        $entity = $io->choice($argument->getDescription(), $this->entityChoices());
79 16
80
        $input->setArgument('entity', $entity);
81
    }
82 16
83
    public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
84 16
    {
85
        $class = $input->getArgument('entity');
86 16
87
        if (!\class_exists($class)) {
0 ignored issues
show
Bug introduced by
It seems like $class can also be of type null and string[]; however, parameter $class of class_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
        if (!\class_exists(/** @scrutinizer ignore-type */ $class)) {
Loading history...
88
            $class = $generator->createClassNameDetails($class, 'Entity\\')->getFullName();
0 ignored issues
show
Bug introduced by
It seems like $class can also be of type null and string[]; however, parameter $name of Symfony\Bundle\MakerBund...reateClassNameDetails() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

88
            $class = $generator->createClassNameDetails(/** @scrutinizer ignore-type */ $class, 'Entity\\')->getFullName();
Loading history...
89 16
        }
90 16
91 16
        if (!\class_exists($class)) {
92
            throw new RuntimeCommandException(\sprintf('Entity "%s" not found.', $input->getArgument('entity')));
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('entity') can also be of type string[]; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

92
            throw new RuntimeCommandException(\sprintf('Entity "%s" not found.', /** @scrutinizer ignore-type */ $input->getArgument('entity')));
Loading history...
93 16
        }
94 16
95
        $namespace = $input->getOption('namespace');
96
97
        // strip maker's root namespace if set
98 16
        if (0 === \mb_strpos($namespace, $generator->getRootNamespace())) {
99
            $namespace = \mb_substr($namespace, \mb_strlen($generator->getRootNamespace()));
100 16
        }
101
102 16
        $namespace = \trim($namespace, '\\');
103 16
104
        // if creating in tests dir, ensure namespace prefixed with Tests\
105
        if ($input->getOption('test') && 0 !== \mb_strpos($namespace, 'Tests\\')) {
106 16
            $namespace = 'Tests\\'.$namespace;
107
        }
108 20
109
        $entity = new \ReflectionClass($class);
0 ignored issues
show
Bug introduced by
It seems like $class can also be of type string[]; however, parameter $objectOrClass of ReflectionClass::__construct() does only seem to accept object|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

109
        $entity = new \ReflectionClass(/** @scrutinizer ignore-type */ $class);
Loading history...
110
        $factory = $generator->createClassNameDetails($entity->getShortName(), $namespace, 'Factory');
111 20
112
        $repository = new \ReflectionClass($this->managerRegistry->getRepository($entity->getName()));
113 8
114
        if (0 !== \mb_strpos($repository->getName(), $generator->getRootNamespace())) {
115 8
            // not using a custom repository
116
            $repository = null;
117 8
        }
118 8
119 8
        $defaultProperties = $this->getProperties($entity);
120
121
        $generator->generateClass(
122
            $factory->getFullName(),
123 8
            __DIR__.'/../Resources/skeleton/Factory.tpl.php',
124
            [
125 8
                'entity' => $entity,
126
                'defaultProperties' => $defaultProperties,
127
                'repository' => $repository,
128
            ]
129
        );
130
131
        $generator->writeChanges();
132
133
        $this->writeSuccessMessage($io);
134
135
        $io->text([
136
            'Next: Open your new factory and set default values/states.',
137
            'Find the documentation at https://github.com/zenstruck/foundry#model-factories',
138
        ]);
139
    }
140
141
    public function configureDependencies(DependencyBuilder $dependencies): void
142
    {
143
        // noop
144
    }
145
146
    private function entityChoices(): array
147
    {
148
        $choices = [];
149
150
        foreach ($this->managerRegistry->getManagers() as $manager) {
151
            foreach ($manager->getMetadataFactory()->getAllMetadata() as $metadata) {
152
                if (!\in_array($metadata->getName(), $this->entitiesWithFactories, true)) {
153
                    $choices[] = $metadata->getName();
154
                }
155
            }
156
        }
157
158
        \sort($choices);
159
160
        if (empty($choices)) {
161
            throw new RuntimeCommandException('No entities found.');
162
        }
163
164
        return $choices;
165
    }
166
167
    private function getProperties($classname)
168
    {
169
        $properties = $this->propertyExtractor->getScalarPropertiesFromDoctrineFieldMappings($classname);
170
171
        return $properties;
172
    }
173
}
174