Completed
Push — 3.x ( 9aa22d...aa303f )
by Grégoire
04:40
created

AdminMaker::configureDependencies()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Maker;
13
14
use Sonata\AdminBundle\Command\Validators;
15
use Sonata\AdminBundle\Manipulator\ServicesManipulator;
16
use Sonata\AdminBundle\Model\ModelManagerInterface;
17
use Symfony\Bundle\MakerBundle\ConsoleStyle;
18
use Symfony\Bundle\MakerBundle\DependencyBuilder;
19
use Symfony\Bundle\MakerBundle\Generator;
20
use Symfony\Bundle\MakerBundle\InputConfiguration;
21
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
22
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
23
use Symfony\Component\Console\Command\Command;
24
use Symfony\Component\Console\Input\InputArgument;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\InputOption;
27
use Symfony\Component\DependencyInjection\Container;
28
29
/**
30
 * @author Gaurav Singh Faujdar <[email protected]>
31
 */
32
final class AdminMaker extends AbstractMaker
33
{
34
    /**
35
     * @var string
36
     */
37
    private $projectDirectory;
38
    /**
39
     * @var string[]
40
     */
41
    private $availableModelManagers;
42
    /**
43
     * @var string
44
     */
45
    private $skeletonDirectory;
46
    /**
47
     * @var string
48
     */
49
    private $modelClass;
50
    /**
51
     * @var string
52
     */
53
    private $modelClassBasename;
54
    /**
55
     * @var string
56
     */
57
    private $adminClassBasename;
58
    /**
59
     * @var string
60
     */
61
    private $controllerClassBasename;
62
    /**
63
     * @var string
64
     */
65
    private $managerType;
66
    /**
67
     * @var ModelManagerInterface
68
     */
69
    private $modelManager;
70
71
    public function __construct($projectDirectory, array $modelManagers = [])
72
    {
73
        $this->projectDirectory = $projectDirectory;
74
        $this->availableModelManagers = $modelManagers;
75
        $this->skeletonDirectory = __DIR__.'/../Resources/skeleton';
76
    }
77
78
    public static function getCommandName(): string
79
    {
80
        return 'make:sonata:admin';
81
    }
82
83
    public function configureCommand(Command $command, InputConfiguration $inputConfig)
84
    {
85
        $command
86
            ->setDescription('Generates an admin class based on the given model class')
87
            ->addArgument('model', InputArgument::REQUIRED, 'The fully qualified model class')
88
            ->addOption('admin', 'a', InputOption::VALUE_OPTIONAL, 'The admin class basename')
89
            ->addOption('controller', 'c', InputOption::VALUE_OPTIONAL, 'The controller class basename')
90
            ->addOption('manager', 'm', InputOption::VALUE_OPTIONAL, 'The model manager type')
91
            ->addOption('services', 's', InputOption::VALUE_OPTIONAL, 'The services YAML file', 'services.yaml')
92
            ->addOption('id', 'i', InputOption::VALUE_OPTIONAL, 'The admin service ID');
93
94
        $inputConfig->setArgumentAsNonInteractive('model');
95
    }
96
97
    public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
98
    {
99
        $io->section('Welcome to the Sonata Admin');
100
        $modelClass = $io->ask(
101
            'The fully qualified model class',
102
            $input->getArgument('model'),
103
            [Validators::class, 'validateClass']
104
        );
105
        $modelClassBasename = current(\array_slice(explode('\\', $modelClass), -1));
106
107
        $adminClassBasename = $io->ask(
108
            'The admin class basename',
109
            $input->getOption('admin') ?: $modelClassBasename.'Admin',
110
            [Validators::class, 'validateAdminClassBasename']
111
        );
112
        if (\count($this->availableModelManagers) > 1) {
113
            $managerTypes = array_keys($this->availableModelManagers);
114
            $managerType = $io->choice('The manager type', $managerTypes, $managerTypes[0]);
115
116
            $input->setOption('manager', $managerType);
117
        }
118
        if ($io->confirm('Do you want to generate a controller?', false)) {
119
            $controllerClassBasename = $io->ask(
120
                'The controller class basename',
121
                $input->getOption('controller') ?: $modelClassBasename.'AdminController',
122
                [Validators::class, 'validateControllerClassBasename']
123
            );
124
            $input->setOption('controller', $controllerClassBasename);
125
        }
126
        $input->setOption('services', false);
127
        if ($io->confirm('Do you want to update the services YAML configuration file?', true)) {
128
            $path = $this->projectDirectory.'/config/';
129
            $servicesFile = $io->ask(
130
                'The services YAML configuration file',
131
                is_file($path.'admin.yaml') ? 'admin.yaml' : 'services.yaml',
132
                [Validators::class, 'validateServicesFile']
133
            );
134
            $id = $io->ask(
135
                'The admin service ID',
136
                $this->getAdminServiceId($adminClassBasename),
137
                [Validators::class, 'validateServiceId']
138
            );
139
            $input->setOption('services', $servicesFile);
140
            $input->setOption('id', $id);
141
        }
142
        $input->setArgument('model', $modelClass);
143
        $input->setOption('admin', $adminClassBasename);
144
    }
145
146
    /**
147
     * Configure any library dependencies that your maker requires.
148
     */
149
    public function configureDependencies(DependencyBuilder $dependencies)
150
    {
151
    }
152
153
    /**
154
     * Called after normal code generation: allows you to do anything.
155
     */
156
    public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
157
    {
158
        $this->configure($input);
159
160
        $adminClassNameDetails = $generator->createClassNameDetails(
161
            $this->modelClassBasename,
162
            'Admin\\',
163
            'Admin'
164
        );
165
166
        $adminClassFullName = $adminClassNameDetails->getFullName();
167
        $this->generateAdmin($io, $generator, $adminClassNameDetails);
168
169
        $controllerClassFullName = null;
170
        if ($this->controllerClassBasename) {
171
            $controllerClassNameDetails = $generator->createClassNameDetails(
172
                $this->controllerClassBasename,
173
                'Controller\\',
174
                'Controller'
175
            );
176
177
            $controllerClassFullName = $controllerClassNameDetails->getFullName();
178
        }
179
180
        $this->generateController($input, $io, $generator, $controllerClassNameDetails);
0 ignored issues
show
Bug introduced by
The variable $controllerClassNameDetails does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
181
        $this->generateService($input, $io, $adminClassFullName, $controllerClassFullName);
182
    }
183
184
    private function getAdminServiceId(string $adminClassBasename): string
185
    {
186
        return Container::underscore(sprintf(
187
            'admin.%s',
188
            str_replace('\\', '.', 'Admin' == substr($adminClassBasename, -5) ?
189
                substr($adminClassBasename, 0, -5) : $adminClassBasename)
190
        ));
191
    }
192
193
    private function generateService(
194
        InputInterface $input,
195
        ConsoleStyle $io,
196
        string $adminClassFullName,
197
        string $controllerClassFullName
198
    ) {
199
        if ($servicesFile = $input->getOption('services')) {
200
            $file = sprintf('%s/config/%s', $this->projectDirectory, $servicesFile);
201
            $servicesManipulator = new ServicesManipulator($file);
202
            $controllerName = $this->controllerClassBasename ? $controllerClassFullName : '~';
203
204
            $id = $input->getOption('id') ?:
205
                $this->getAdminServiceId('App', $this->adminClassBasename);
206
207
            $servicesManipulator->addResource(
208
                $id,
209
                $this->modelClass,
210
                $adminClassFullName,
211
                $controllerName,
212
                substr($this->managerType, \strlen('sonata.admin.manager.'))
213
            );
214
215
            $io->writeln(sprintf(
216
                '%sThe service "<info>%s</info>" has been appended to the file <info>"%s</info>".',
217
                PHP_EOL,
218
                $id,
219
                realpath($file)
220
            ));
221
        }
222
    }
223
224
    private function generateController(
225
        InputInterface $input,
226
        ConsoleStyle $io,
227
        Generator $generator,
228
        ClassNameDetails $controllerClassNameDetails
229
    ) {
230
        $controllerClassFullName = null;
0 ignored issues
show
Unused Code introduced by
$controllerClassFullName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
231
        if ($controllerClassNameDetails) {
232
            $controllerClassFullName = $controllerClassNameDetails->getFullName();
233
            $generator->generateClass($controllerClassFullName,
234
                $this->skeletonDirectory.'/AdminController.tpl.php',
235
                []
236
            );
237
            $generator->writeChanges();
238
            $io->writeln(sprintf(
239
                '%sThe controller class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
240
                PHP_EOL,
241
                $controllerClassNameDetails->getShortName(),
242
                $controllerClassFullName
243
            ));
244
        }
245
    }
246
247
    private function generateAdmin(ConsoleStyle $io, Generator $generator, ClassNameDetails $adminClassNameDetails)
248
    {
249
        $adminClassFullName = $adminClassNameDetails->getFullName();
250
251
        $fields = $this->modelManager->getExportFields($this->modelClass);
252
        $fieldString = '';
253
        foreach ($fields as $field) {
254
            $fieldString = $fieldString."\t\t\t->add('".$field."')".PHP_EOL;
255
        }
256
257
        $fieldString .= "\t\t\t";
258
259
        $generator->generateClass($adminClassFullName,
260
            $this->skeletonDirectory.'/Admin.tpl.php',
261
            ['fields' => $fieldString]);
262
263
        $generator->writeChanges();
264
265
        $io->writeln(sprintf(
266
            '%sThe admin class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
267
            PHP_EOL,
268
            $adminClassNameDetails->getShortName(),
269
            $adminClassFullName
270
        ));
271
    }
272
273
    private function configure(InputInterface $input)
274
    {
275
        $this->modelClass = Validators::validateClass($input->getArgument('model'));
276
        $this->modelClassBasename = (new \ReflectionClass($this->modelClass))->getShortName();
277
        $this->adminClassBasename = Validators::validateAdminClassBasename(
278
            $input->getOption('admin') ?: $this->modelClassBasename.'Admin'
279
        );
280
281
        if ($this->controllerClassBasename = $input->getOption('controller')) {
282
            $this->controllerClassBasename = Validators::validateControllerClassBasename($this->controllerClassBasename);
283
        }
284
285
        if (!\count($this->availableModelManagers)) {
286
            throw new \RuntimeException('There are no model managers registered.');
287
        }
288
289
        $this->managerType = $input->getOption('manager') ?: array_keys($this->availableModelManagers)[0];
290
        $this->modelManager = $this->availableModelManagers[$this->managerType] ?: current($this->availableModelManagers);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->availableModelMan...availableModelManagers) of type string or false is incompatible with the declared type object<Sonata\AdminBundl...\ModelManagerInterface> of property $modelManager.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
291
    }
292
}
293