Completed
Push — 3.x ( e95e95...638cd1 )
by Oskar
05:54
created

src/Command/GenerateAdminCommand.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Admin\Pool;
18
use Sonata\AdminBundle\Controller\CRUDController;
19
use Sonata\AdminBundle\Generator\AdminGenerator;
20
use Sonata\AdminBundle\Generator\ControllerGenerator;
21
use Sonata\AdminBundle\Manipulator\ServicesManipulator;
22
use Sonata\AdminBundle\Model\ModelManagerInterface;
23
use Symfony\Bundle\FrameworkBundle\Console\Application;
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\Console\Output\OutputInterface;
28
use Symfony\Component\DependencyInjection\Container;
29
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
30
use Symfony\Component\HttpKernel\KernelInterface;
31
32
/**
33
 * @author Marek Stipek <[email protected]>
34
 * @author Simon Cosandey <[email protected]>
35
 */
36
class GenerateAdminCommand extends QuestionableCommand
37
{
38
    /**
39
     * {@inheritdoc}
40
     */
41
    protected static $defaultName = 'sonata:admin:generate';
42
43
    /**
44
     * @var Pool
45
     */
46
    private $pool;
47
48
    /**
49
     * An array of model managers indexed by their service ids.
50
     *
51
     * @var ModelManagerInterface[]
52
     */
53
    private $managerTypes = [];
54
55
    public function __construct(Pool $pool, array $managerTypes)
56
    {
57
        $this->pool = $pool;
58
        $this->managerTypes = $managerTypes;
59
60
        parent::__construct();
61
    }
62
63
    public function configure()
64
    {
65
        $this
66
            ->setDescription('Generates an admin class based on the given model class')
67
            ->addArgument('model', InputArgument::REQUIRED, 'The fully qualified model class')
68
            ->addOption('bundle', 'b', InputOption::VALUE_OPTIONAL, 'The bundle name')
69
            ->addOption('admin', 'a', InputOption::VALUE_OPTIONAL, 'The admin class basename')
70
            ->addOption('controller', 'c', InputOption::VALUE_OPTIONAL, 'The controller class basename')
71
            ->addOption('manager', 'm', InputOption::VALUE_OPTIONAL, 'The model manager type')
72
            ->addOption('services', 'y', InputOption::VALUE_OPTIONAL, 'The services YAML file', 'services.yml')
73
            ->addOption('id', 'i', InputOption::VALUE_OPTIONAL, 'The admin service ID')
74
        ;
75
    }
76
77
    public function isEnabled()
78
    {
79
        return class_exists(SensioGeneratorBundle::class);
80
    }
81
82
    /**
83
     * @param string $managerType
84
     *
85
     * @throws \InvalidArgumentException
86
     *
87
     * @return string
88
     */
89
    public function validateManagerType($managerType)
90
    {
91
        $managerTypes = $this->getAvailableManagerTypes();
92
93
        if (!isset($managerTypes[$managerType])) {
94
            throw new \InvalidArgumentException(sprintf(
95
                'Invalid manager type "%s". Available manager types are "%s".',
96
                $managerType,
97
                implode('", "', array_keys($managerTypes))
98
            ));
99
        }
100
101
        return $managerType;
102
    }
103
104
    protected function execute(InputInterface $input, OutputInterface $output)
105
    {
106
        $modelClass = Validators::validateClass($input->getArgument('model'));
0 ignored issues
show
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...
107
        $modelClassBasename = current(\array_slice(explode('\\', $modelClass), -1));
108
        $bundle = $this->getBundle($input->getOption('bundle') ?: $this->getBundleNameFromClass($modelClass));
109
        $adminClassBasename = $input->getOption('admin') ?: $modelClassBasename.'Admin';
110
        $adminClassBasename = Validators::validateAdminClassBasename($adminClassBasename);
111
        $managerType = $input->getOption('manager') ?: $this->getDefaultManagerType();
112
        $modelManager = $this->getModelManager($managerType);
113
        $skeletonDirectory = __DIR__.'/../Resources/skeleton';
114
        $adminGenerator = new AdminGenerator($modelManager, $skeletonDirectory);
115
116
        try {
117
            $adminGenerator->generate($bundle, $adminClassBasename, $modelClass);
118
            $output->writeln(sprintf(
119
                '%sThe admin class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
120
                PHP_EOL,
121
                $adminGenerator->getClass(),
122
                realpath($adminGenerator->getFile())
123
            ));
124
        } catch (\Exception $e) {
125
            $this->writeError($output, $e->getMessage());
126
        }
127
128
        $controllerName = CRUDController::class;
129
130
        if ($controllerClassBasename = $input->getOption('controller')) {
131
            $controllerClassBasename = Validators::validateControllerClassBasename($controllerClassBasename);
132
            $controllerGenerator = new ControllerGenerator($skeletonDirectory);
133
134
            try {
135
                $controllerGenerator->generate($bundle, $controllerClassBasename);
136
                $controllerName = $controllerGenerator->getClass();
137
                $output->writeln(sprintf(
138
                    '%sThe controller class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
139
                    PHP_EOL,
140
                    $controllerName,
141
                    realpath($controllerGenerator->getFile())
142
                ));
143
            } catch (\Exception $e) {
144
                $this->writeError($output, $e->getMessage());
145
            }
146
        }
147
148
        if ($servicesFile = $input->getOption('services')) {
149
            $adminClass = $adminGenerator->getClass();
150
            $file = sprintf('%s/Resources/config/%s', $bundle->getPath(), $servicesFile);
151
            $servicesManipulator = new ServicesManipulator($file);
152
153
            try {
154
                $id = $input->getOption('id') ?: $this->getAdminServiceId($bundle->getName(), $adminClassBasename);
155
                $servicesManipulator->addResource($id, $modelClass, $adminClass, $controllerName, $managerType);
156
                $output->writeln(sprintf(
157
                    '%sThe service "<info>%s</info>" has been appended to the file <info>"%s</info>".',
158
                    PHP_EOL,
159
                    $id,
160
                    realpath($file)
161
                ));
162
            } catch (\Exception $e) {
163
                $this->writeError($output, $e->getMessage());
164
            }
165
        }
166
167
        return 0;
168
    }
169
170
    protected function interact(InputInterface $input, OutputInterface $output)
171
    {
172
        $questionHelper = $this->getQuestionHelper();
173
        $questionHelper->writeSection($output, 'Welcome to the Sonata admin generator');
174
        $modelClass = $this->askAndValidate(
175
            $input,
176
            $output,
177
            'The fully qualified model class',
178
            $input->getArgument('model'),
179
            'Sonata\AdminBundle\Command\Validators::validateClass'
180
        );
181
        $modelClassBasename = current(\array_slice(explode('\\', $modelClass), -1));
182
        $bundleName = $this->askAndValidate(
183
            $input,
184
            $output,
185
            'The bundle name',
186
            $input->getOption('bundle') ?: $this->getBundleNameFromClass($modelClass),
187
            'Sensio\Bundle\GeneratorBundle\Command\Validators::validateBundleName'
188
        );
189
        $adminClassBasename = $this->askAndValidate(
190
            $input,
191
            $output,
192
            'The admin class basename',
193
            $input->getOption('admin') ?: $modelClassBasename.'Admin',
194
            'Sonata\AdminBundle\Command\Validators::validateAdminClassBasename'
195
        );
196
197
        if (\count($this->getAvailableManagerTypes()) > 1) {
198
            $managerType = $this->askAndValidate(
199
                $input,
200
                $output,
201
                'The manager type',
202
                $input->getOption('manager') ?: $this->getDefaultManagerType(),
203
                [$this, 'validateManagerType']
204
            );
205
            $input->setOption('manager', $managerType);
206
        }
207
208
        if ($this->askConfirmation($input, $output, 'Do you want to generate a controller', 'no', '?')) {
209
            $controllerClassBasename = $this->askAndValidate(
210
                $input,
211
                $output,
212
                'The controller class basename',
213
                $input->getOption('controller') ?: $modelClassBasename.'AdminController',
214
                'Sonata\AdminBundle\Command\Validators::validateControllerClassBasename'
215
            );
216
            $input->setOption('controller', $controllerClassBasename);
217
        }
218
219
        if ($this->askConfirmation($input, $output, 'Do you want to update the services YAML configuration file', 'yes', '?')) {
220
            $path = $this->getBundle($bundleName)->getPath().'/Resources/config/';
221
            $servicesFile = $this->askAndValidate(
222
                $input,
223
                $output,
224
                'The services YAML configuration file',
225
                is_file($path.'admin.yml') ? 'admin.yml' : 'services.yml',
226
                'Sonata\AdminBundle\Command\Validators::validateServicesFile'
227
            );
228
            $id = $this->askAndValidate(
229
                $input,
230
                $output,
231
                'The admin service ID',
232
                $this->getAdminServiceId($bundleName, $adminClassBasename),
233
                'Sonata\AdminBundle\Command\Validators::validateServiceId'
234
            );
235
            $input->setOption('services', $servicesFile);
236
            $input->setOption('id', $id);
237
        } else {
238
            $input->setOption('services', false);
239
        }
240
241
        $input->setArgument('model', $modelClass);
242
        $input->setOption('admin', $adminClassBasename);
243
        $input->setOption('bundle', $bundleName);
244
    }
245
246
    /**
247
     * @throws \InvalidArgumentException
248
     */
249
    private function getBundleNameFromClass(string $class): ?string
250
    {
251
        $application = $this->getApplication();
252
        /* @var $application Application */
253
254
        foreach ($application->getKernel()->getBundles() as $bundle) {
255
            if (0 === strpos($class, $bundle->getNamespace().'\\')) {
256
                return $bundle->getName();
257
            }
258
        }
259
260
        return null;
261
    }
262
263
    private function getBundle(string $name): BundleInterface
264
    {
265
        return $this->getKernel()->getBundle($name);
266
    }
267
268
    private function writeError(OutputInterface $output, string $message): void
269
    {
270
        $output->writeln(sprintf("\n<error>%s</error>", $message));
271
    }
272
273
    /**
274
     * @throws \RuntimeException
275
     */
276
    private function getDefaultManagerType(): string
277
    {
278
        if (!$managerTypes = $this->getAvailableManagerTypes()) {
279
            throw new \RuntimeException('There are no model managers registered.');
280
        }
281
282
        return current(array_keys($managerTypes));
283
    }
284
285
    private function getModelManager(string $managerType): ModelManagerInterface
286
    {
287
        $modelManager = $this->getAvailableManagerTypes()[$managerType];
288
        \assert($modelManager instanceof ModelManagerInterface);
289
290
        return $modelManager;
291
    }
292
293
    private function getAdminServiceId(string $bundleName, string $adminClassBasename): string
294
    {
295
        $prefix = 'Bundle' === substr($bundleName, -6) ? substr($bundleName, 0, -6) : $bundleName;
296
        $suffix = 'Admin' === substr($adminClassBasename, -5) ? substr($adminClassBasename, 0, -5) : $adminClassBasename;
297
        $suffix = str_replace('\\', '.', $suffix);
298
299
        return Container::underscore(sprintf(
300
            '%s.admin.%s',
301
            $prefix,
302
            $suffix
303
        ));
304
    }
305
306
    /**
307
     * @return string[]
308
     */
309
    private function getAvailableManagerTypes(): array
310
    {
311
        $managerTypes = [];
312
        foreach ($this->managerTypes as $id => $manager) {
313
            $managerType = substr($id, 21);
314
            $managerTypes[$managerType] = $manager;
315
        }
316
317
        return $managerTypes;
318
    }
319
320
    private function getKernel(): KernelInterface
321
    {
322
        /* @var $application Application */
323
        $application = $this->getApplication();
324
325
        return $application->getKernel();
326
    }
327
}
328