'Open an interactive shell on a Drupal site.', 'remote-tty' => TRUE, 'aliases' => array('php'), 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'topics' => array('docs-repl'), 'options' => array( 'version-history' => 'Use command history based on Drupal version (Default is per site).', ), ); $items['docs-repl'] = array( 'description' => 'repl.md', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_NONE, 'callback' => 'drush_print_file', 'callback arguments' => array(drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH) . '/docs/repl.md'), ); return $items; } /** * Command callback. */ function drush_cli_core_cli() { $drupal_major_version = drush_drupal_major_version(); $configuration = new \Psy\Configuration(); // Set the Drush specific history file path. $configuration->setHistoryFile(drush_history_path_cli()); $shell = new Shell($configuration); if ($drupal_major_version >= 8) { // Register the assertion handler so exceptions are thrown instead of errors // being triggered. This plays nicer with PsySH. Handle::register(); $shell->setScopeVariables(['container' => \Drupal::getContainer()]); // Add Drupal 8 specific casters to the shell configuration. $configuration->addCasters(_drush_core_cli_get_casters()); } // Add Drush commands to the shell. $commands = [new DrushHelpCommand()]; foreach (drush_commands_categorize(_drush_core_cli_get_commands()) as $category_data) { $category_title = (string) $category_data['title']; foreach ($category_data['commands'] as $command_config) { $command = new DrushCommand($command_config); // Set the category label on each. $command->setCategory($category_title); $commands[] = $command; } } $shell->addCommands($commands); // PsySH will never return control to us, but our shutdown handler will still // run after the user presses ^D. Mark this command as completed to avoid a // spurious error message. drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE); // Run the terminate event before the shell is run. Otherwise, if the shell // is forking processes (the default), any child processes will close the // database connection when they are killed. So when we return back to the // parent process after, there is no connection. This will be called after the // command in preflight still, but the subscriber instances are already // created from before. Call terminate() regardless, this is a no-op for all // DrupalBoot classes except DrupalBoot8. if ($bootstrap = drush_get_bootstrap_object()) { $bootstrap->terminate(); } // To fix the above problem in Drupal 7, the connection can be closed manually. // This will make sure a new connection is created again in child loops. So // any shutdown functions will still run ok after the shell has exited. if ($drupal_major_version == 7) { Database::closeConnection(); } $shell->run(); } /** * Returns a filtered list of Drush commands used for CLI commands. * * @return array */ function _drush_core_cli_get_commands() { $commands = drush_get_commands(); $ignored_commands = ['help', 'drush-psysh', 'php-eval', 'core-cli', 'php']; $php_keywords = _drush_core_cli_get_php_keywords(); foreach ($commands as $name => $config) { // Ignore some commands that don't make sense inside PsySH, are PHP keywords // are hidden, or are aliases. if (in_array($name, $ignored_commands) || in_array($name, $php_keywords) || !empty($config['hidden']) || ($name !== $config['command'])) { unset($commands[$name]); } else { // Make sure the command aliases don't contain any PHP keywords. if (!empty($config['aliases'])) { $commands[$name]['aliases'] = array_diff($commands[$name]['aliases'], $php_keywords); } } } return $commands; } /** * Returns a mapped array of casters for use in the shell. * * These are Symfony VarDumper casters. * See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters * for more information. * * @return array. * An array of caster callbacks keyed by class or interface. */ function _drush_core_cli_get_casters() { return [ 'Drupal\Core\Entity\ContentEntityInterface' => 'Drush\Psysh\Caster::castContentEntity', 'Drupal\Core\Field\FieldItemListInterface' => 'Drush\Psysh\Caster::castFieldItemList', 'Drupal\Core\Field\FieldItemInterface' => 'Drush\Psysh\Caster::castFieldItem', 'Drupal\Core\Config\Entity\ConfigEntityInterface' => 'Drush\Psysh\Caster::castConfigEntity', 'Drupal\Core\Config\ConfigBase' => 'Drush\Psysh\Caster::castConfig', 'Drupal\Component\DependencyInjection\Container' => 'Drush\Psysh\Caster::castContainer', ]; } /** * Returns the file path for the CLI history. * * This can either be site specific (default) or Drupal version specific. * * @return string. */ function drush_history_path_cli() { $cli_directory = drush_directory_cache('cli'); // If only the Drupal version is being used for the history. if (drush_get_option('version-history', FALSE)) { $drupal_major_version = drush_drupal_major_version(); $file_name = "drupal-$drupal_major_version"; } // If there is an alias, use that in the site specific name. Otherwise, // use a hash of the root path. else { if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { $site = drush_sitealias_get_record($alias); $site_suffix = $site['#name']; } else { $site_suffix = md5(DRUPAL_ROOT); } $file_name = "drupal-site-$site_suffix"; } $full_path = "$cli_directory/$file_name"; // Output the history path if verbose is enabled. if (drush_get_context('DRUSH_VERBOSE')) { drush_log(dt('History: @full_path', ['@full_path' => $full_path]), LogLevel::INFO); } return $full_path; } /** * Returns a list of PHP keywords. * * This will act as a blacklist for command and alias names. * * @return array */ function _drush_core_cli_get_php_keywords() { return [ '__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor', ]; }