3 use Drush\Log\LogLevel;
4 use Drupal\Component\Assertion\Handle;
5 use Drush\Psysh\DrushHelpCommand;
6 use Drush\Psysh\DrushCommand;
10 * Implements hook_drush_command().
12 function cli_drush_command() {
13 $items['core-cli'] = array(
14 'description' => 'Open an interactive shell on a Drupal site.',
16 'aliases' => array('php'),
17 'bootstrap' => DRUSH_BOOTSTRAP_MAX,
18 'topics' => array('docs-repl'),
20 'version-history' => 'Use command history based on Drupal version (Default is per site).',
23 $items['docs-repl'] = array(
24 'description' => 'repl.md',
27 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
28 'callback' => 'drush_print_file',
29 'callback arguments' => array(drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH) . '/docs/repl.md'),
37 function drush_cli_core_cli() {
38 $drupal_major_version = drush_drupal_major_version();
39 $configuration = new \Psy\Configuration();
41 // Set the Drush specific history file path.
42 $configuration->setHistoryFile(drush_history_path_cli());
44 $shell = new Shell($configuration);
46 if ($drupal_major_version >= 8) {
47 // Register the assertion handler so exceptions are thrown instead of errors
48 // being triggered. This plays nicer with PsySH.
50 $shell->setScopeVariables(['container' => \Drupal::getContainer()]);
52 // Add Drupal 8 specific casters to the shell configuration.
53 $configuration->addCasters(_drush_core_cli_get_casters());
56 // Add Drush commands to the shell.
57 $commands = [new DrushHelpCommand()];
59 foreach (drush_commands_categorize(_drush_core_cli_get_commands()) as $category_data) {
60 $category_title = (string) $category_data['title'];
61 foreach ($category_data['commands'] as $command_config) {
62 $command = new DrushCommand($command_config);
63 // Set the category label on each.
64 $command->setCategory($category_title);
65 $commands[] = $command;
69 $shell->addCommands($commands);
71 // PsySH will never return control to us, but our shutdown handler will still
72 // run after the user presses ^D. Mark this command as completed to avoid a
73 // spurious error message.
74 drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE);
76 // Run the terminate event before the shell is run. Otherwise, if the shell
77 // is forking processes (the default), any child processes will close the
78 // database connection when they are killed. So when we return back to the
79 // parent process after, there is no connection. This will be called after the
80 // command in preflight still, but the subscriber instances are already
81 // created from before. Call terminate() regardless, this is a no-op for all
82 // DrupalBoot classes except DrupalBoot8.
83 if ($bootstrap = drush_get_bootstrap_object()) {
84 $bootstrap->terminate();
87 // To fix the above problem in Drupal 7, the connection can be closed manually.
88 // This will make sure a new connection is created again in child loops. So
89 // any shutdown functions will still run ok after the shell has exited.
90 if ($drupal_major_version == 7) {
91 Database::closeConnection();
98 * Returns a filtered list of Drush commands used for CLI commands.
102 function _drush_core_cli_get_commands() {
103 $commands = drush_get_commands();
104 $ignored_commands = ['help', 'drush-psysh', 'php-eval', 'core-cli', 'php'];
105 $php_keywords = _drush_core_cli_get_php_keywords();
107 foreach ($commands as $name => $config) {
108 // Ignore some commands that don't make sense inside PsySH, are PHP keywords
109 // are hidden, or are aliases.
110 if (in_array($name, $ignored_commands) || in_array($name, $php_keywords) || !empty($config['hidden']) || ($name !== $config['command'])) {
111 unset($commands[$name]);
114 // Make sure the command aliases don't contain any PHP keywords.
115 if (!empty($config['aliases'])) {
116 $commands[$name]['aliases'] = array_diff($commands[$name]['aliases'], $php_keywords);
125 * Returns a mapped array of casters for use in the shell.
127 * These are Symfony VarDumper casters.
128 * See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
129 * for more information.
132 * An array of caster callbacks keyed by class or interface.
134 function _drush_core_cli_get_casters() {
136 'Drupal\Core\Entity\ContentEntityInterface' => 'Drush\Psysh\Caster::castContentEntity',
137 'Drupal\Core\Field\FieldItemListInterface' => 'Drush\Psysh\Caster::castFieldItemList',
138 'Drupal\Core\Field\FieldItemInterface' => 'Drush\Psysh\Caster::castFieldItem',
139 'Drupal\Core\Config\Entity\ConfigEntityInterface' => 'Drush\Psysh\Caster::castConfigEntity',
140 'Drupal\Core\Config\ConfigBase' => 'Drush\Psysh\Caster::castConfig',
141 'Drupal\Component\DependencyInjection\Container' => 'Drush\Psysh\Caster::castContainer',
146 * Returns the file path for the CLI history.
148 * This can either be site specific (default) or Drupal version specific.
152 function drush_history_path_cli() {
153 $cli_directory = drush_directory_cache('cli');
155 // If only the Drupal version is being used for the history.
156 if (drush_get_option('version-history', FALSE)) {
157 $drupal_major_version = drush_drupal_major_version();
158 $file_name = "drupal-$drupal_major_version";
160 // If there is an alias, use that in the site specific name. Otherwise,
161 // use a hash of the root path.
163 if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
164 $site = drush_sitealias_get_record($alias);
165 $site_suffix = $site['#name'];
168 $site_suffix = md5(DRUPAL_ROOT);
171 $file_name = "drupal-site-$site_suffix";
174 $full_path = "$cli_directory/$file_name";
176 // Output the history path if verbose is enabled.
177 if (drush_get_context('DRUSH_VERBOSE')) {
178 drush_log(dt('History: @full_path', ['@full_path' => $full_path]), LogLevel::INFO);
185 * Returns a list of PHP keywords.
187 * This will act as a blacklist for command and alias names.
191 function _drush_core_cli_get_php_keywords() {