Files
icehrm/lib/composer/vendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php
2017-09-03 20:39:22 +02:00

328 lines
11 KiB
PHP

<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\AutomaticOptionsProviderInterface;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The AnnotatedCommandFactory creates commands for your application.
* Use with a Dependency Injection Container and the CommandFactory.
* Alternately, use the CommandFileDiscovery to find commandfiles, and
* then use AnnotatedCommandFactory::createCommandsFromClass() to create
* commands. See the README for more information.
*
* @package Consolidation\AnnotatedCommand
*/
class AnnotatedCommandFactory implements AutomaticOptionsProviderInterface
{
/** var CommandProcessor */
protected $commandProcessor;
/** var CommandCreationListenerInterface[] */
protected $listeners = [];
/** var AutomaticOptionsProvider[] */
protected $automaticOptionsProviderList = [];
/** var boolean */
protected $includeAllPublicMethods = true;
/** var CommandInfoAltererInterface */
protected $commandInfoAlterers = [];
public function __construct()
{
$this->commandProcessor = new CommandProcessor(new HookManager());
$this->addAutomaticOptionProvider($this);
}
public function setCommandProcessor(CommandProcessor $commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
/**
* @return CommandProcessor
*/
public function commandProcessor()
{
return $this->commandProcessor;
}
/**
* Set the 'include all public methods flag'. If true (the default), then
* every public method of each commandFile will be used to create commands.
* If it is false, then only those public methods annotated with @command
* or @name (deprecated) will be used to create commands.
*/
public function setIncludeAllPublicMethods($includeAllPublicMethods)
{
$this->includeAllPublicMethods = $includeAllPublicMethods;
return $this;
}
public function getIncludeAllPublicMethods()
{
return $this->includeAllPublicMethods;
}
/**
* @return HookManager
*/
public function hookManager()
{
return $this->commandProcessor()->hookManager();
}
/**
* Add a listener that is notified immediately before the command
* factory creates commands from a commandFile instance. This
* listener can use this opportunity to do more setup for the commandFile,
* and so on.
*
* @param CommandCreationListenerInterface $listener
*/
public function addListener(CommandCreationListenerInterface $listener)
{
$this->listeners[] = $listener;
}
/**
* Call all command creation listeners
*
* @param object $commandFileInstance
*/
protected function notify($commandFileInstance)
{
foreach ($this->listeners as $listener) {
$listener->notifyCommandFileAdded($commandFileInstance);
}
}
public function addAutomaticOptionProvider(AutomaticOptionsProviderInterface $optionsProvider)
{
$this->automaticOptionsProviderList[] = $optionsProvider;
}
public function addCommandInfoAlterer(CommandInfoAltererInterface $alterer)
{
$this->commandInfoAlterers[] = $alterer;
}
/**
* n.b. This registers all hooks from the commandfile instance as a side-effect.
*/
public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
$this->notify($commandFileInstance);
$commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
$this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods);
}
public function getCommandInfoListFromClass($classNameOrInstance)
{
$commandInfoList = [];
// Ignore special functions, such as __construct and __call, which
// can never be commands.
$commandMethodNames = array_filter(
get_class_methods($classNameOrInstance) ?: [],
function ($m) {
return !preg_match('#^_#', $m);
}
);
foreach ($commandMethodNames as $commandMethodName) {
$commandInfoList[] = new CommandInfo($classNameOrInstance, $commandMethodName);
}
return $commandInfoList;
}
public function createCommandInfo($classNameOrInstance, $commandMethodName)
{
return new CommandInfo($classNameOrInstance, $commandMethodName);
}
public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
return $this->createSelectedCommandsFromClassInfo(
$commandInfoList,
$commandFileInstance,
function ($commandInfo) use ($includeAllPublicMethods) {
return static::isCommandMethod($commandInfo, $includeAllPublicMethods);
}
);
}
public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
{
$commandList = [];
foreach ($commandInfoList as $commandInfo) {
if ($commandSelector($commandInfo)) {
$command = $this->createCommand($commandInfo, $commandFileInstance);
$commandList[] = $command;
}
}
return $commandList;
}
public static function isCommandMethod($commandInfo, $includeAllPublicMethods)
{
// Ignore everything labeled @hook
if ($commandInfo->hasAnnotation('hook')) {
return false;
}
// Include everything labeled @command
if ($commandInfo->hasAnnotation('command')) {
return true;
}
// Skip anything named like an accessor ('get' or 'set')
if (preg_match('#^(get[A-Z]|set[A-Z])#', $commandInfo->getMethodName())) {
return false;
}
// Default to the setting of 'include all public methods'.
return $includeAllPublicMethods;
}
public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
{
foreach ($commandInfoList as $commandInfo) {
if ($commandInfo->hasAnnotation('hook')) {
$this->registerCommandHook($commandInfo, $commandFileInstance);
}
}
}
/**
* Register a command hook given the CommandInfo for a method.
*
* The hook format is:
*
* @hook type name type
*
* For example, the pre-validate hook for the core:init command is:
*
* @hook pre-validate core:init
*
* If no command name is provided, then this hook will affect every
* command that is defined in the same file.
*
* If no hook is provided, then we will presume that ALTER_RESULT
* is intended.
*
* @param CommandInfo $commandInfo Information about the command hook method.
* @param object $commandFileInstance An instance of the CommandFile class.
*/
public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
{
// Ignore if the command info has no @hook
if (!$commandInfo->hasAnnotation('hook')) {
return;
}
$hookData = $commandInfo->getAnnotation('hook');
$hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
$commandName = $this->getNthWord($hookData, 1);
// Register the hook
$callback = [$commandFileInstance, $commandInfo->getMethodName()];
$this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
// If the hook has options, then also register the commandInfo
// with the hook manager, so that we can add options and such to
// the commands they hook.
if (!$commandInfo->options()->isEmpty()) {
$this->commandProcessor()->hookManager()->recordHookOptions($commandInfo, $commandName);
}
}
protected function getNthWord($string, $n, $default = '', $delimiter = ' ')
{
$words = explode($delimiter, $string);
if (!empty($words[$n])) {
return $words[$n];
}
return $default;
}
public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
{
$this->alterCommandInfo($commandInfo, $commandFileInstance);
$command = new AnnotatedCommand($commandInfo->getName());
$commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
$command->setCommandCallback($commandCallback);
$command->setCommandProcessor($this->commandProcessor);
$command->setCommandInfo($commandInfo);
$automaticOptions = $this->callAutomaticOptionsProviders($commandInfo);
$command->setCommandOptions($commandInfo, $automaticOptions);
// Annotation commands are never bootstrap-aware, but for completeness
// we will notify on every created command, as some clients may wish to
// use this notification for some other purpose.
$this->notify($command);
return $command;
}
/**
* Give plugins an opportunity to update the commandInfo
*/
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance)
{
foreach ($this->commandInfoAlterers as $alterer) {
$alterer->alterCommandInfo($commandInfo, $commandFileInstance);
}
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function callAutomaticOptionsProviders(CommandInfo $commandInfo)
{
$automaticOptions = [];
foreach ($this->automaticOptionsProviderList as $automaticOptionsProvider) {
$automaticOptions += $automaticOptionsProvider->automaticOptions($commandInfo);
}
return $automaticOptions;
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function automaticOptions(CommandInfo $commandInfo)
{
$automaticOptions = [];
$formatManager = $this->commandProcessor()->formatterManager();
if ($formatManager) {
$annotationData = $commandInfo->getAnnotations()->getArrayCopy();
$formatterOptions = new FormatterOptions($annotationData);
$dataType = $commandInfo->getReturnType();
$automaticOptions = $formatManager->automaticOptions($formatterOptions, $dataType);
}
return $automaticOptions;
}
}