Pārlūkot izejas kodu

Adds a command to generates admin class, controller and services for a given entity
- reimplemented the implementation here https://github.com/sonata-project/SonataAdminBundle/pull/925

Marek Štípek 12 gadi atpakaļ
vecāks
revīzija
fd3d68b0de

+ 335 - 0
Command/GenerateAdminCommand.php

@@ -0,0 +1,335 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Command;
+
+use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Sensio\Bundle\GeneratorBundle\Command\Helper\DialogHelper;
+use Sonata\AdminBundle\Generator\AdminGenerator;
+use Sonata\AdminBundle\Generator\ControllerGenerator;
+use Sonata\AdminBundle\Manipulator\ServicesManipulator;
+use Sonata\AdminBundle\Model\ModelManagerInterface;
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Bundle\FrameworkBundle\Console\Application;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\HttpKernel\Bundle\BundleInterface;
+
+/**
+ * @author Marek Stipek <mario.dweller@seznam.cz>
+ * @author Simon Cosandey <simon.cosandey@simseo.ch>
+ */
+class GenerateAdminCommand extends ContainerAwareCommand
+{
+    /** @var string[] */
+    private $managerTypes;
+
+    /**
+     * {@inheritDoc}
+     */
+    public function configure()
+    {
+        $this
+            ->setName('sonata:admin:generate')
+            ->setDescription('Generates an admin class based on the given entity class')
+            ->addArgument('entity', InputArgument::REQUIRED, 'The entity name')
+            ->addArgument('controller', InputArgument::OPTIONAL, 'The controller class name')
+            ->addOption('bundle', 'b', InputOption::VALUE_OPTIONAL, 'The bundle name')
+            ->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'The manager type')
+            ->addOption('services', 'y', InputOption::VALUE_OPTIONAL, 'The services YAML file', 'services.yml')
+            ->addOption('id', 'i', InputOption::VALUE_OPTIONAL, 'The admin service ID')
+        ;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $skeletonDirectory = __DIR__ . '/../Resources/skeleton';
+        $entity = $input->getArgument('entity');
+        list($bundleName, $entityClassName) = Validators::validateEntityName($entity);
+        $bundle = $this->getBundle($input->getOption('bundle') ?: $bundleName);
+        $modelManager = $this->getModelManager($input->getOption('type') ?: $this->getDefaultManagerType());
+
+        if (!$entityClass = $this->getEntityClass($modelManager, $entity)) {
+            $entityClass = sprintf('%s\Entity\%s', $bundle->getNamespace(), $entityClassName);
+        }
+
+        $adminGenerator = new AdminGenerator($modelManager, $skeletonDirectory);
+
+        try {
+            $adminGenerator->generate($bundle, $entityClassName . 'Admin', $entityClass);
+            $output->writeln(sprintf(
+                '%sThe admin class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
+                "\n",
+                $adminGenerator->getClass(),
+                realpath($adminGenerator->getFile())
+            ));
+        } catch (\Exception $e) {
+            $this->writeError($output, $e->getMessage());
+        }
+
+        if ($controller = $input->getArgument('controller')) {
+            $controllerGenerator = new ControllerGenerator($skeletonDirectory);
+
+            try {
+                $controllerGenerator->generate($bundle, $entityClassName . 'AdminController');
+                $output->writeln(sprintf(
+                    '%sThe controller class "<info>%s</info>" has been generated under the file "<info>%s</info>".',
+                    "\n",
+                    $controllerGenerator->getClass(),
+                    realpath($controllerGenerator->getFile())
+                ));
+            } catch (\Exception $e) {
+                $this->writeError($output, $e->getMessage());
+            }
+        }
+
+        if ($services = $input->getOption('services')) {
+            $adminClass = $adminGenerator->getClass();
+            $file = sprintf('%s/Resources/config/%s', $bundle->getPath(), $services);
+            $servicesManipulator = new ServicesManipulator($file);
+            $controllerName = $controller
+                ? sprintf('%s:%s', $bundle->getName(), substr($controller, 0, -10))
+                : 'SonataAdminBundle:CRUD'
+            ;
+
+            try {
+                $id = $input->getOption('id') ?: $this->getAdminServiceId($bundle->getName(), $entityClassName);
+                $servicesManipulator->addResource($id, $entityClass, $adminClass, $controllerName);
+                $output->writeln(sprintf(
+                    '%sThe service "<info>%s</info>" has been appended to the file <info>"%s</info>".',
+                    "\n",
+                    $id,
+                    realpath($file)
+                ));
+            } catch (\Exception $e) {
+                $this->writeError($output, $e->getMessage());
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function interact(InputInterface $input, OutputInterface $output)
+    {
+        $dialog = $this->getDialogHelper();
+        $dialog->writeSection($output, 'Welcome to the Sonata admin generator');
+        list($bundleName, $entity) = $this->askAndValidate(
+            $output,
+            'The entity name',
+            $input->getArgument('entity'),
+            'Sonata\AdminBundle\Command\Validators::validateEntityName'
+        );
+        $bundleName = $this->askAndValidate(
+            $output,
+            'The bundle name',
+            $input->getOption('bundle') ?: $bundleName,
+            'Sensio\Bundle\GeneratorBundle\Command\Validators::validateBundleName'
+        );
+
+        if (count($this->getManagerTypes()) > 1) {
+            $managerType = $this->askAndValidate(
+                $output,
+                'The manager type',
+                $input->getOption('type') ?: $this->getDefaultManagerType(),
+                array($this, 'validateManagerType')
+            );
+            $input->setOption('type', $managerType);
+        }
+
+        $question = $dialog->getQuestion('Do you want to generate a controller', 'no', '?');
+
+        if ($dialog->askConfirmation($output, $question, false)) {
+            $controller = $this->askAndValidate(
+                $output,
+                'The controller class name',
+                $input->getArgument('controller') ?: $entity . 'AdminController',
+                'Sonata\AdminBundle\Command\Validators::validateControllerClassName'
+            );
+            $input->setArgument('controller', $controller);
+        }
+
+        $question = $dialog->getQuestion('Do you want to update the services YAML configuration file', 'yes', '?');
+
+        if ($dialog->askConfirmation($output, $question)) {
+            $path = $this->getBundle($bundleName)->getPath() . '/Resources/config/';
+            $services = $this->askAndValidate(
+                $output,
+                'The services YAML configuration file',
+                is_file($path . 'admin.yml') ? 'admin.yml' : 'services.yml',
+                'Sonata\AdminBundle\Command\Validators::validateServicesFile'
+            );
+            $id = $this->askAndValidate(
+                $output,
+                'The admin service ID',
+                $this->getAdminServiceId($bundleName, $entity),
+                'Sonata\AdminBundle\Command\Validators::validateServiceId'
+            );
+            $input->setOption('services', $services);
+            $input->setOption('id', $id);
+        }
+
+        $input->setArgument('entity', sprintf('%s:%s', $bundleName, $entity));
+        $input->setOption('bundle', $bundleName);
+    }
+
+    /**
+     * @param string $managerType
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public function validateManagerType($managerType)
+    {
+        $managerTypes = $this->getManagerTypes();
+
+        if (!in_array($managerType, $managerTypes)) {
+            throw new \InvalidArgumentException(sprintf(
+                'Invalid manager type "%s". Valid manager types are "%s".',
+                $managerType,
+                implode('", "', $managerTypes)
+            ));
+        }
+
+        return $managerType;
+    }
+
+    /**
+     * @param OutputInterface $output
+     * @param string $question
+     * @param mixed $default
+     * @param callable $validator
+     * @return mixed
+     */
+    private function askAndValidate(OutputInterface $output, $question, $default, $validator)
+    {
+        $dialog = $this->getDialogHelper();
+
+        return $dialog->askAndValidate($output, $dialog->getQuestion($question, $default), $validator, false, $default);
+    }
+
+    /**
+     * @param OutputInterface $output
+     * @param string $message
+     */
+    private function writeError(OutputInterface $output, $message)
+    {
+        $output->writeln(sprintf("\n<error>%s</error>", $message));
+    }
+
+    /**
+     * @param string $name
+     * @return BundleInterface
+     */
+    private function getBundle($name)
+    {
+        $application = $this->getApplication();
+        /* @var $application Application */
+
+        return $application->getKernel()->getBundle($name);
+    }
+
+    /**
+     * @param ModelManagerInterface $modelManager
+     * @param string $entity
+     * @return string|null
+     */
+    private function getEntityClass(ModelManagerInterface $modelManager, $entity)
+    {
+        if (is_callable(array($modelManager, 'getMetadata'))) {
+            $metadata = $modelManager->getMetadata($entity);
+
+            if ($metadata instanceof ClassMetadata) {
+                return $metadata->name;
+            };
+        }
+
+        return null;
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getManagerTypes()
+    {
+        $container = $this->getContainer();
+
+        if (!$container instanceof Container) {
+            return array();
+        }
+
+        if ($this->managerTypes === null) {
+            $this->managerTypes = array();
+
+            foreach ($container->getServiceIds() as $id) {
+                if (!strncmp($id, 'sonata.admin.manager.', 21)) {
+                    $this->managerTypes[] = substr($id, 21);
+                }
+            }
+        }
+
+        return $this->managerTypes;
+    }
+
+    /**
+     * @return string
+     * @throws \RuntimeException
+     */
+    private function getDefaultManagerType()
+    {
+        if (!$managerTypes = $this->getManagerTypes()) {
+            throw new \RuntimeException('There are no registered model managers.');
+        }
+
+        return $managerTypes[0];
+    }
+
+    /**
+     * @param string $managerType
+     * @return ModelManagerInterface
+     */
+    private function getModelManager($managerType)
+    {
+        return $this->getContainer()->get('sonata.admin.manager.' . $managerType);
+    }
+
+    /**
+     * @param string $bundleName
+     * @param string $entityClassName
+     * @return string
+     */
+    private function getAdminServiceId($bundleName, $entityClassName)
+    {
+        return Container::underscore(sprintf('%s.admin.%s', substr($bundleName, 0, -6), $entityClassName));
+    }
+
+    /**
+     * @return DialogHelper
+     */
+    private function getDialogHelper()
+    {
+        $dialogHelper = $this->getHelper('dialog');
+
+        if (!$dialogHelper instanceof DialogHelper) {
+            $dialogHelper = new DialogHelper();
+            $this->getHelperSet()->set($dialogHelper);
+        }
+
+        return $dialogHelper;
+    }
+}

+ 56 - 0
Command/Validators.php

@@ -48,4 +48,60 @@ class Validators
 
         return array(substr($entity, 0, $pos), substr($entity, $pos + 1));
     }
+
+    /**
+     * @static
+     *
+     * @param string $controllerClassName
+     *
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public static function validateControllerClassName($controllerClassName)
+    {
+        $controllerClassName = str_replace('/', '\\', $controllerClassName);
+
+        if (false !== strpos($controllerClassName, ':')) {
+            throw new \InvalidArgumentException(sprintf('The controller class name must not contain a : ("%s" given, expecting something like PostAdminController")', $controllerClassName));
+        }
+
+        if (substr($controllerClassName, -10) != 'Controller') {
+            throw new \InvalidArgumentException('The controller class name must end with Controller.');
+        }
+
+        return $controllerClassName;
+    }
+
+    /**
+     * @static
+     *
+     * @param string $servicesFile
+     *
+     * @return string
+     */
+    public static function validateServicesFile($servicesFile)
+    {
+        return trim($servicesFile, '/');
+    }
+
+    /**
+     * @static
+     *
+     * @param string $serviceId
+     *
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public static function validateServiceId($serviceId)
+    {
+        if (preg_match('/[^A-z\._0-9]/', $serviceId, $matches)) {
+            throw new \InvalidArgumentException(sprintf(
+                'Service ID "%s" contains invalid character "%s".',
+                $serviceId,
+                $matches[0]
+            ));
+        }
+
+        return $serviceId;
+    }
 }

+ 86 - 0
Generator/AdminGenerator.php

@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace Sonata\AdminBundle\Generator;
+
+use Sensio\Bundle\GeneratorBundle\Generator\Generator;
+use Sonata\AdminBundle\Model\ModelManagerInterface;
+use Symfony\Component\HttpKernel\Bundle\BundleInterface;
+
+/**
+ * @author Marek Stipek <mario.dweller@seznam.cz>
+ * @author Simon Cosandey <simon.cosandey@simseo.ch>
+ */
+class AdminGenerator extends Generator
+{
+    /** @var ModelManagerInterface */
+    private $modelManager;
+
+    /** @var string|null */
+    private $class;
+
+    /** @var string|null */
+    private $file;
+
+    /**
+     * @param ModelManagerInterface $modelManager
+     * @param string $skeletonDirectory
+     */
+    public function __construct(ModelManagerInterface $modelManager, $skeletonDirectory)
+    {
+        $this->modelManager = $modelManager;
+        $this->setSkeletonDirs($skeletonDirectory);
+    }
+
+    /**
+     * @param BundleInterface $bundle
+     * @param string $adminClassName
+     * @param string $entityClass
+     * @throws \RuntimeException
+     */
+    public function generate(BundleInterface $bundle, $adminClassName, $entityClass)
+    {
+        $this->class = sprintf('%s\Admin\%s', $bundle->getNamespace(), $adminClassName);
+        $this->file = sprintf('%s/Admin/%s.php', $bundle->getPath(), str_replace('\\', '/', $adminClassName));
+        $parts = explode('\\', $this->class);
+
+        if (file_exists($this->file)) {
+            throw new \RuntimeException(sprintf(
+                'Unable to generate the admin class "%s". The file "%s" already exists.',
+                $this->class,
+                realpath($this->file)
+            ));
+        }
+
+        $this->renderFile('Admin.php.twig', $this->file, [
+            'className' => array_pop($parts),
+            'namespace' => implode('\\', $parts),
+            'fields' => $this->modelManager->getExportFields($entityClass)
+        ]);
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getClass()
+    {
+        return $this->class;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getFile()
+    {
+        return $this->file;
+    }
+}

+ 78 - 0
Generator/ControllerGenerator.php

@@ -0,0 +1,78 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace Sonata\AdminBundle\Generator;
+
+use Sensio\Bundle\GeneratorBundle\Generator\Generator;
+use Symfony\Component\HttpKernel\Bundle\BundleInterface;
+
+/**
+ * @author Marek Stipek <mario.dweller@seznam.cz>
+ * @author Simon Cosandey <simon.cosandey@simseo.ch>
+ */
+class ControllerGenerator extends Generator
+{
+    /** @var string|null */
+    private $class;
+
+    /** @var string|null */
+    private $file;
+
+    /**
+     * @param string $skeletonDirectory
+     */
+    public function __construct($skeletonDirectory)
+    {
+        $this->setSkeletonDirs($skeletonDirectory);
+    }
+
+    /**
+     * @param BundleInterface $bundle
+     * @param string $controllerClassName
+     * @throws \RuntimeException
+     */
+    public function generate(BundleInterface $bundle, $controllerClassName)
+    {
+        $this->class = sprintf('%s\Controller\%s', $bundle->getNamespace(), $controllerClassName);
+        $this->file = sprintf('%s/Controller/%s.php', $bundle->getPath(), str_replace('\\', '/', $controllerClassName));
+        $parts = explode('\\', $this->class);
+
+        if (file_exists($this->file)) {
+            throw new \RuntimeException(sprintf(
+                'Unable to generate the admin controller class "%s". The file "%s" already exists.',
+                $this->class,
+                realpath($this->file)
+            ));
+        }
+
+        $this->renderFile('AdminController.php.twig', $this->file, [
+            'className' => array_pop($parts),
+            'namespace' => implode('\\', $parts)
+        ]);
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getClass()
+    {
+        return $this->class;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getFile()
+    {
+        return $this->file;
+    }
+}

+ 98 - 0
Manipulator/ServicesManipulator.php

@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace Sonata\AdminBundle\Manipulator;
+
+use Symfony\Component\HttpKernel\Bundle\BundleInterface;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * @author Marek Stipek <mario.dweller@seznam.cz>
+ * @author Simon Cosandey <simon.cosandey@simseo.ch>
+ */
+class ServicesManipulator
+{
+    /** @var string */
+    private $file;
+
+    /** @var string */
+    private $template = '   %s:
+        class: %s
+        arguments: [~, %s, %s]
+        tags:
+            - {name: sonata.admin, manager_type: orm, group: admin, label: %s}
+';
+
+    /**
+     * @param string $file
+     */
+    public function __construct($file)
+    {
+        $this->file = (string) $file;
+    }
+
+    /**
+     * @param string $serviceId
+     * @param string $entityClass
+     * @param string $adminClass
+     * @param string $controllerName
+     * @throws \RuntimeException
+     */
+    public function addResource($serviceId, $entityClass, $adminClass, $controllerName)
+    {
+        $code = "services:\n";
+
+        if (is_file($this->file)) {
+            $code = rtrim(file_get_contents($this->file));
+            $data = (array) Yaml::parse($code);
+
+            if ($code !== '') {
+                $code .= "\n";
+            }
+
+            if (array_key_exists('services', $data)) {
+                if (array_key_exists($serviceId, (array) $data['services'])) {
+                    throw new \RuntimeException(sprintf(
+                        'The service "%s" is already defined in the file "%s".',
+                        $serviceId,
+                        realpath($this->file)
+                    ));
+                }
+
+                if ($data['services'] !== null) {
+                    $code .= "\n";
+                }
+            } else {
+                $code .= $code === '' ? '' : "\n" . "services:\n";
+            }
+        }
+
+        $code .= sprintf(
+            $this->template,
+            $serviceId,
+            $adminClass,
+            $entityClass,
+            $controllerName,
+            current(array_slice(explode('\\', $entityClass), -1))
+        );
+        @mkdir(dirname($this->file), 0777, true);
+
+        if (@file_put_contents($this->file, $code) === false) {
+            throw new \RuntimeException(sprintf(
+                'Unable to append service "%s" to the file "%s". You will have to do it manually.',
+                $serviceId,
+                $this->file
+            ));
+        };
+    }
+}

+ 65 - 0
Resources/skeleton/Admin.php.twig

@@ -0,0 +1,65 @@
+<?php
+{%- set code %}
+    {%- for field in fields %}
+
+            ->add('{{ field }}')
+    {%- endfor %}
+{% endset %}
+
+namespace {{ namespace }};
+
+use Sonata\AdminBundle\Admin\Admin;
+use Sonata\AdminBundle\Datagrid\DatagridMapper;
+use Sonata\AdminBundle\Datagrid\ListMapper;
+use Sonata\AdminBundle\Form\FormMapper;
+use Sonata\AdminBundle\Show\ShowMapper;
+
+class {{ className }}
+{
+    /**
+     * @param DatagridMapper $datagridMapper
+     */
+    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
+    {
+        $datagridMapper
+            {{- code }}
+        ;
+    }
+
+    /**
+     * @param ListMapper $listMapper
+     */
+    protected function configureListFields(ListMapper $listMapper)
+    {
+        $listMapper
+            {{- code }}
+            ->add('_action', 'actions', array(
+                'actions' => array(
+                    'view' => array(),
+                    'edit' => array(),
+                    'delete' => array(),
+                )
+            ))
+        ;
+    }
+
+    /**
+     * @param FormMapper $formMapper
+     */
+    protected function configureFormFields(FormMapper $formMapper)
+    {
+        $formMapper
+            {{- code }}
+        ;
+    }
+
+    /**
+     * @param ShowMapper $showMapper
+     */
+    protected function configureShowFields(ShowMapper $showMapper)
+    {
+        $showMapper
+            {{- code }}
+        ;
+    }
+}

+ 10 - 0
Resources/skeleton/AdminController.php.twig

@@ -0,0 +1,10 @@
+<?php
+
+namespace {{ namespace }};
+
+use Sonata\AdminBundle\Controller\CRUDController;
+
+class {{ className }} extends CRUDController
+{
+
+}