Patched to Drupal 8.4.8 level. See https://www.drupal.org/sa-core-2018-004 and patch...
[yaffs-website] / web / core / lib / Drupal / Core / Test / FunctionalTestSetupTrait.php
1 <?php
2
3 namespace Drupal\Core\Test;
4
5 use Drupal\Component\FileCache\FileCacheFactory;
6 use Drupal\Component\Utility\SafeMarkup;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\Core\Config\Development\ConfigSchemaChecker;
9 use Drupal\Core\Database\Database;
10 use Drupal\Core\DrupalKernel;
11 use Drupal\Core\Extension\MissingDependencyException;
12 use Drupal\Core\Serialization\Yaml;
13 use Drupal\Core\Session\UserSession;
14 use Drupal\Core\Site\Settings;
15 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
17 use Symfony\Component\HttpFoundation\Request;
18 use Symfony\Component\Yaml\Yaml as SymfonyYaml;
19
20 /**
21  * Defines a trait for shared functional test setup functionality.
22  */
23 trait FunctionalTestSetupTrait {
24
25   /**
26    * The "#1" admin user.
27    *
28    * @var \Drupal\Core\Session\AccountInterface
29    */
30   protected $rootUser;
31
32   /**
33    * The class loader to use for installation and initialization of setup.
34    *
35    * @var \Symfony\Component\Classloader\Classloader
36    */
37   protected $classLoader;
38
39   /**
40    * The config directories used in this test.
41    */
42   protected $configDirectories = [];
43
44   /**
45    * Prepares site settings and services before installation.
46    */
47   protected function prepareSettings() {
48     // Prepare installer settings that are not install_drupal() parameters.
49     // Copy and prepare an actual settings.php, so as to resemble a regular
50     // installation.
51     // Not using File API; a potential error must trigger a PHP warning.
52     $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
53     copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
54
55     // The public file system path is created during installation. Additionally,
56     // during tests:
57     // - The temporary directory is set and created by install_base_system().
58     // - The private file directory is created post install by
59     //   FunctionalTestSetupTrait::initConfig().
60     // @see system_requirements()
61     // @see TestBase::prepareEnvironment()
62     // @see install_base_system()
63     // @see \Drupal\Core\Test\FunctionalTestSetupTrait::initConfig()
64     $settings['settings']['file_public_path'] = (object) [
65       'value' => $this->publicFilesDirectory,
66       'required' => TRUE,
67     ];
68     $settings['settings']['file_private_path'] = (object) [
69       'value' => $this->privateFilesDirectory,
70       'required' => TRUE,
71     ];
72     // Save the original site directory path, so that extensions in the
73     // site-specific directory can still be discovered in the test site
74     // environment.
75     // @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
76     $settings['settings']['test_parent_site'] = (object) [
77       'value' => $this->originalSite,
78       'required' => TRUE,
79     ];
80     // Add the parent profile's search path to the child site's search paths.
81     // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
82     $settings['conf']['simpletest.settings']['parent_profile'] = (object) [
83       'value' => $this->originalProfile,
84       'required' => TRUE,
85     ];
86     $this->writeSettings($settings);
87     // Allow for test-specific overrides.
88     $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
89     if (file_exists($settings_testing_file)) {
90       // Copy the testing-specific settings.php overrides in place.
91       copy($settings_testing_file, $directory . '/settings.testing.php');
92       // Add the name of the testing class to settings.php and include the
93       // testing specific overrides.
94       file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
95     }
96     $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
97     if (!file_exists($settings_services_file)) {
98       // Otherwise, use the default services as a starting point for overrides.
99       $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
100     }
101     // Copy the testing-specific service overrides in place.
102     copy($settings_services_file, $directory . '/services.yml');
103     if ($this->strictConfigSchema) {
104       // Add a listener to validate configuration schema on save.
105       $yaml = new SymfonyYaml();
106       $content = file_get_contents($directory . '/services.yml');
107       $services = $yaml->parse($content);
108       $services['services']['simpletest.config_schema_checker'] = [
109         'class' => ConfigSchemaChecker::class,
110         'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
111         'tags' => [['name' => 'event_subscriber']],
112       ];
113       file_put_contents($directory . '/services.yml', $yaml->dump($services));
114     }
115     // Since Drupal is bootstrapped already, install_begin_request() will not
116     // bootstrap again. Hence, we have to reload the newly written custom
117     // settings.php manually.
118     Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
119   }
120
121   /**
122    * Rewrites the settings.php file of the test site.
123    *
124    * @param array $settings
125    *   An array of settings to write out, in the format expected by
126    *   drupal_rewrite_settings().
127    *
128    * @see drupal_rewrite_settings()
129    */
130   protected function writeSettings(array $settings) {
131     include_once DRUPAL_ROOT . '/core/includes/install.inc';
132     $filename = $this->siteDirectory . '/settings.php';
133     // system_requirements() removes write permissions from settings.php
134     // whenever it is invoked.
135     // Not using File API; a potential error must trigger a PHP warning.
136     chmod($filename, 0666);
137     drupal_rewrite_settings($settings, $filename);
138   }
139
140   /**
141    * Changes parameters in the services.yml file.
142    *
143    * @param string $name
144    *   The name of the parameter.
145    * @param string $value
146    *   The value of the parameter.
147    */
148   protected function setContainerParameter($name, $value) {
149     $filename = $this->siteDirectory . '/services.yml';
150     chmod($filename, 0666);
151
152     $services = Yaml::decode(file_get_contents($filename));
153     $services['parameters'][$name] = $value;
154     file_put_contents($filename, Yaml::encode($services));
155
156     // Ensure that the cache is deleted for the yaml file loader.
157     $file_cache = FileCacheFactory::get('container_yaml_loader');
158     $file_cache->delete($filename);
159   }
160
161   /**
162    * Rebuilds \Drupal::getContainer().
163    *
164    * Use this to update the test process's kernel with a new service container.
165    * For example, when the list of enabled modules is changed via the internal
166    * browser the test process's kernel has a service container with an out of
167    * date module list.
168    *
169    * @see TestBase::prepareEnvironment()
170    * @see TestBase::restoreEnvironment()
171    *
172    * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
173    *   changes are immediately reflected in \Drupal::getContainer(). Until then,
174    *   tests can invoke this workaround when requiring services from newly
175    *   enabled modules to be immediately available in the same request.
176    */
177   protected function rebuildContainer() {
178     // Rebuild the kernel and bring it back to a fully bootstrapped state.
179     $this->container = $this->kernel->rebuildContainer();
180
181     // Make sure the url generator has a request object, otherwise calls to
182     // $this->drupalGet() will fail.
183     $this->prepareRequestForGenerator();
184   }
185
186   /**
187    * Resets all data structures after having enabled new modules.
188    *
189    * This method is called by FunctionalTestSetupTrait::rebuildAll() after
190    * enabling the requested modules. It must be called again when additional
191    * modules are enabled later.
192    *
193    * @see \Drupal\Core\Test\FunctionalTestSetupTrait::rebuildAll()
194    * @see \Drupal\Tests\BrowserTestBase::installDrupal()
195    * @see \Drupal\simpletest\WebTestBase::setUp()
196    */
197   protected function resetAll() {
198     // Clear all database and static caches and rebuild data structures.
199     drupal_flush_all_caches();
200     $this->container = \Drupal::getContainer();
201
202     // Reset static variables and reload permissions.
203     $this->refreshVariables();
204   }
205
206   /**
207    * Refreshes in-memory configuration and state information.
208    *
209    * Useful after a page request is made that changes configuration or state in
210    * a different thread.
211    *
212    * In other words calling a settings page with $this->drupalPostForm() with a
213    * changed value would update configuration to reflect that change, but in the
214    * thread that made the call (thread running the test) the changed values
215    * would not be picked up.
216    *
217    * This method clears the cache and loads a fresh copy.
218    */
219   protected function refreshVariables() {
220     // Clear the tag cache.
221     \Drupal::service('cache_tags.invalidator')->resetChecksums();
222     foreach (Cache::getBins() as $backend) {
223       if (is_callable([$backend, 'reset'])) {
224         $backend->reset();
225       }
226     }
227
228     $this->container->get('config.factory')->reset();
229     $this->container->get('state')->resetCache();
230   }
231
232   /**
233    * Creates a mock request and sets it on the generator.
234    *
235    * This is used to manipulate how the generator generates paths during tests.
236    * It also ensures that calls to $this->drupalGet() will work when running
237    * from run-tests.sh because the url generator no longer looks at the global
238    * variables that are set there but relies on getting this information from a
239    * request object.
240    *
241    * @param bool $clean_urls
242    *   Whether to mock the request using clean urls.
243    * @param array $override_server_vars
244    *   An array of server variables to override.
245    *
246    * @return \Symfony\Component\HttpFoundation\Request
247    *   The mocked request object.
248    */
249   protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = []) {
250     $request = Request::createFromGlobals();
251     $server = $request->server->all();
252     if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
253       // We need this for when the test is executed by run-tests.sh.
254       // @todo Remove this once run-tests.sh has been converted to use a Request
255       //   object.
256       $cwd = getcwd();
257       $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
258       $base_path = rtrim($server['REQUEST_URI'], '/');
259     }
260     else {
261       $base_path = $request->getBasePath();
262     }
263     if ($clean_urls) {
264       $request_path = $base_path ? $base_path . '/user' : 'user';
265     }
266     else {
267       $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
268     }
269     $server = array_merge($server, $override_server_vars);
270
271     $request = Request::create($request_path, 'GET', [], [], [], $server);
272     // Ensure the request time is REQUEST_TIME to ensure that API calls
273     // in the test use the right timestamp.
274     $request->server->set('REQUEST_TIME', REQUEST_TIME);
275     $this->container->get('request_stack')->push($request);
276
277     // The request context is normally set by the router_listener from within
278     // its KernelEvents::REQUEST listener. In the simpletest parent site this
279     // event is not fired, therefore it is necessary to updated the request
280     // context manually here.
281     $this->container->get('router.request_context')->fromRequest($request);
282
283     return $request;
284   }
285
286   /**
287    * Execute the non-interactive installer.
288    *
289    * @see install_drupal()
290    */
291   protected function doInstall() {
292     require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
293     install_drupal($this->classLoader, $this->installParameters());
294   }
295
296   /**
297    * Initialize settings created during install.
298    */
299   protected function initSettings() {
300     Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
301     foreach ($GLOBALS['config_directories'] as $type => $path) {
302       $this->configDirectories[$type] = $path;
303     }
304
305     // After writing settings.php, the installer removes write permissions
306     // from the site directory. To allow drupal_generate_test_ua() to write
307     // a file containing the private key for drupal_valid_test_ua(), the site
308     // directory has to be writable.
309     // TestBase::restoreEnvironment() will delete the entire site directory.
310     // Not using File API; a potential error must trigger a PHP warning.
311     chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
312
313     // During tests, cacheable responses should get the debugging cacheability
314     // headers by default.
315     $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
316   }
317
318   /**
319    * Initialize various configurations post-installation.
320    *
321    * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
322    *   The container.
323    */
324   protected function initConfig(ContainerInterface $container) {
325     $config = $container->get('config.factory');
326
327     // Manually create the private directory.
328     file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
329
330     // Manually configure the test mail collector implementation to prevent
331     // tests from sending out emails and collect them in state instead.
332     // While this should be enforced via settings.php prior to installation,
333     // some tests expect to be able to test mail system implementations.
334     $config->getEditable('system.mail')
335       ->set('interface.default', 'test_mail_collector')
336       ->save();
337
338     // By default, verbosely display all errors and disable all production
339     // environment optimizations for all tests to avoid needless overhead and
340     // ensure a sane default experience for test authors.
341     // @see https://www.drupal.org/node/2259167
342     $config->getEditable('system.logging')
343       ->set('error_level', 'verbose')
344       ->save();
345     $config->getEditable('system.performance')
346       ->set('css.preprocess', FALSE)
347       ->set('js.preprocess', FALSE)
348       ->save();
349
350     // Set an explicit time zone to not rely on the system one, which may vary
351     // from setup to setup. The Australia/Sydney time zone is chosen so all
352     // tests are run using an edge case scenario (UTC10 and DST). This choice
353     // is made to prevent time zone related regressions and reduce the
354     // fragility of the testing system in general.
355     $config->getEditable('system.date')
356       ->set('timezone.default', 'Australia/Sydney')
357       ->save();
358   }
359
360   /**
361    * Initializes user 1 for the site to be installed.
362    */
363   protected function initUserSession() {
364     $password = $this->randomMachineName();
365     // Define information about the user 1 account.
366     $this->rootUser = new UserSession([
367       'uid' => 1,
368       'name' => 'admin',
369       'mail' => 'admin@example.com',
370       'pass_raw' => $password,
371       'passRaw' => $password,
372       'timezone' => date_default_timezone_get(),
373     ]);
374
375     // The child site derives its session name from the database prefix when
376     // running web tests.
377     $this->generateSessionName($this->databasePrefix);
378   }
379
380   /**
381    * Initializes the kernel after installation.
382    *
383    * @param \Symfony\Component\HttpFoundation\Request $request
384    *   Request object.
385    *
386    * @return \Symfony\Component\DependencyInjection\ContainerInterface
387    *   The container.
388    */
389   protected function initKernel(Request $request) {
390     $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
391     $this->kernel->prepareLegacyRequest($request);
392     // Force the container to be built from scratch instead of loaded from the
393     // disk. This forces us to not accidentally load the parent site.
394     return $this->kernel->rebuildContainer();
395   }
396
397   /**
398    * Install modules defined by `static::$modules`.
399    *
400    * To install test modules outside of the testing environment, add
401    * @code
402    * $settings['extension_discovery_scan_tests'] = TRUE;
403    * @endcode
404    * to your settings.php.
405    *
406    * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
407    *   The container.
408    */
409   protected function installModulesFromClassProperty(ContainerInterface $container) {
410     $class = get_class($this);
411     $modules = [];
412     while ($class) {
413       if (property_exists($class, 'modules')) {
414         $modules = array_merge($modules, $class::$modules);
415       }
416       $class = get_parent_class($class);
417     }
418     if ($modules) {
419       $modules = array_unique($modules);
420       try {
421         $success = $container->get('module_installer')->install($modules, TRUE);
422         $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', ['%modules' => implode(', ', $modules)]));
423       }
424       catch (MissingDependencyException $e) {
425         // The exception message has all the details.
426         $this->fail($e->getMessage());
427       }
428
429       $this->rebuildContainer();
430     }
431   }
432
433   /**
434    * Resets and rebuilds the environment after setup.
435    */
436   protected function rebuildAll() {
437     // Reset/rebuild all data structures after enabling the modules, primarily
438     // to synchronize all data structures and caches between the test runner and
439     // the child site.
440     // @see \Drupal\Core\DrupalKernel::bootCode()
441     // @todo Test-specific setUp() methods may set up further fixtures; find a
442     //   way to execute this after setUp() is done, or to eliminate it entirely.
443     $this->resetAll();
444     $this->kernel->prepareLegacyRequest(\Drupal::request());
445
446     // Explicitly call register() again on the container registered in \Drupal.
447     // @todo This should already be called through
448     //   DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
449     //   appears to be calling a different container.
450     $this->container->get('stream_wrapper_manager')->register();
451   }
452
453   /**
454    * Returns the parameters that will be used when Simpletest installs Drupal.
455    *
456    * @see install_drupal()
457    * @see install_state_defaults()
458    *
459    * @return array
460    *   Array of parameters for use in install_drupal().
461    */
462   protected function installParameters() {
463     $connection_info = Database::getConnectionInfo();
464     $driver = $connection_info['default']['driver'];
465     $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
466     unset($connection_info['default']['driver']);
467     unset($connection_info['default']['namespace']);
468     unset($connection_info['default']['pdo']);
469     unset($connection_info['default']['init_commands']);
470     // Remove database connection info that is not used by SQLite.
471     if ($driver === 'sqlite') {
472       unset($connection_info['default']['username']);
473       unset($connection_info['default']['password']);
474       unset($connection_info['default']['host']);
475       unset($connection_info['default']['port']);
476     }
477     $parameters = [
478       'interactive' => FALSE,
479       'parameters' => [
480         'profile' => $this->profile,
481         'langcode' => 'en',
482       ],
483       'forms' => [
484         'install_settings_form' => [
485           'driver' => $driver,
486           $driver => $connection_info['default'],
487         ],
488         'install_configure_form' => [
489           'site_name' => 'Drupal',
490           'site_mail' => 'simpletest@example.com',
491           'account' => [
492             'name' => $this->rootUser->name,
493             'mail' => $this->rootUser->getEmail(),
494             'pass' => [
495               'pass1' => isset($this->rootUser->pass_raw) ? $this->rootUser->pass_raw : $this->rootUser->passRaw,
496               'pass2' => isset($this->rootUser->pass_raw) ? $this->rootUser->pass_raw : $this->rootUser->passRaw,
497             ],
498           ],
499           // form_type_checkboxes_value() requires NULL instead of FALSE values
500           // for programmatic form submissions to disable a checkbox.
501           'enable_update_status_module' => NULL,
502           'enable_update_status_emails' => NULL,
503         ],
504       ],
505     ];
506
507     // If we only have one db driver available, we cannot set the driver.
508     include_once DRUPAL_ROOT . '/core/includes/install.inc';
509     if (count($this->getDatabaseTypes()) == 1) {
510       unset($parameters['forms']['install_settings_form']['driver']);
511     }
512     return $parameters;
513   }
514
515   /**
516    * Sets up the base URL based upon the environment variable.
517    *
518    * @throws \Exception
519    *   Thrown when no SIMPLETEST_BASE_URL environment variable is provided.
520    */
521   protected function setupBaseUrl() {
522     global $base_url;
523
524     // Get and set the domain of the environment we are running our test
525     // coverage against.
526     $base_url = getenv('SIMPLETEST_BASE_URL');
527     if (!$base_url) {
528       throw new \Exception(
529         'You must provide a SIMPLETEST_BASE_URL environment variable to run some PHPUnit based functional tests.'
530       );
531     }
532
533     // Setup $_SERVER variable.
534     $parsed_url = parse_url($base_url);
535     $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
536     $path = isset($parsed_url['path']) ? rtrim(rtrim($parsed_url['path']), '/') : '';
537     $port = isset($parsed_url['port']) ? $parsed_url['port'] : 80;
538
539     $this->baseUrl = $base_url;
540
541     // If the passed URL schema is 'https' then setup the $_SERVER variables
542     // properly so that testing will run under HTTPS.
543     if ($parsed_url['scheme'] === 'https') {
544       $_SERVER['HTTPS'] = 'on';
545     }
546     $_SERVER['HTTP_HOST'] = $host;
547     $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
548     $_SERVER['SERVER_ADDR'] = '127.0.0.1';
549     $_SERVER['SERVER_PORT'] = $port;
550     $_SERVER['SERVER_SOFTWARE'] = NULL;
551     $_SERVER['SERVER_NAME'] = 'localhost';
552     $_SERVER['REQUEST_URI'] = $path . '/';
553     $_SERVER['REQUEST_METHOD'] = 'GET';
554     $_SERVER['SCRIPT_NAME'] = $path . '/index.php';
555     $_SERVER['SCRIPT_FILENAME'] = $path . '/index.php';
556     $_SERVER['PHP_SELF'] = $path . '/index.php';
557     $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
558   }
559
560   /**
561    * Prepares the current environment for running the test.
562    *
563    * Also sets up new resources for the testing environment, such as the public
564    * filesystem and configuration directories.
565    *
566    * This method is private as it must only be called once by
567    * BrowserTestBase::setUp() (multiple invocations for the same test would have
568    * unpredictable consequences) and it must not be callable or overridable by
569    * test classes.
570    */
571   protected function prepareEnvironment() {
572     // Bootstrap Drupal so we can use Drupal's built in functions.
573     $this->classLoader = require __DIR__ . '/../../../../../autoload.php';
574     $request = Request::createFromGlobals();
575     $kernel = TestRunnerKernel::createFromRequest($request, $this->classLoader);
576     // TestRunnerKernel expects the working directory to be DRUPAL_ROOT.
577     chdir(DRUPAL_ROOT);
578     $kernel->prepareLegacyRequest($request);
579     $this->prepareDatabasePrefix();
580
581     $this->originalSite = $kernel->findSitePath($request);
582
583     // Create test directory ahead of installation so fatal errors and debug
584     // information can be logged during installation process.
585     file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
586
587     // Prepare filesystem directory paths.
588     $this->publicFilesDirectory = $this->siteDirectory . '/files';
589     $this->privateFilesDirectory = $this->siteDirectory . '/private';
590     $this->tempFilesDirectory = $this->siteDirectory . '/temp';
591     $this->translationFilesDirectory = $this->siteDirectory . '/translations';
592
593     // Ensure the configImporter is refreshed for each test.
594     $this->configImporter = NULL;
595
596     // Unregister all custom stream wrappers of the parent site.
597     $wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::ALL);
598     foreach ($wrappers as $scheme => $info) {
599       stream_wrapper_unregister($scheme);
600     }
601
602     // Reset statics.
603     drupal_static_reset();
604
605     $this->container = NULL;
606
607     // Unset globals.
608     unset($GLOBALS['config_directories']);
609     unset($GLOBALS['config']);
610     unset($GLOBALS['conf']);
611
612     // Log fatal errors.
613     ini_set('log_errors', 1);
614     ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
615
616     // Change the database prefix.
617     $this->changeDatabasePrefix();
618
619     // After preparing the environment and changing the database prefix, we are
620     // in a valid test environment.
621     drupal_valid_test_ua($this->databasePrefix);
622
623     // Reset settings.
624     new Settings([
625       // For performance, simply use the database prefix as hash salt.
626       'hash_salt' => $this->databasePrefix,
627     ]);
628
629     drupal_set_time_limit($this->timeLimit);
630
631     // Save and clean the shutdown callbacks array because it is static cached
632     // and will be changed by the test run. Otherwise it will contain callbacks
633     // from both environments and the testing environment will try to call the
634     // handlers defined by the original one.
635     $callbacks = &drupal_register_shutdown_function();
636     $this->originalShutdownCallbacks = $callbacks;
637     $callbacks = [];
638   }
639
640   /**
641    * Returns all supported database driver installer objects.
642    *
643    * This wraps drupal_get_database_types() for use without a current container.
644    *
645    * @return \Drupal\Core\Database\Install\Tasks[]
646    *   An array of available database driver installer objects.
647    */
648   protected function getDatabaseTypes() {
649     if ($this->originalContainer) {
650       \Drupal::setContainer($this->originalContainer);
651     }
652     $database_types = drupal_get_database_types();
653     if ($this->originalContainer) {
654       \Drupal::unsetContainer();
655     }
656     return $database_types;
657   }
658
659 }