diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php index c1b726048..a528c3098 100644 --- a/src/Wallabag/CoreBundle/Command/InstallCommand.php +++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php @@ -4,134 +4,194 @@ namespace Wallabag\CoreBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\NullOutput; use Wallabag\CoreBundle\Entity\User; use Wallabag\CoreBundle\Entity\Config; class InstallCommand extends ContainerAwareCommand { + /** + * @var InputInterface + */ + protected $defaultInput; + + /** + * @var OutputInterface + */ + protected $defaultOutput; + protected function configure() { $this ->setName('wallabag:install') ->setDescription('Wallabag installer.') + ->addOption( + 'reset', + null, + InputOption::VALUE_NONE, + 'Reset current database' + ) ; } protected function execute(InputInterface $input, OutputInterface $output) { - $output->writeln('Installing Wallabag.'); + $this->defaultInput = $input; + $this->defaultOutput = $output; + + $output->writeln('Installing Wallabag...'); $output->writeln(''); $this - ->checkStep($output) - ->setupStep($input, $output) + ->checkRequirements() + ->setupDatabase() + ->setupAdmin() + ->setupAsset() ; $output->writeln('Wallabag has been successfully installed.'); $output->writeln('Just execute `php app/console server:run` for using wallabag: http://localhost:8000'); } - protected function checkStep(OutputInterface $output) + protected function checkRequirements() { - $output->writeln('Checking system requirements.'); + $this->defaultOutput->writeln('Step 1 of 4. Checking system requirements.'); $fulfilled = true; // @TODO: find a better way to check requirements - $output->writeln('Check PCRE'); + $label = 'PCRE'; if (extension_loaded('pcre')) { - $output->writeln(' OK'); + $status = 'OK!'; + $help = ''; } else { $fulfilled = false; - $output->writeln(' ERROR'); - $output->writeln('You should enabled PCRE extension'); + $status = 'ERROR!'; + $help = 'You should enabled PCRE extension'; } + $rows[] = array($label, $status, $help); - $output->writeln('Check DOM'); + $label = 'DOM'; if (extension_loaded('DOM')) { - $output->writeln(' OK'); + $status = 'OK!'; + $help = ''; } else { $fulfilled = false; - $output->writeln(' ERROR'); - $output->writeln('You should enabled DOM extension'); + $status = 'ERROR!'; + $help = 'You should enabled DOM extension'; } + $rows[] = array($label, $status, $help); + + $this->getHelper('table') + ->setHeaders(array('Checked', 'Status', 'Recommendation')) + ->setRows($rows) + ->render($this->defaultOutput); if (!$fulfilled) { - throw new RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.'); - } - - $output->writeln(''); - - return $this; - } - - protected function setupStep(InputInterface $input, OutputInterface $output) - { - $output->writeln('Setting up database.'); - - $this->setupDatabase($input, $output); - - // if ($this->getHelperSet()->get('dialog')->askConfirmation($output, 'Load fixtures (Y/N)?', false)) { - // $this->setupFixtures($input, $output); - // } - - $output->writeln(''); - $output->writeln('Administration setup.'); - - $this->setupAdmin($output); - - $output->writeln(''); - - return $this; - } - - protected function setupDatabase(InputInterface $input, OutputInterface $output) - { - if ($this->getHelperSet()->get('dialog')->askConfirmation($output, 'Drop current database (Y/N)?', true)) { - $connection = $this->getContainer()->get('doctrine')->getConnection(); - $params = $connection->getParams(); - - $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false); - unset($params['dbname']); - - if (!isset($params['path'])) { - $name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name); - } - - $connection->getSchemaManager()->dropDatabase($name); + throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.'); } else { - throw new \Exception("Install setup stopped, database need to be dropped. Please backup your current one and re-launch the install command."); + $this->defaultOutput->writeln('Success! Your system can run Wallabag properly.'); } - $this - ->runCommand('doctrine:database:create', $input, $output) - ->runCommand('doctrine:schema:create', $input, $output) - ->runCommand('cache:clear', $input, $output) - ->runCommand('assets:install', $input, $output) - ->runCommand('assetic:dump', $input, $output) - ; + $this->defaultOutput->writeln(''); + + return $this; } - protected function setupFixtures(InputInterface $input, OutputInterface $output) + protected function setupDatabase() { - $doctrineConfig = $this->getContainer()->get('doctrine.orm.entity_manager')->getConnection()->getConfiguration(); - $logger = $doctrineConfig->getSQLLogger(); - // speed up fixture load - $doctrineConfig->setSQLLogger(null); - $this->runCommand('doctrine:fixtures:load', $input, $output); - $doctrineConfig->setSQLLogger($logger); + $this->defaultOutput->writeln('Step 2 of 4. Setting up database.'); + + // user want to reset everything? Don't care about what is already here + if (true === $this->defaultInput->getOption('reset')) { + $this->defaultOutput->writeln('Droping database, creating database and schema'); + + $this + ->runCommand('doctrine:database:drop', array('--force' => true)) + ->runCommand('doctrine:database:create') + ->runCommand('doctrine:schema:create') + ; + + return $this; + } + + if (!$this->isDatabasePresent()) { + $this->defaultOutput->writeln('Creating database and schema, clearing the cache'); + + $this + ->runCommand('doctrine:database:create') + ->runCommand('doctrine:schema:create') + ->runCommand('cache:clear') + ; + + return $this; + } + + $dialog = $this->getHelper('dialog'); + + if ($dialog->askConfirmation($this->defaultOutput, 'It appears that your database already exists. Would you like to reset it? (y/N) ', false)) { + $this->defaultOutput->writeln('Droping database, creating database and schema'); + + $this + ->runCommand('doctrine:database:drop', array('--force' => true)) + ->runCommand('doctrine:database:create') + ->runCommand('doctrine:schema:create') + ; + } elseif ($this->isSchemaPresent()) { + if ($dialog->askConfirmation($this->defaultOutput, 'Seems like your database contains schema. Do you want to reset it? (y/N) ', false)) { + $this->defaultOutput->writeln('Droping schema and creating schema'); + + $this + ->runCommand('doctrine:schema:drop', array('--force' => true)) + ->runCommand('doctrine:schema:create') + ; + } + } else { + $this->defaultOutput->writeln('Creating schema'); + + $this + ->runCommand('doctrine:schema:create') + ; + } + + $this->defaultOutput->writeln('Clearing the cache'); + $this->runCommand('cache:clear'); + + /* + if ($this->getHelperSet()->get('dialog')->askConfirmation($this->defaultOutput, 'Load fixtures (Y/N)?', false)) { + $doctrineConfig = $this->getContainer()->get('doctrine.orm.entity_manager')->getConnection()->getConfiguration(); + $logger = $doctrineConfig->getSQLLogger(); + // speed up fixture load + $doctrineConfig->setSQLLogger(null); + $this->runCommand('doctrine:fixtures:load'); + $doctrineConfig->setSQLLogger($logger); + } + */ + + $this->defaultOutput->writeln(''); + + return $this; } - protected function setupAdmin(OutputInterface $output) + protected function setupAdmin() { + $this->defaultOutput->writeln('Step 3 of 4. Administration setup.'); + $dialog = $this->getHelperSet()->get('dialog'); + + if (false === $dialog->askConfirmation($this->defaultOutput, 'Would you like to create a new user ? (y/N)', true)) { + return $this; + } + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); $user = new User(); - $user->setUsername($dialog->ask($output, 'Username (default: wallabag) :', 'wallabag')); - $user->setPassword($dialog->ask($output, 'Password (default: wallabag) :', 'wallabag')); - $user->setEmail($dialog->ask($output, 'Email:', '')); + $user->setUsername($dialog->ask($this->defaultOutput, 'Username (default: wallabag) :', 'wallabag')); + $user->setPassword($dialog->ask($this->defaultOutput, 'Password (default: wallabag) :', 'wallabag')); + $user->setEmail($dialog->ask($this->defaultOutput, 'Email:', '')); $em->persist($user); @@ -141,16 +201,100 @@ class InstallCommand extends ContainerAwareCommand $config->setLanguage($this->getContainer()->getParameter('language')); $em->persist($config); - } - protected function runCommand($command, InputInterface $input, OutputInterface $output) - { - $this - ->getApplication() - ->find($command) - ->run($input, $output) - ; + $em->flush(); + + $this->defaultOutput->writeln(''); return $this; } + + protected function setupAsset() + { + $this->defaultOutput->writeln('Step 4 of 4. Installing assets.'); + + $this + ->runCommand('assets:install') + ->runCommand('assetic:dump') + ; + + $this->defaultOutput->writeln(''); + + return $this; + } + + /** + * Run a command + * + * @param string $command + * @param array $parameters Parameters to this command (usually 'force' => true) + */ + protected function runCommand($command, $parameters = array()) + { + $parameters = array_merge( + array('command' => $command), + $parameters, + array( + '--no-debug' => true, + '--env' => $this->defaultInput->getOption('env') ?: 'dev', + ) + ); + + if ($this->defaultInput->getOption('no-interaction')) { + $parameters = array_merge($parameters, array('--no-interaction' => true)); + } + + $this->getApplication()->setAutoExit(false); + $exitCode = $this->getApplication()->run(new ArrayInput($parameters), new NullOutput()); + + if (0 !== $exitCode) { + $this->getApplication()->setAutoExit(true); + + $errorMessage = sprintf('The command "%s" terminated with an error code: %u.', $command, $exitCode); + $this->defaultOutput->writeln("$errorMessage"); + $exception = new \Exception($errorMessage, $exitCode); + + throw $exception; + } + + // PDO does not always close the connection after Doctrine commands. + // See https://github.com/symfony/symfony/issues/11750. + $this->getContainer()->get('doctrine')->getManager()->getConnection()->close(); + + return $this; + } + + /** + * Check if the database already exists + * + * @return boolean + */ + private function isDatabasePresent() + { + $databaseName = $this->getContainer()->getParameter('database_name'); + + try { + $schemaManager = $this->getContainer()->get('doctrine')->getManager()->getConnection()->getSchemaManager(); + } catch (\Exception $exception) { + if (false !== strpos($exception->getMessage(), sprintf("Unknown database '%s'", $databaseName))) { + return false; + } + + throw $exception; + } + + return in_array($databaseName, $schemaManager->listDatabases()); + } + + /** + * Check if the schema is already created + * + * @return boolean + */ + private function isSchemaPresent() + { + $schemaManager = $this->getContainer()->get('doctrine')->getManager()->getConnection()->getSchemaManager(); + + return $schemaManager->tablesExist(array('entry')); + } } diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php new file mode 100644 index 000000000..900e151d8 --- /dev/null +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php @@ -0,0 +1,45 @@ +getReference('admin-user')); + $adminConfig->setTheme('baggy'); + $adminConfig->setItemsPerPage(30); + $adminConfig->setLanguage('en_US'); + + $manager->persist($adminConfig); + + $this->addReference('admin-config', $adminConfig); + + $bobConfig = new Config($this->getReference('bob-user')); + $bobConfig->setTheme('default'); + $bobConfig->setItemsPerPage(10); + $bobConfig->setLanguage('fr_FR'); + + $manager->persist($bobConfig); + + $this->addReference('bob-config', $bobConfig); + + $manager->flush(); + } + + /** + * {@inheritDoc} + */ + public function getOrder() + { + return 20; + } +} diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php index 520b44b80..3be323ed7 100644 --- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php +++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php @@ -49,6 +49,6 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface */ public function getOrder() { - return 20; + return 30; } } diff --git a/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php b/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php new file mode 100644 index 000000000..6bcc9707d --- /dev/null +++ b/src/Wallabag/CoreBundle/Tests/Command/InstallCommandTest.php @@ -0,0 +1,61 @@ +setAutoExit(false); + + $code = $application->run(new ArrayInput(array( + 'command' => 'doctrine:fixtures:load', + '--no-interaction' => true, + '--env' => 'test', + )), new NullOutput()); + } + + public function testRunInstallCommand() + { + $this->container = static::$kernel->getContainer(); + + $application = new Application(static::$kernel); + $application->add(new InstallCommand()); + + $command = $application->find('wallabag:install'); + + // We mock the DialogHelper + $dialog = $this->getMockBuilder('Symfony\Component\Console\Helper\DialogHelper') + ->disableOriginalConstructor() + ->getMock(); + $dialog->expects($this->any()) + ->method('ask') + ->will($this->returnValue('test')); + $dialog->expects($this->any()) + ->method('askConfirmation') + ->will($this->returnValue(true)); + + // We override the standard helper with our mock + $command->getHelperSet()->set($dialog, 'dialog'); + + $tester = new CommandTester($command); + $tester->execute(array( + 'command' => $command->getName() + )); + + $this->assertContains('Step 1 of 4. Checking system requirements.', $tester->getDisplay()); + $this->assertContains('Step 2 of 4. Setting up database.', $tester->getDisplay()); + $this->assertContains('Step 3 of 4. Administration setup.', $tester->getDisplay()); + $this->assertContains('Step 4 of 4. Installing assets.', $tester->getDisplay()); + } +}