d020304018ab36c9ce64a1d68c322b578534ac17
[yaffs-website] / web / core / tests / Drupal / TestSite / Commands / TestSiteInstallCommand.php
1 <?php
2
3 namespace Drupal\TestSite\Commands;
4
5 use Drupal\Core\Database\Database;
6 use Drupal\Core\Test\FunctionalTestSetupTrait;
7 use Drupal\Core\Test\TestDatabase;
8 use Drupal\Core\Test\TestSetupTrait;
9 use Drupal\TestSite\TestSetupInterface;
10 use Drupal\Tests\RandomGeneratorTrait;
11 use Symfony\Component\Console\Command\Command;
12 use Symfony\Component\Console\Input\InputInterface;
13 use Symfony\Component\Console\Input\InputOption;
14 use Symfony\Component\Console\Output\OutputInterface;
15 use Symfony\Component\Console\Style\SymfonyStyle;
16
17 /**
18  * Command to create a test Drupal site.
19  *
20  * @internal
21  */
22 class TestSiteInstallCommand extends Command {
23
24   use FunctionalTestSetupTrait {
25     installParameters as protected installParametersTrait;
26   }
27   use RandomGeneratorTrait;
28   use TestSetupTrait {
29     changeDatabasePrefix as protected changeDatabasePrefixTrait;
30   }
31
32   /**
33    * The install profile to use.
34    *
35    * @var string
36    */
37   protected $profile = 'testing';
38
39   /**
40    * Time limit in seconds for the test.
41    *
42    * Used by \Drupal\Core\Test\FunctionalTestSetupTrait::prepareEnvironment().
43    *
44    * @var int
45    */
46   protected $timeLimit = 500;
47
48   /**
49    * The database prefix of this test run.
50    *
51    * @var string
52    */
53   protected $databasePrefix;
54
55   /**
56    * The language to install the site in.
57    *
58    * @var string
59    */
60   protected $langcode = 'en';
61
62   /**
63    * {@inheritdoc}
64    */
65   protected function configure() {
66     $this->setName('install')
67       ->setDescription('Creates a test Drupal site')
68       ->setHelp('The details to connect to the test site created will be displayed upon success. It will contain the database prefix and the user agent.')
69       ->addOption('setup-file', NULL, InputOption::VALUE_OPTIONAL, 'The path to a PHP file containing a class to setup configuration used by the test, for example, core/tests/Drupal/TestSite/TestSiteInstallTestScript.php.')
70       ->addOption('db-url', NULL, InputOption::VALUE_OPTIONAL, 'URL for database. Defaults to the environment variable SIMPLETEST_DB.', getenv('SIMPLETEST_DB'))
71       ->addOption('base-url', NULL, InputOption::VALUE_OPTIONAL, 'Base URL for site under test. Defaults to the environment variable SIMPLETEST_BASE_URL.', getenv('SIMPLETEST_BASE_URL'))
72       ->addOption('install-profile', NULL, InputOption::VALUE_OPTIONAL, 'Install profile to install the site in. Defaults to testing.', 'testing')
73       ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en')
74       ->addOption('json', NULL, InputOption::VALUE_NONE, 'Output test site connection details in JSON.')
75       ->addUsage('--setup-file core/tests/Drupal/TestSite/TestSiteInstallTestScript.php --json')
76       ->addUsage('--install-profile demo_umami --langcode fr')
77       ->addUsage('--base-url "http://example.com" --db-url "mysql://username:password@localhost/databasename#table_prefix"');
78   }
79
80   /**
81    * {@inheritdoc}
82    */
83   protected function execute(InputInterface $input, OutputInterface $output) {
84     // Determines and validates the setup class prior to installing a database
85     // to avoid creating unnecessary sites.
86     $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
87     chdir($root);
88     $class_name = $this->getSetupClass($input->getOption('setup-file'));
89     // Ensure we can install a site in the sites/simpletest directory.
90     $this->ensureDirectory($root);
91
92     $db_url = $input->getOption('db-url');
93     $base_url = $input->getOption('base-url');
94     putenv("SIMPLETEST_DB=$db_url");
95     putenv("SIMPLETEST_BASE_URL=$base_url");
96
97     // Manage site fixture.
98     $this->setup($input->getOption('install-profile'), $class_name, $input->getOption('langcode'));
99
100     $user_agent = drupal_generate_test_ua($this->databasePrefix);
101     if ($input->getOption('json')) {
102       $output->writeln(json_encode([
103         'db_prefix' => $this->databasePrefix,
104         'user_agent' => $user_agent,
105         'site_path' => $this->siteDirectory,
106       ]));
107     }
108     else {
109       $output->writeln('<info>Successfully installed a test site</info>');
110       $io = new SymfonyStyle($input, $output);
111       $io->table([], [
112         ['Database prefix', $this->databasePrefix],
113         ['User agent', $user_agent],
114         ['Site path', $this->siteDirectory],
115       ]);
116     }
117   }
118
119   /**
120    * Gets the setup class.
121    *
122    * @param string|null $file
123    *   The file to get the setup class from.
124    *
125    * @return string|null
126    *   The setup class contained in the provided $file.
127    *
128    * @throws \InvalidArgumentException
129    *   Thrown if the file does not exist, does not contain a class or the class
130    *   does not implement \Drupal\TestSite\TestSetupInterface.
131    */
132   protected function getSetupClass($file) {
133     if ($file === NULL) {
134       return;
135     }
136     if (!file_exists($file)) {
137       throw new \InvalidArgumentException("The file $file does not exist.");
138     }
139
140     $classes = get_declared_classes();
141     include_once $file;
142     $new_classes = array_values(array_diff(get_declared_classes(), $classes));
143     if (empty($new_classes)) {
144       throw new \InvalidArgumentException("The file $file does not contain a class.");
145     }
146     $class = array_pop($new_classes);
147
148     if (!is_subclass_of($class, TestSetupInterface::class)) {
149       throw new \InvalidArgumentException("The class $class contained in $file needs to implement \Drupal\TestSite\TestSetupInterface");
150     }
151     return $class;
152   }
153
154   /**
155    * Ensures that the sites/simpletest directory exists and is writable.
156    *
157    * @param string $root
158    *   The Drupal root.
159    */
160   protected function ensureDirectory($root) {
161     if (!is_writable($root . '/sites/simpletest')) {
162       if (!@mkdir($root . '/sites/simpletest')) {
163         throw new \RuntimeException($root . '/sites/simpletest must exist and be writable to install a test site');
164       }
165     }
166   }
167
168   /**
169    * Creates a test drupal installation.
170    *
171    * @param string $profile
172    *   (optional) The installation profile to use.
173    * @param string $setup_class
174    *   (optional) Setup class. A PHP class to setup configuration used by the
175    *   test.
176    * @param string $langcode
177    *   (optional) The language to install the site in.
178    */
179   public function setup($profile = 'testing', $setup_class = NULL, $langcode = 'en') {
180     $this->profile = $profile;
181     $this->langcode = $langcode;
182     $this->setupBaseUrl();
183     $this->prepareEnvironment();
184     $this->installDrupal();
185
186     if ($setup_class) {
187       $this->executeSetupClass($setup_class);
188     }
189   }
190
191   /**
192    * Installs Drupal into the test site.
193    */
194   protected function installDrupal() {
195     $this->initUserSession();
196     $this->prepareSettings();
197     $this->doInstall();
198     $this->initSettings();
199     $container = $this->initKernel(\Drupal::request());
200     $this->initConfig($container);
201     $this->installModulesFromClassProperty($container);
202     $this->rebuildAll();
203   }
204
205   /**
206    * Uses the setup file to configure Drupal.
207    *
208    * @param string $class
209    *   The fully qualified class name, which should set up Drupal for tests. For
210    *   example this class could create content types and fields or install
211    *   modules. The class needs to implement TestSetupInterface.
212    *
213    * @see \Drupal\TestSite\TestSetupInterface
214    */
215   protected function executeSetupClass($class) {
216     /** @var \Drupal\TestSite\TestSetupInterface $instance */
217     $instance = new $class();
218     $instance->setup();
219   }
220
221   /**
222    * {@inheritdoc}
223    */
224   protected function installParameters() {
225     $parameters = $this->installParametersTrait();
226     $parameters['parameters']['langcode'] = $this->langcode;
227     return $parameters;
228   }
229
230   /**
231    * {@inheritdoc}
232    */
233   protected function changeDatabasePrefix() {
234     // Ensure that we use the database from SIMPLETEST_DB environment variable.
235     Database::removeConnection('default');
236     $this->changeDatabasePrefixTrait();
237   }
238
239   /**
240    * {@inheritdoc}
241    */
242   protected function prepareDatabasePrefix() {
243     // Override this method so that we can force a lock to be created.
244     $test_db = new TestDatabase(NULL, TRUE);
245     $this->siteDirectory = $test_db->getTestSitePath();
246     $this->databasePrefix = $test_db->getDatabasePrefix();
247   }
248
249 }