Completed
Push — 3.x ( 64b83e...0e8449 )
by Oskar
04:28
created

GenerateAdminCommand::getKernel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Command;
15
16
use Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle;
17
use Sonata\AdminBundle\Controller\CRUDController;
18
use Sonata\AdminBundle\Generator\AdminGenerator;
19
use Sonata\AdminBundle\Generator\ControllerGenerator;
20
use Sonata\AdminBundle\Manipulator\ServicesManipulator;
21
use Sonata\AdminBundle\Model\ModelManagerInterface;
22
use Symfony\Bundle\FrameworkBundle\Console\Application;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\DependencyInjection\Container;
28
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
29
use Symfony\Component\HttpKernel\KernelInterface;
30
31
/**
32
 * @author Marek Stipek <[email protected]>
33
 * @author Simon Cosandey <[email protected]>
34
 */
35
class GenerateAdminCommand extends QuestionableCommand
36
{
37
    /**
38
     * @var string[]
39
     */
40
    private $managerTypes;
41
42
    public function configure()
43
    {
44
        $this
45
            ->setName('sonata:admin:generate')
46
            ->setDescription('Generates an admin class based on the given model class')
47
            ->addArgument('model', InputArgument::REQUIRED, 'The fully qualified model class')
48
            ->addOption('bundle', 'b', InputOption::VALUE_OPTIONAL, 'The bundle name')
49
            ->addOption('admin', 'a', InputOption::VALUE_OPTIONAL, 'The admin class basename')
50
            ->addOption('controller', 'c', InputOption::VALUE_OPTIONAL, 'The controller class basename')
51
            ->addOption('manager', 'm', InputOption::VALUE_OPTIONAL, 'The model manager type')
52
            ->addOption('services', 'y', InputOption::VALUE_OPTIONAL, 'The services YAML file', 'services.yml')
53
            ->addOption('id', 'i', InputOption::VALUE_OPTIONAL, 'The admin service ID')
54
        ;
55
    }
56
57
    public function isEnabled()
58
    {
59
        return class_exists(SensioGeneratorBundle::class);
60
    }
61
62
    /**
63
     * @param string $managerType
64
     *
65
     * @throws \InvalidArgumentException
66
     *
67
     * @return string
68
     */
69
    public function validateManagerType($managerType)
70
    {
71
        $managerTypes = $this->getAvailableManagerTypes();
72
73
        if (!isset($managerTypes[$managerType])) {
74
            throw new \InvalidArgumentException(sprintf(
75
                'Invalid manager type "%s". Available manager types are "%s".',
76
                $managerType,
77
                implode('", "', $managerTypes)
78
            ));
79
        }
80
81
        return $managerType;
82
    }
83
84
    protected function execute(InputInterface $input, OutputInterface $output)
85
    {
86
        $modelClass = Validators::validateClass($input->getArgument('model'));
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('model') targeting Symfony\Component\Consol...nterface::getArgument() can also be of type array<integer,string> or null; however, Sonata\AdminBundle\Comma...dators::validateClass() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
87
        $modelClassBasename = current(\array_slice(explode('\\', $modelClass), -1));
88
        $bundle = $this->getBundle($input->getOption('bundle') ?: $this->getBundleNameFromClass($modelClass));
89
        $adminClassBasename = $input->getOption('admin') ?: $modelClassBasename.'Admin';
90
        $adminClassBasename = Validators::validateAdminClassBasename($adminClassBasename);
91
        $managerType = $input->getOption('manager') ?: $this->getDefaultManagerType();
92
        $modelManager = $this->getModelManager($managerType);
93
        $skeletonDirectory = __DIR__.'/../Resources/skeleton';
94
        $adminGenerator = new AdminGenerator($modelManager, $skeletonDirectory);
95
96
        try {
97
            $adminGenerator->generate($bundle, $adminClassBasename, $modelClass);
98
            $output->writeln(sprintf(
99
                '%sThe admin class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
100
                PHP_EOL,
101
                $adminGenerator->getClass(),
102
                realpath($adminGenerator->getFile())
103
            ));
104
        } catch (\Exception $e) {
105
            $this->writeError($output, $e->getMessage());
106
        }
107
108
        $controllerName = CRUDController::class;
109
110
        if ($controllerClassBasename = $input->getOption('controller')) {
111
            $controllerClassBasename = Validators::validateControllerClassBasename($controllerClassBasename);
112
            $controllerGenerator = new ControllerGenerator($skeletonDirectory);
113
114
            try {
115
                $controllerGenerator->generate($bundle, $controllerClassBasename);
116
                $controllerName = $controllerGenerator->getClass();
117
                $output->writeln(sprintf(
118
                    '%sThe controller class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
119
                    PHP_EOL,
120
                    $controllerName,
121
                    realpath($controllerGenerator->getFile())
122
                ));
123
            } catch (\Exception $e) {
124
                $this->writeError($output, $e->getMessage());
125
            }
126
        }
127
128
        if ($servicesFile = $input->getOption('services')) {
129
            $adminClass = $adminGenerator->getClass();
130
            $file = sprintf('%s/Resources/config/%s', $bundle->getPath(), $servicesFile);
131
            $servicesManipulator = new ServicesManipulator($file);
132
133
            try {
134
                $id = $input->getOption('id') ?: $this->getAdminServiceId($bundle->getName(), $adminClassBasename);
135
                $servicesManipulator->addResource($id, $modelClass, $adminClass, $controllerName, $managerType);
136
                $output->writeln(sprintf(
137
                    '%sThe service "<info>%s</info>" has been appended to the file <info>"%s</info>".',
138
                    PHP_EOL,
139
                    $id,
140
                    realpath($file)
141
                ));
142
            } catch (\Exception $e) {
143
                $this->writeError($output, $e->getMessage());
144
            }
145
        }
146
147
        return 0;
148
    }
149
150
    protected function interact(InputInterface $input, OutputInterface $output)
151
    {
152
        $questionHelper = $this->getQuestionHelper();
153
        $questionHelper->writeSection($output, 'Welcome to the Sonata admin generator');
154
        $modelClass = $this->askAndValidate(
155
            $input,
156
            $output,
157
            'The fully qualified model class',
158
            $input->getArgument('model'),
159
            'Sonata\AdminBundle\Command\Validators::validateClass'
160
        );
161
        $modelClassBasename = current(\array_slice(explode('\\', $modelClass), -1));
162
        $bundleName = $this->askAndValidate(
163
            $input,
164
            $output,
165
            'The bundle name',
166
            $input->getOption('bundle') ?: $this->getBundleNameFromClass($modelClass),
167
            'Sensio\Bundle\GeneratorBundle\Command\Validators::validateBundleName'
168
        );
169
        $adminClassBasename = $this->askAndValidate(
170
            $input,
171
            $output,
172
            'The admin class basename',
173
            $input->getOption('admin') ?: $modelClassBasename.'Admin',
174
            'Sonata\AdminBundle\Command\Validators::validateAdminClassBasename'
175
        );
176
177
        if (\count($this->getAvailableManagerTypes()) > 1) {
178
            $managerType = $this->askAndValidate(
179
                $input,
180
                $output,
181
                'The manager type',
182
                $input->getOption('manager') ?: $this->getDefaultManagerType(),
183
                [$this, 'validateManagerType']
184
            );
185
            $input->setOption('manager', $managerType);
186
        }
187
188
        if ($this->askConfirmation($input, $output, 'Do you want to generate a controller', 'no', '?')) {
189
            $controllerClassBasename = $this->askAndValidate(
190
                $input,
191
                $output,
192
                'The controller class basename',
193
                $input->getOption('controller') ?: $modelClassBasename.'AdminController',
194
                'Sonata\AdminBundle\Command\Validators::validateControllerClassBasename'
195
            );
196
            $input->setOption('controller', $controllerClassBasename);
197
        }
198
199
        if ($this->askConfirmation($input, $output, 'Do you want to update the services YAML configuration file', 'yes', '?')) {
200
            $path = $this->getBundle($bundleName)->getPath().'/Resources/config/';
201
            $servicesFile = $this->askAndValidate(
202
                $input,
203
                $output,
204
                'The services YAML configuration file',
205
                is_file($path.'admin.yml') ? 'admin.yml' : 'services.yml',
206
                'Sonata\AdminBundle\Command\Validators::validateServicesFile'
207
            );
208
            $id = $this->askAndValidate(
209
                $input,
210
                $output,
211
                'The admin service ID',
212
                $this->getAdminServiceId($bundleName, $adminClassBasename),
213
                'Sonata\AdminBundle\Command\Validators::validateServiceId'
214
            );
215
            $input->setOption('services', $servicesFile);
216
            $input->setOption('id', $id);
217
        } else {
218
            $input->setOption('services', false);
219
        }
220
221
        $input->setArgument('model', $modelClass);
222
        $input->setOption('admin', $adminClassBasename);
223
        $input->setOption('bundle', $bundleName);
224
    }
225
226
    /**
227
     * @throws \InvalidArgumentException
228
     */
229
    private function getBundleNameFromClass(string $class): ?string
230
    {
231
        $application = $this->getApplication();
232
        /* @var $application Application */
233
234
        foreach ($application->getKernel()->getBundles() as $bundle) {
235
            if (0 === strpos($class, $bundle->getNamespace().'\\')) {
236
                return $bundle->getName();
237
            }
238
        }
239
240
        return null;
241
    }
242
243
    private function getBundle(string $name): BundleInterface
244
    {
245
        return $this->getKernel()->getBundle($name);
246
    }
247
248
    private function writeError(OutputInterface $output, string $message): void
249
    {
250
        $output->writeln(sprintf("\n<error>%s</error>", $message));
251
    }
252
253
    /**
254
     * @throws \RuntimeException
255
     */
256
    private function getDefaultManagerType(): string
257
    {
258
        if (!$managerTypes = $this->getAvailableManagerTypes()) {
259
            throw new \RuntimeException('There are no model managers registered.');
260
        }
261
262
        return current($managerTypes);
263
    }
264
265
    private function getModelManager(string $managerType): ModelManagerInterface
266
    {
267
        $modelManager = $this->getContainer()->get('sonata.admin.manager.'.$managerType);
268
        \assert($modelManager instanceof ModelManagerInterface);
269
270
        return $modelManager;
271
    }
272
273
    private function getAdminServiceId(string $bundleName, string $adminClassBasename): string
274
    {
275
        $prefix = 'Bundle' === substr($bundleName, -6) ? substr($bundleName, 0, -6) : $bundleName;
276
        $suffix = 'Admin' === substr($adminClassBasename, -5) ? substr($adminClassBasename, 0, -5) : $adminClassBasename;
277
        $suffix = str_replace('\\', '.', $suffix);
278
279
        return Container::underscore(sprintf(
280
            '%s.admin.%s',
281
            $prefix,
282
            $suffix
283
        ));
284
    }
285
286
    /**
287
     * @return string[]
288
     */
289
    private function getAvailableManagerTypes(): array
290
    {
291
        $container = $this->getContainer();
292
293
        if (!$container instanceof Container) {
294
            return [];
295
        }
296
297
        if (null === $this->managerTypes) {
298
            $this->managerTypes = [];
299
300
            foreach ($container->getServiceIds() as $id) {
301
                if (0 === strpos($id, 'sonata.admin.manager.')) {
302
                    $managerType = substr($id, 21);
303
                    $this->managerTypes[$managerType] = $managerType;
304
                }
305
            }
306
        }
307
308
        return $this->managerTypes;
309
    }
310
311
    private function getKernel(): KernelInterface
312
    {
313
        /* @var $application Application */
314
        $application = $this->getApplication();
315
316
        return $application->getKernel();
317
    }
318
}
319