first commit
This commit is contained in:
245
vendor/yiisoft/yii2/console/Application.php
vendored
Normal file
245
vendor/yiisoft/yii2/console/Application.php
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
use Yii;
|
||||
use yii\base\InvalidRouteException;
|
||||
use yii\base\Module;
|
||||
|
||||
// define STDIN, STDOUT and STDERR if the PHP SAPI did not define them (e.g. creating console application in web env)
|
||||
// https://www.php.net/manual/en/features.commandline.io-streams.php
|
||||
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
|
||||
defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w'));
|
||||
defined('STDERR') or define('STDERR', fopen('php://stderr', 'w'));
|
||||
|
||||
/**
|
||||
* Application represents a console application.
|
||||
*
|
||||
* Application extends from [[\yii\base\Application]] by providing functionalities that are
|
||||
* specific to console requests. In particular, it deals with console requests
|
||||
* through a command-based approach:
|
||||
*
|
||||
* - A console application consists of one or several possible user commands;
|
||||
* - Each user command is implemented as a class extending [[\yii\console\Controller]];
|
||||
* - User specifies which command to run on the command line;
|
||||
* - The command processes the user request with the specified parameters.
|
||||
*
|
||||
* The command classes should be under the namespace specified by [[controllerNamespace]].
|
||||
* Their naming should follow the same naming convention as controllers. For example, the `help` command
|
||||
* is implemented using the `HelpController` class.
|
||||
*
|
||||
* To run the console application, enter the following on the command line:
|
||||
*
|
||||
* ```
|
||||
* yii <route> [--param1=value1 --param2 ...]
|
||||
* ```
|
||||
*
|
||||
* where `<route>` refers to a controller route in the form of `ModuleID/ControllerID/ActionID`
|
||||
* (e.g. `sitemap/create`), and `param1`, `param2` refers to a set of named parameters that
|
||||
* will be used to initialize the controller action (e.g. `--since=0` specifies a `since` parameter
|
||||
* whose value is 0 and a corresponding `$since` parameter is passed to the action method).
|
||||
*
|
||||
* A `help` command is provided by default, which lists available commands and shows their usage.
|
||||
* To use this command, simply type:
|
||||
*
|
||||
* ```
|
||||
* yii help
|
||||
* ```
|
||||
*
|
||||
* @property-read ErrorHandler $errorHandler The error handler application component.
|
||||
* @property-read Request $request The request component.
|
||||
* @property-read Response $response The response component.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Application extends \yii\base\Application
|
||||
{
|
||||
/**
|
||||
* The option name for specifying the application configuration file path.
|
||||
*/
|
||||
public const OPTION_APPCONFIG = 'appconfig';
|
||||
/**
|
||||
* @var string the default route of this application. Defaults to 'help',
|
||||
* meaning the `help` command.
|
||||
*/
|
||||
public $defaultRoute = 'help';
|
||||
/**
|
||||
* @var bool whether to enable the commands provided by the core framework.
|
||||
* Defaults to true.
|
||||
*/
|
||||
public $enableCoreCommands = true;
|
||||
/**
|
||||
* @var Controller|null the currently active controller instance
|
||||
*
|
||||
* @phpstan-var Controller<Module>|null
|
||||
* @psalm-var Controller<Module>|null
|
||||
*/
|
||||
public $controller;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
$config = $this->loadConfig($config);
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the configuration.
|
||||
* This method will check if the command line option [[OPTION_APPCONFIG]] is specified.
|
||||
* If so, the corresponding file will be loaded as the application configuration.
|
||||
* Otherwise, the configuration provided as the parameter will be returned back.
|
||||
* @param array $config the configuration provided in the constructor.
|
||||
* @return array the actual configuration to be used by the application.
|
||||
*/
|
||||
protected function loadConfig($config)
|
||||
{
|
||||
if (!empty($_SERVER['argv'])) {
|
||||
$option = '--' . self::OPTION_APPCONFIG . '=';
|
||||
foreach ($_SERVER['argv'] as $param) {
|
||||
if (strpos($param, $option) !== false) {
|
||||
$path = substr($param, strlen($option));
|
||||
if (!empty($path) && is_file($file = Yii::getAlias($path))) {
|
||||
return require $file;
|
||||
}
|
||||
|
||||
exit("The configuration file does not exist: $path\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the application.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
if ($this->enableCoreCommands) {
|
||||
foreach ($this->coreCommands() as $id => $command) {
|
||||
if (!isset($this->controllerMap[$id])) {
|
||||
$this->controllerMap[$id] = $command;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ensure we have the 'help' command so that we can list the available commands
|
||||
if (!isset($this->controllerMap['help'])) {
|
||||
$this->controllerMap['help'] = 'yii\console\controllers\HelpController';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the specified request.
|
||||
* @param Request $request the request to be handled
|
||||
* @return Response the resulting response
|
||||
*/
|
||||
public function handleRequest($request)
|
||||
{
|
||||
list($route, $params) = $request->resolve();
|
||||
$this->requestedRoute = $route;
|
||||
$result = $this->runAction($route, $params);
|
||||
if ($result instanceof Response) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = $this->getResponse();
|
||||
$response->exitStatus = $result;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a controller action specified by a route.
|
||||
* This method parses the specified route and creates the corresponding child module(s), controller and action
|
||||
* instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
|
||||
* If the route is empty, the method will use [[defaultRoute]].
|
||||
*
|
||||
* For example, to run `public function actionTest($a, $b)` assuming that the controller has options the following
|
||||
* code should be used:
|
||||
*
|
||||
* ```
|
||||
* \Yii::$app->runAction('controller/test', ['option' => 'value', $a, $b]);
|
||||
* ```
|
||||
*
|
||||
* @param string $route the route that specifies the action.
|
||||
* @param array $params the parameters to be passed to the action
|
||||
* @return int|Response|null the result of the action. This can be either an exit code or Response object.
|
||||
* Exit code 0 means normal, and other values mean abnormal. Exit code of `null` is treated as `0` as well.
|
||||
* @throws Exception if the route is invalid
|
||||
*/
|
||||
public function runAction($route, $params = [])
|
||||
{
|
||||
try {
|
||||
$res = parent::runAction($route, $params);
|
||||
return is_object($res) ? $res : (int) $res;
|
||||
} catch (InvalidRouteException $e) {
|
||||
throw new UnknownCommandException($route, $this, 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration of the built-in commands.
|
||||
* @return array the configuration of the built-in commands.
|
||||
*/
|
||||
public function coreCommands()
|
||||
{
|
||||
return [
|
||||
'asset' => 'yii\console\controllers\AssetController',
|
||||
'cache' => 'yii\console\controllers\CacheController',
|
||||
'fixture' => 'yii\console\controllers\FixtureController',
|
||||
'help' => 'yii\console\controllers\HelpController',
|
||||
'message' => 'yii\console\controllers\MessageController',
|
||||
'migrate' => 'yii\console\controllers\MigrateController',
|
||||
'serve' => 'yii\console\controllers\ServeController',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error handler component.
|
||||
* @return ErrorHandler the error handler application component.
|
||||
*/
|
||||
public function getErrorHandler()
|
||||
{
|
||||
return $this->get('errorHandler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request component.
|
||||
* @return Request the request component.
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->get('request');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response component.
|
||||
* @return Response the response component.
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->get('response');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function coreComponents()
|
||||
{
|
||||
return array_merge(parent::coreComponents(), [
|
||||
'request' => ['class' => 'yii\console\Request'],
|
||||
'response' => ['class' => 'yii\console\Response'],
|
||||
'errorHandler' => ['class' => 'yii\console\ErrorHandler'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
815
vendor/yiisoft/yii2/console/Controller.php
vendored
Normal file
815
vendor/yiisoft/yii2/console/Controller.php
vendored
Normal file
@ -0,0 +1,815 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Action;
|
||||
use yii\base\InlineAction;
|
||||
use yii\base\InvalidRouteException;
|
||||
use yii\helpers\Console;
|
||||
use yii\helpers\Inflector;
|
||||
use yii\base\Controller as BaseController;
|
||||
use yii\base\Module;
|
||||
|
||||
/**
|
||||
* Controller is the base class of console command classes.
|
||||
*
|
||||
* A console controller consists of one or several actions known as sub-commands.
|
||||
* Users call a console command by specifying the corresponding route which identifies a controller action.
|
||||
* The `yii` program is used when calling a console command, like the following:
|
||||
*
|
||||
* ```
|
||||
* yii <route> [--param1=value1 --param2 ...]
|
||||
* ```
|
||||
*
|
||||
* where `<route>` is a route to a controller action and the params will be populated as properties of a command.
|
||||
* See [[options()]] for details.
|
||||
*
|
||||
* @property Request $request The request object.
|
||||
* @property Response $response The response object.
|
||||
* @property-read string $help The help information for this controller.
|
||||
* @property-read string $helpSummary The one-line short summary describing this controller.
|
||||
* @property-read array $passedOptionValues The properties corresponding to the passed options.
|
||||
* @property-read array $passedOptions The names of the options passed during execution.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*
|
||||
* @template T of Module
|
||||
* @extends BaseController<T>
|
||||
*/
|
||||
class Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* @deprecated since 2.0.13. Use [[ExitCode::OK]] instead.
|
||||
*/
|
||||
public const EXIT_CODE_NORMAL = 0;
|
||||
/**
|
||||
* @deprecated since 2.0.13. Use [[ExitCode::UNSPECIFIED_ERROR]] instead.
|
||||
*/
|
||||
public const EXIT_CODE_ERROR = 1;
|
||||
/**
|
||||
* @var bool whether to run the command interactively.
|
||||
*/
|
||||
public $interactive = true;
|
||||
/**
|
||||
* @var bool|null whether to enable ANSI color in the output.
|
||||
* If not set, ANSI color will only be enabled for terminals that support it.
|
||||
*/
|
||||
public $color;
|
||||
/**
|
||||
* @var bool whether to display help information about current command.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public $help = false;
|
||||
/**
|
||||
* @var bool|null if true - script finish with `ExitCode::OK` in case of exception.
|
||||
* false - `ExitCode::UNSPECIFIED_ERROR`.
|
||||
* Default: `YII_ENV_TEST`
|
||||
* @since 2.0.36
|
||||
*/
|
||||
public $silentExitOnException;
|
||||
|
||||
/**
|
||||
* @var array the options passed during execution.
|
||||
*/
|
||||
private $_passedOptions = [];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
$silentExit = $this->silentExitOnException !== null ? $this->silentExitOnException : YII_ENV_TEST;
|
||||
Yii::$app->errorHandler->silentExitOnException = $silentExit;
|
||||
|
||||
return parent::beforeAction($action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value indicating whether ANSI color is enabled.
|
||||
*
|
||||
* ANSI color is enabled only if [[color]] is set true or is not set
|
||||
* and the terminal supports ANSI color.
|
||||
*
|
||||
* @param resource $stream the stream to check.
|
||||
* @return bool Whether to enable ANSI style in output.
|
||||
*/
|
||||
public function isColorEnabled($stream = \STDOUT)
|
||||
{
|
||||
return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an action with the specified action ID and parameters.
|
||||
* If the action ID is empty, the method will use [[defaultAction]].
|
||||
* @param string $id the ID of the action to be executed.
|
||||
* @param array $params the parameters (name-value pairs) to be passed to the action.
|
||||
* @return int the status of the action execution. 0 means normal, other values mean abnormal.
|
||||
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
|
||||
* @throws Exception if there are unknown options or missing arguments
|
||||
* @see createAction
|
||||
*/
|
||||
public function runAction($id, $params = [])
|
||||
{
|
||||
if (!empty($params)) {
|
||||
// populate options here so that they are available in beforeAction().
|
||||
$options = $this->options($id === '' ? $this->defaultAction : $id);
|
||||
if (isset($params['_aliases'])) {
|
||||
$optionAliases = $this->optionAliases();
|
||||
foreach ($params['_aliases'] as $name => $value) {
|
||||
if (array_key_exists($name, $optionAliases)) {
|
||||
$params[$optionAliases[$name]] = $value;
|
||||
} else {
|
||||
$message = Yii::t('yii', 'Unknown alias: -{name}', ['name' => $name]);
|
||||
if (!empty($optionAliases)) {
|
||||
$aliasesAvailable = [];
|
||||
foreach ($optionAliases as $alias => $option) {
|
||||
$aliasesAvailable[] = '-' . $alias . ' (--' . $option . ')';
|
||||
}
|
||||
|
||||
$message .= '. ' . Yii::t('yii', 'Aliases available: {aliases}', [
|
||||
'aliases' => implode(', ', $aliasesAvailable)
|
||||
]);
|
||||
}
|
||||
throw new Exception($message);
|
||||
}
|
||||
}
|
||||
unset($params['_aliases']);
|
||||
}
|
||||
foreach ($params as $name => $value) {
|
||||
// Allow camelCase options to be entered in kebab-case
|
||||
if (!in_array($name, $options, true) && strpos($name, '-') !== false) {
|
||||
$kebabName = $name;
|
||||
$altName = lcfirst(Inflector::id2camel($kebabName));
|
||||
if (in_array($altName, $options, true)) {
|
||||
$name = $altName;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($name, $options, true)) {
|
||||
$default = $this->$name;
|
||||
if (is_array($default) && is_string($value)) {
|
||||
$this->$name = preg_split('/\s*,\s*(?![^()]*\))/', $value);
|
||||
} elseif ($default !== null) {
|
||||
settype($value, gettype($default));
|
||||
$this->$name = $value;
|
||||
} else {
|
||||
$this->$name = $value;
|
||||
}
|
||||
$this->_passedOptions[] = $name;
|
||||
unset($params[$name]);
|
||||
if (isset($kebabName)) {
|
||||
unset($params[$kebabName]);
|
||||
}
|
||||
} elseif (!is_int($name)) {
|
||||
$message = Yii::t('yii', 'Unknown option: --{name}', ['name' => $name]);
|
||||
if (!empty($options)) {
|
||||
$message .= '. ' . Yii::t('yii', 'Options available: {options}', ['options' => '--' . implode(', --', $options)]);
|
||||
}
|
||||
|
||||
throw new Exception($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->help) {
|
||||
$route = $this->getUniqueId() . '/' . $id;
|
||||
return Yii::$app->runAction('help', [$route]);
|
||||
}
|
||||
|
||||
return parent::runAction($id, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the parameters to the action.
|
||||
* This method is invoked by [[Action]] when it begins to run with the given parameters.
|
||||
* This method will first bind the parameters with the [[options()|options]]
|
||||
* available to the action. It then validates the given arguments.
|
||||
* @param Action $action the action to be bound with parameters
|
||||
* @param array $params the parameters to be bound to the action
|
||||
* @return array the valid parameters that the action can run with.
|
||||
* @throws Exception if there are unknown options or missing arguments
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*
|
||||
* @phpstan-param array<array-key, mixed> $params
|
||||
* @psalm-param array<array-key, mixed> $params
|
||||
*
|
||||
* @phpstan-return mixed[]
|
||||
* @psalm-return mixed[]
|
||||
*/
|
||||
public function bindActionParams($action, $params)
|
||||
{
|
||||
if ($action instanceof InlineAction) {
|
||||
$method = new \ReflectionMethod($this, $action->actionMethod);
|
||||
} else {
|
||||
$method = new \ReflectionMethod($action, 'run');
|
||||
}
|
||||
|
||||
$paramKeys = array_keys($params);
|
||||
$args = [];
|
||||
$missing = [];
|
||||
$actionParams = [];
|
||||
$requestedParams = [];
|
||||
foreach ($method->getParameters() as $i => $param) {
|
||||
$name = $param->getName();
|
||||
$key = null;
|
||||
if (array_key_exists($i, $params)) {
|
||||
$key = $i;
|
||||
} elseif (array_key_exists($name, $params)) {
|
||||
$key = $name;
|
||||
}
|
||||
|
||||
if ($key !== null) {
|
||||
if ($param->isVariadic()) {
|
||||
for ($j = array_search($key, $paramKeys); $j < count($paramKeys); $j++) {
|
||||
$jKey = $paramKeys[$j];
|
||||
if ($jKey !== $key && !is_int($jKey)) {
|
||||
break;
|
||||
}
|
||||
$args[] = $actionParams[$key][] = $params[$jKey];
|
||||
unset($params[$jKey]);
|
||||
}
|
||||
} else {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
$isArray = ($type = $param->getType()) instanceof \ReflectionNamedType && $type->getName() === 'array';
|
||||
} else {
|
||||
$isArray = $param->isArray();
|
||||
}
|
||||
if ($isArray) {
|
||||
$params[$key] = $params[$key] === '' ? [] : preg_split('/\s*,\s*/', $params[$key]);
|
||||
}
|
||||
$args[] = $actionParams[$key] = $params[$key];
|
||||
unset($params[$key]);
|
||||
}
|
||||
} elseif (
|
||||
PHP_VERSION_ID >= 70100
|
||||
&& ($type = $param->getType()) !== null
|
||||
&& $type instanceof \ReflectionNamedType
|
||||
&& !$type->isBuiltin()
|
||||
) {
|
||||
try {
|
||||
$this->bindInjectedParams($type, $name, $args, $requestedParams);
|
||||
} catch (\yii\base\Exception $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
} elseif ($param->isDefaultValueAvailable()) {
|
||||
$args[] = $actionParams[$i] = $param->getDefaultValue();
|
||||
} else {
|
||||
$missing[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($missing)) {
|
||||
throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)]));
|
||||
}
|
||||
|
||||
// We use a different array here, specifically one that doesn't contain service instances but descriptions instead.
|
||||
if (\Yii::$app->requestedParams === null) {
|
||||
\Yii::$app->requestedParams = array_merge($actionParams, $requestedParams);
|
||||
}
|
||||
|
||||
return array_merge($args, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string with ANSI codes.
|
||||
*
|
||||
* You may pass additional parameters using the constants defined in [[\yii\helpers\Console]].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
|
||||
* ```
|
||||
*
|
||||
* @param string $string the string to be formatted
|
||||
* @return string
|
||||
*/
|
||||
public function ansiFormat($string)
|
||||
{
|
||||
if ($this->isColorEnabled()) {
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
$string = Console::ansiFormat($string, $args);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a string to STDOUT.
|
||||
*
|
||||
* You may optionally format the string with ANSI codes by
|
||||
* passing additional parameters using the constants defined in [[\yii\helpers\Console]].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
|
||||
* ```
|
||||
*
|
||||
* @param string $string the string to print
|
||||
* @param int ...$args additional parameters to decorate the output
|
||||
* @return int|bool Number of bytes printed or false on error
|
||||
*/
|
||||
public function stdout($string)
|
||||
{
|
||||
if ($this->isColorEnabled()) {
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
$string = Console::ansiFormat($string, $args);
|
||||
}
|
||||
|
||||
return Console::stdout($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a string to STDERR.
|
||||
*
|
||||
* You may optionally format the string with ANSI codes by
|
||||
* passing additional parameters using the constants defined in [[\yii\helpers\Console]].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
|
||||
* ```
|
||||
*
|
||||
* @param string $string the string to print
|
||||
* @param int ...$args additional parameters to decorate the output
|
||||
* @return int|bool Number of bytes printed or false on error
|
||||
*/
|
||||
public function stderr($string)
|
||||
{
|
||||
if ($this->isColorEnabled(\STDERR)) {
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
$string = Console::ansiFormat($string, $args);
|
||||
}
|
||||
|
||||
return fwrite(\STDERR, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for input and validates it.
|
||||
*
|
||||
* @param string $text prompt string
|
||||
* @param array $options the options to validate the input:
|
||||
*
|
||||
* - required: whether it is required or not
|
||||
* - default: default value if no input is inserted by the user
|
||||
* - pattern: regular expression pattern to validate user input
|
||||
* - validator: a callable function to validate input. The function must accept two parameters:
|
||||
* - $input: the user input to validate
|
||||
* - $error: the error value passed by reference if validation failed.
|
||||
*
|
||||
* An example of how to use the prompt method with a validator function.
|
||||
*
|
||||
* ```
|
||||
* $code = $this->prompt('Enter 4-Chars-Pin', ['required' => true, 'validator' => function($input, &$error) {
|
||||
* if (strlen($input) !== 4) {
|
||||
* $error = 'The Pin must be exactly 4 chars!';
|
||||
* return false;
|
||||
* }
|
||||
* return true;
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* @return string the user input
|
||||
*/
|
||||
public function prompt($text, $options = [])
|
||||
{
|
||||
if ($this->interactive) {
|
||||
return Console::prompt($text, $options);
|
||||
}
|
||||
|
||||
return isset($options['default']) ? $options['default'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks user to confirm by typing y or n.
|
||||
*
|
||||
* A typical usage looks like the following:
|
||||
*
|
||||
* ```
|
||||
* if ($this->confirm("Are you sure?")) {
|
||||
* echo "user typed yes\n";
|
||||
* } else {
|
||||
* echo "user typed no\n";
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param string $message to echo out before waiting for user input
|
||||
* @param bool $default this value is returned if no selection is made.
|
||||
* @return bool whether user confirmed.
|
||||
* Will return true if [[interactive]] is false.
|
||||
*/
|
||||
public function confirm($message, $default = false)
|
||||
{
|
||||
if ($this->interactive) {
|
||||
return Console::confirm($message, $default);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the user an option to choose from. Giving '?' as an input will show
|
||||
* a list of options to choose from and their explanations.
|
||||
*
|
||||
* @param string $prompt the prompt message
|
||||
* @param array $options Key-value array of options to choose from
|
||||
* @param string|null $default value to use when the user doesn't provide an option.
|
||||
* If the default is `null`, the user is required to select an option.
|
||||
*
|
||||
* @return string An option character the user chose
|
||||
* @since 2.0.49 Added the $default argument
|
||||
*/
|
||||
public function select($prompt, $options = [], $default = null)
|
||||
{
|
||||
if ($this->interactive) {
|
||||
return Console::select($prompt, $options, $default);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of valid options for the action (id)
|
||||
* An option requires the existence of a public member variable whose
|
||||
* name is the option name.
|
||||
* Child classes may override this method to specify possible options.
|
||||
*
|
||||
* Note that the values setting via options are not available
|
||||
* until [[beforeAction()]] is being called.
|
||||
*
|
||||
* @param string $actionID the action id of the current request
|
||||
* @return string[] the names of the options valid for the action
|
||||
*/
|
||||
public function options($actionID)
|
||||
{
|
||||
// $actionId might be used in subclasses to provide options specific to action id
|
||||
return ['color', 'interactive', 'help', 'silentExitOnException'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns option alias names.
|
||||
* Child classes may override this method to specify alias options.
|
||||
*
|
||||
* @return array the options alias names valid for the action
|
||||
* where the keys is alias name for option and value is option name.
|
||||
*
|
||||
* @since 2.0.8
|
||||
* @see options()
|
||||
*/
|
||||
public function optionAliases()
|
||||
{
|
||||
return [
|
||||
'h' => 'help',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns properties corresponding to the options for the action id
|
||||
* Child classes may override this method to specify possible properties.
|
||||
*
|
||||
* @param string $actionID the action id of the current request
|
||||
* @return array properties corresponding to the options for the action
|
||||
*/
|
||||
public function getOptionValues($actionID)
|
||||
{
|
||||
// $actionId might be used in subclasses to provide properties specific to action id
|
||||
$properties = [];
|
||||
foreach ($this->options($this->action->id) as $property) {
|
||||
$properties[$property] = $this->$property;
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of valid options passed during execution.
|
||||
*
|
||||
* @return array the names of the options passed during execution
|
||||
*/
|
||||
public function getPassedOptions()
|
||||
{
|
||||
return $this->_passedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties corresponding to the passed options.
|
||||
*
|
||||
* @return array the properties corresponding to the passed options
|
||||
*/
|
||||
public function getPassedOptionValues()
|
||||
{
|
||||
$properties = [];
|
||||
foreach ($this->_passedOptions as $property) {
|
||||
$properties[$property] = $this->$property;
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one-line short summary describing this controller.
|
||||
*
|
||||
* You may override this method to return customized summary.
|
||||
* The default implementation returns first line from the PHPDoc comment.
|
||||
*
|
||||
* @return string the one-line short summary describing this controller.
|
||||
*/
|
||||
public function getHelpSummary()
|
||||
{
|
||||
return $this->parseDocCommentSummary(new \ReflectionClass($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns help information for this controller.
|
||||
*
|
||||
* You may override this method to return customized help.
|
||||
* The default implementation returns help information retrieved from the PHPDoc comment.
|
||||
* @return string the help information for this controller.
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
return $this->parseDocCommentDetail(new \ReflectionClass($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a one-line short summary describing the specified action.
|
||||
* @param Action $action action to get summary for
|
||||
* @return string a one-line short summary describing the specified action.
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*/
|
||||
public function getActionHelpSummary($action)
|
||||
{
|
||||
if ($action === null) {
|
||||
return $this->ansiFormat(Yii::t('yii', 'Action not found.'), Console::FG_RED);
|
||||
}
|
||||
|
||||
return $this->parseDocCommentSummary($this->getActionMethodReflection($action));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detailed help information for the specified action.
|
||||
* @param Action $action action to get help for
|
||||
* @return string the detailed help information for the specified action.
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*/
|
||||
public function getActionHelp($action)
|
||||
{
|
||||
return $this->parseDocCommentDetail($this->getActionMethodReflection($action));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the help information for the anonymous arguments for the action.
|
||||
*
|
||||
* The returned value should be an array. The keys are the argument names, and the values are
|
||||
* the corresponding help information. Each value must be an array of the following structure:
|
||||
*
|
||||
* - required: bool, whether this argument is required
|
||||
* - type: string|null, the PHP type(s) of this argument
|
||||
* - default: mixed, the default value of this argument
|
||||
* - comment: string, the description of this argument
|
||||
*
|
||||
* The default implementation will return the help information extracted from the Reflection or
|
||||
* DocBlock of the parameters corresponding to the action method.
|
||||
*
|
||||
* @param Action $action the action instance
|
||||
* @return array the help information of the action arguments
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*/
|
||||
public function getActionArgsHelp($action)
|
||||
{
|
||||
$method = $this->getActionMethodReflection($action);
|
||||
|
||||
$tags = $this->parseDocCommentTags($method);
|
||||
$tags['param'] = isset($tags['param']) ? (array) $tags['param'] : [];
|
||||
$phpDocParams = [];
|
||||
foreach ($tags['param'] as $i => $tag) {
|
||||
if (preg_match('/^(?<type>\S+)(\s+\$(?<name>\w+))?(?<comment>.*)/us', $tag, $matches) === 1) {
|
||||
$key = empty($matches['name']) ? $i : $matches['name'];
|
||||
$phpDocParams[$key] = ['type' => $matches['type'], 'comment' => $matches['comment']];
|
||||
}
|
||||
}
|
||||
unset($tags);
|
||||
|
||||
$args = [];
|
||||
|
||||
/** @var \ReflectionParameter $parameter */
|
||||
foreach ($method->getParameters() as $i => $parameter) {
|
||||
$type = null;
|
||||
$comment = '';
|
||||
if (PHP_MAJOR_VERSION > 5 && $parameter->hasType()) {
|
||||
$reflectionType = $parameter->getType();
|
||||
if (PHP_VERSION_ID >= 70100) {
|
||||
$types = method_exists($reflectionType, 'getTypes') ? $reflectionType->getTypes() : [$reflectionType];
|
||||
foreach ($types as $key => $reflectionType) {
|
||||
$types[$key] = $reflectionType->getName();
|
||||
}
|
||||
$type = implode('|', $types);
|
||||
} else {
|
||||
$type = (string) $reflectionType;
|
||||
}
|
||||
}
|
||||
// find PhpDoc tag by property name or position
|
||||
$key = isset($phpDocParams[$parameter->name]) ? $parameter->name : (isset($phpDocParams[$i]) ? $i : null);
|
||||
if ($key !== null) {
|
||||
$comment = $phpDocParams[$key]['comment'];
|
||||
if ($type === null && !empty($phpDocParams[$key]['type'])) {
|
||||
$type = $phpDocParams[$key]['type'];
|
||||
}
|
||||
}
|
||||
// if type still not detected, then using type of default value
|
||||
if ($type === null && $parameter->isDefaultValueAvailable() && $parameter->getDefaultValue() !== null) {
|
||||
$type = gettype($parameter->getDefaultValue());
|
||||
}
|
||||
|
||||
$args[$parameter->name] = [
|
||||
'required' => !$parameter->isOptional(),
|
||||
'type' => $type,
|
||||
'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null,
|
||||
'comment' => $comment,
|
||||
];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the help information for the options for the action.
|
||||
*
|
||||
* The returned value should be an array. The keys are the option names, and the values are
|
||||
* the corresponding help information. Each value must be an array of the following structure:
|
||||
*
|
||||
* - type: string, the PHP type of this argument.
|
||||
* - default: string, the default value of this argument
|
||||
* - comment: string, the comment of this argument
|
||||
*
|
||||
* The default implementation will return the help information extracted from the doc-comment of
|
||||
* the properties corresponding to the action options.
|
||||
*
|
||||
* @param Action $action
|
||||
* @return array the help information of the action options
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*/
|
||||
public function getActionOptionsHelp($action)
|
||||
{
|
||||
$optionNames = $this->options($action->id);
|
||||
if (empty($optionNames)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$class = new \ReflectionClass($this);
|
||||
$options = [];
|
||||
foreach ($class->getProperties() as $property) {
|
||||
$name = $property->getName();
|
||||
if (!in_array($name, $optionNames, true)) {
|
||||
continue;
|
||||
}
|
||||
$defaultValue = $property->getValue($this);
|
||||
$tags = $this->parseDocCommentTags($property);
|
||||
|
||||
// Display camelCase options in kebab-case
|
||||
$name = Inflector::camel2id($name, '-', true);
|
||||
|
||||
if (isset($tags['var']) || isset($tags['property'])) {
|
||||
$doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
|
||||
if (is_array($doc)) {
|
||||
$doc = reset($doc);
|
||||
}
|
||||
if (preg_match('/^(\S+)(.*)/s', $doc, $matches)) {
|
||||
$type = $matches[1];
|
||||
$comment = $matches[2];
|
||||
} else {
|
||||
$type = null;
|
||||
$comment = $doc;
|
||||
}
|
||||
$options[$name] = [
|
||||
'type' => $type,
|
||||
'default' => $defaultValue,
|
||||
'comment' => $comment,
|
||||
];
|
||||
} else {
|
||||
$options[$name] = [
|
||||
'type' => null,
|
||||
'default' => $defaultValue,
|
||||
'comment' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private $_reflections = [];
|
||||
|
||||
/**
|
||||
* @param Action $action
|
||||
* @return \ReflectionFunctionAbstract
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*/
|
||||
protected function getActionMethodReflection($action)
|
||||
{
|
||||
if (!isset($this->_reflections[$action->id])) {
|
||||
if ($action instanceof InlineAction) {
|
||||
$this->_reflections[$action->id] = new \ReflectionMethod($this, $action->actionMethod);
|
||||
} else {
|
||||
$this->_reflections[$action->id] = new \ReflectionMethod($action, 'run');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_reflections[$action->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the comment block into tags.
|
||||
* @param \ReflectionClass|\ReflectionProperty|\ReflectionFunctionAbstract $reflection the comment block
|
||||
* @return array the parsed tags
|
||||
*
|
||||
* @phpstan-param \ReflectionClass<object>|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
* @psalm-param \ReflectionClass<object>|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
protected function parseDocCommentTags($reflection)
|
||||
{
|
||||
$comment = $reflection->getDocComment();
|
||||
$comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**([ \t])?/m', '', trim($comment, '/'))), "\r", '');
|
||||
$parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$tags = [];
|
||||
foreach ($parts as $part) {
|
||||
if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
|
||||
$name = $matches[1];
|
||||
if (!isset($tags[$name])) {
|
||||
$tags[$name] = trim($matches[2]);
|
||||
} elseif (is_array($tags[$name])) {
|
||||
$tags[$name][] = trim($matches[2]);
|
||||
} else {
|
||||
$tags[$name] = [$tags[$name], trim($matches[2])];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first line of docblock.
|
||||
*
|
||||
* @param \ReflectionClass|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
* @return string
|
||||
*
|
||||
* @phpstan-param \ReflectionClass<$this>|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
* @psalm-param \ReflectionClass<$this>|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
protected function parseDocCommentSummary($reflection)
|
||||
{
|
||||
$docLines = preg_split('~\R~u', $reflection->getDocComment());
|
||||
if (isset($docLines[1])) {
|
||||
return trim($docLines[1], "\t *");
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns full description from the docblock.
|
||||
*
|
||||
* @param \ReflectionClass|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
* @return string
|
||||
*
|
||||
* @phpstan-param \ReflectionClass<$this>|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
* @psalm-param \ReflectionClass<$this>|\ReflectionProperty|\ReflectionFunctionAbstract $reflection
|
||||
*/
|
||||
protected function parseDocCommentDetail($reflection)
|
||||
{
|
||||
$comment = strtr(trim(preg_replace('/^\s*\**([ \t])?/m', '', trim($reflection->getDocComment(), '/'))), "\r", '');
|
||||
if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$comment = trim(substr($comment, 0, $matches[0][1]));
|
||||
}
|
||||
if ($comment !== '') {
|
||||
return rtrim(Console::renderColoredString(Console::markdownToAnsi($comment)));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
102
vendor/yiisoft/yii2/console/ErrorHandler.php
vendored
Normal file
102
vendor/yiisoft/yii2/console/ErrorHandler.php
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
use yii\base\UserException;
|
||||
use yii\helpers\Console;
|
||||
|
||||
/**
|
||||
* ErrorHandler handles uncaught PHP errors and exceptions.
|
||||
*
|
||||
* ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
|
||||
* You can access that instance via `Yii::$app->errorHandler`.
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
* @since 2.0
|
||||
*/
|
||||
class ErrorHandler extends \yii\base\ErrorHandler
|
||||
{
|
||||
/**
|
||||
* Renders an exception using ansi format for console output.
|
||||
* @param \Throwable $exception the exception to be rendered.
|
||||
*/
|
||||
protected function renderException($exception)
|
||||
{
|
||||
$previous = $exception->getPrevious();
|
||||
if ($exception instanceof UnknownCommandException) {
|
||||
// display message and suggest alternatives in case of unknown command
|
||||
$message = $this->formatMessage($exception->getName() . ': ') . $exception->command;
|
||||
$alternatives = $exception->getSuggestedAlternatives();
|
||||
if (count($alternatives) === 1) {
|
||||
$message .= "\n\nDid you mean \"" . reset($alternatives) . '"?';
|
||||
} elseif (count($alternatives) > 1) {
|
||||
$message .= "\n\nDid you mean one of these?\n - " . implode("\n - ", $alternatives);
|
||||
}
|
||||
} elseif ($exception instanceof UserException && ($exception instanceof Exception || !YII_DEBUG)) {
|
||||
$message = $this->formatMessage($exception->getName() . ': ') . $exception->getMessage();
|
||||
} elseif (YII_DEBUG) {
|
||||
if ($exception instanceof Exception) {
|
||||
$message = $this->formatMessage("Exception ({$exception->getName()})");
|
||||
} elseif ($exception instanceof ErrorException) {
|
||||
$message = $this->formatMessage($exception->getName());
|
||||
} else {
|
||||
$message = $this->formatMessage('Exception');
|
||||
}
|
||||
$message .= $this->formatMessage(" '" . get_class($exception) . "'", [Console::BOLD, Console::FG_BLUE])
|
||||
. ' with message ' . $this->formatMessage("'{$exception->getMessage()}'", [Console::BOLD]) //. "\n"
|
||||
. "\n\nin " . dirname($exception->getFile()) . DIRECTORY_SEPARATOR . $this->formatMessage(basename($exception->getFile()), [Console::BOLD])
|
||||
. ':' . $this->formatMessage($exception->getLine(), [Console::BOLD, Console::FG_YELLOW]) . "\n";
|
||||
if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) {
|
||||
$message .= "\n" . $this->formatMessage("Error Info:\n", [Console::BOLD]) . print_r($exception->errorInfo, true);
|
||||
}
|
||||
if ($previous === null) {
|
||||
$message .= "\n" . $this->formatMessage("Stack trace:\n", [Console::BOLD]) . $exception->getTraceAsString();
|
||||
}
|
||||
} else {
|
||||
$message = $this->formatMessage('Error: ') . $exception->getMessage();
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
Console::stderr($message . "\n");
|
||||
} else {
|
||||
echo $message . "\n";
|
||||
}
|
||||
if (YII_DEBUG && $previous !== null) {
|
||||
$causedBy = $this->formatMessage('Caused by: ', [Console::BOLD]);
|
||||
if (PHP_SAPI === 'cli') {
|
||||
Console::stderr($causedBy);
|
||||
} else {
|
||||
echo $causedBy;
|
||||
}
|
||||
$this->renderException($previous);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorizes a message for console output.
|
||||
* @param string $message the message to colorize.
|
||||
* @param array $format the message format.
|
||||
* @return string the colorized message.
|
||||
* @see Console::ansiFormat() for details on how to specify the message format.
|
||||
*/
|
||||
protected function formatMessage($message, $format = [Console::FG_RED, Console::BOLD])
|
||||
{
|
||||
$stream = (PHP_SAPI === 'cli') ? \STDERR : \STDOUT;
|
||||
// try controller first to allow check for --color switch
|
||||
if (
|
||||
Yii::$app->controller instanceof \yii\console\Controller && Yii::$app->controller->isColorEnabled($stream)
|
||||
|| Yii::$app instanceof \yii\console\Application && Console::streamSupportsAnsiColors($stream)
|
||||
) {
|
||||
$message = Console::ansiFormat($message, $format);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
27
vendor/yiisoft/yii2/console/Exception.php
vendored
Normal file
27
vendor/yiisoft/yii2/console/Exception.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
use yii\base\UserException;
|
||||
|
||||
/**
|
||||
* Exception represents an exception caused by incorrect usage of a console command.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Exception extends UserException
|
||||
{
|
||||
/**
|
||||
* @return string the user-friendly name of this exception
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Error';
|
||||
}
|
||||
}
|
||||
159
vendor/yiisoft/yii2/console/ExitCode.php
vendored
Normal file
159
vendor/yiisoft/yii2/console/ExitCode.php
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
/**
|
||||
* This class provides constants for defining console command exit codes.
|
||||
*
|
||||
* The exit codes follow the codes defined in the [FreeBSD sysexits(3)](https://man.openbsd.org/sysexits) manual page.
|
||||
*
|
||||
* These constants can be used in console controllers for example like this:
|
||||
*
|
||||
* ```
|
||||
* public function actionIndex()
|
||||
* {
|
||||
* if (!$this->isAllowedToPerformAction()) {
|
||||
* $this->stderr('Error: ' . ExitCode::getReason(ExitCode::NOPERM));
|
||||
* return ExitCode::NOPERM;
|
||||
* }
|
||||
*
|
||||
* // do something
|
||||
*
|
||||
* return ExitCode::OK;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @author Tom Worster <fsb@thefsb.org>
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
* @see https://man.openbsd.org/sysexits
|
||||
* @since 2.0.13
|
||||
*/
|
||||
class ExitCode
|
||||
{
|
||||
/**
|
||||
* The command completed successfully.
|
||||
*/
|
||||
public const OK = 0;
|
||||
/**
|
||||
* The command exited with an error code that says nothing about the error.
|
||||
*/
|
||||
public const UNSPECIFIED_ERROR = 1;
|
||||
/**
|
||||
* The command was used incorrectly, e.g., with the wrong number of
|
||||
* arguments, a bad flag, a bad syntax in a parameter, or whatever.
|
||||
*/
|
||||
public const USAGE = 64;
|
||||
/**
|
||||
* The input data was incorrect in some way. This should only be used for
|
||||
* user's data and not system files.
|
||||
*/
|
||||
public const DATAERR = 65;
|
||||
/**
|
||||
* An input file (not a system file) did not exist or was not readable.
|
||||
* This could also include errors like ``No message'' to a mailer (if it
|
||||
* cared to catch it).
|
||||
*/
|
||||
public const NOINPUT = 66;
|
||||
/**
|
||||
* The user specified did not exist. This might be used for mail addresses
|
||||
* or remote logins.
|
||||
*/
|
||||
public const NOUSER = 67;
|
||||
/**
|
||||
* The host specified did not exist. This is used in mail addresses or
|
||||
* network requests.
|
||||
*/
|
||||
public const NOHOST = 68;
|
||||
/**
|
||||
* A service is unavailable. This can occur if a support program or file
|
||||
* does not exist. This can also be used as a catchall message when
|
||||
* something you wanted to do does not work, but you do not know why.
|
||||
*/
|
||||
public const UNAVAILABLE = 69;
|
||||
/**
|
||||
* An internal software error has been detected. This should be limited to
|
||||
* non-operating system related errors as possible.
|
||||
*/
|
||||
public const SOFTWARE = 70;
|
||||
/**
|
||||
* An operating system error has been detected. This is intended to be
|
||||
* used for such things as ``cannot fork'', ``cannot create pipe'', or the
|
||||
* like. It includes things like getuid returning a user that does not
|
||||
* exist in the passwd file.
|
||||
*/
|
||||
public const OSERR = 71;
|
||||
/**
|
||||
* Some system file (e.g., /etc/passwd, /var/run/utx.active, etc.) does not
|
||||
* exist, cannot be opened, or has some sort of error (e.g., syntax error).
|
||||
*/
|
||||
public const OSFILE = 72;
|
||||
/**
|
||||
* A (user specified) output file cannot be created.
|
||||
*/
|
||||
public const CANTCREAT = 73;
|
||||
/**
|
||||
* An error occurred while doing I/O on some file.
|
||||
*/
|
||||
public const IOERR = 74;
|
||||
/**
|
||||
* Temporary failure, indicating something that is not really an error. In
|
||||
* sendmail, this means that a mailer (e.g.) could not create a connection,
|
||||
* and the request should be reattempted later.
|
||||
*/
|
||||
public const TEMPFAIL = 75;
|
||||
/**
|
||||
* The remote system returned something that was ``not possible'' during a
|
||||
* protocol exchange.
|
||||
*/
|
||||
public const PROTOCOL = 76;
|
||||
/**
|
||||
* You did not have sufficient permission to perform the operation. This
|
||||
* is not intended for file system problems, which should use NOINPUT or
|
||||
* CANTCREAT, but rather for higher level permissions.
|
||||
*/
|
||||
public const NOPERM = 77;
|
||||
/**
|
||||
* Something was found in an unconfigured or misconfigured state.
|
||||
*/
|
||||
public const CONFIG = 78;
|
||||
/**
|
||||
* @var array a map of reason descriptions for exit codes.
|
||||
*/
|
||||
public static $reasons = [
|
||||
self::OK => 'Success',
|
||||
self::UNSPECIFIED_ERROR => 'Unspecified error',
|
||||
self::USAGE => 'Incorrect usage, argument or option error',
|
||||
self::DATAERR => 'Error in input data',
|
||||
self::NOINPUT => 'Input file not found or unreadable',
|
||||
self::NOUSER => 'User not found',
|
||||
self::NOHOST => 'Host not found',
|
||||
self::UNAVAILABLE => 'A required service is unavailable',
|
||||
self::SOFTWARE => 'Internal error',
|
||||
self::OSERR => 'Error making system call or using OS service',
|
||||
self::OSFILE => 'Error accessing system file',
|
||||
self::CANTCREAT => 'Cannot create output file',
|
||||
self::IOERR => 'I/O error',
|
||||
self::TEMPFAIL => 'Temporary failure',
|
||||
self::PROTOCOL => 'Unexpected remote service behavior',
|
||||
self::NOPERM => 'Insufficient permissions',
|
||||
self::CONFIG => 'Configuration error',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Returns a short reason text for the given exit code.
|
||||
*
|
||||
* This method uses [[$reasons]] to determine the reason for an exit code.
|
||||
* @param int $exitCode one of the constants defined in this class.
|
||||
* @return string the reason text, or `"Unknown exit code"` if the code is not listed in [[$reasons]].
|
||||
*/
|
||||
public static function getReason($exitCode)
|
||||
{
|
||||
return isset(static::$reasons[$exitCode]) ? static::$reasons[$exitCode] : 'Unknown exit code';
|
||||
}
|
||||
}
|
||||
106
vendor/yiisoft/yii2/console/Markdown.php
vendored
Normal file
106
vendor/yiisoft/yii2/console/Markdown.php
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
use cebe\markdown\block\FencedCodeTrait;
|
||||
use cebe\markdown\inline\CodeTrait;
|
||||
use cebe\markdown\inline\EmphStrongTrait;
|
||||
use cebe\markdown\inline\StrikeoutTrait;
|
||||
use yii\helpers\Console;
|
||||
|
||||
/**
|
||||
* A Markdown parser that enhances markdown for reading in console environments.
|
||||
*
|
||||
* Based on [cebe/markdown](https://github.com/cebe/markdown).
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Markdown extends \cebe\markdown\Parser
|
||||
{
|
||||
use FencedCodeTrait;
|
||||
use CodeTrait;
|
||||
use EmphStrongTrait;
|
||||
use StrikeoutTrait;
|
||||
|
||||
/**
|
||||
* @var array these are "escapeable" characters. When using one of these prefixed with a
|
||||
* backslash, the character will be outputted without the backslash and is not interpreted
|
||||
* as markdown.
|
||||
*/
|
||||
protected $escapeCharacters = [
|
||||
'\\', // backslash
|
||||
'`', // backtick
|
||||
'*', // asterisk
|
||||
'_', // underscore
|
||||
'~', // tilde
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Renders a code block.
|
||||
*
|
||||
* @param array $block
|
||||
* @return string
|
||||
*/
|
||||
protected function renderCode($block)
|
||||
{
|
||||
return Console::ansiFormat($block['content'], [Console::NEGATIVE]) . "\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a paragraph block.
|
||||
*
|
||||
* @param array $block
|
||||
* @return string
|
||||
*/
|
||||
protected function renderParagraph($block)
|
||||
{
|
||||
return rtrim($this->renderAbsy($block['content'])) . "\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an inline code span `` ` ``.
|
||||
* @param array $element
|
||||
* @return string
|
||||
*/
|
||||
protected function renderInlineCode($element)
|
||||
{
|
||||
return Console::ansiFormat($element[1], [Console::UNDERLINE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders empathized elements.
|
||||
* @param array $element
|
||||
* @return string
|
||||
*/
|
||||
protected function renderEmph($element)
|
||||
{
|
||||
return Console::ansiFormat($this->renderAbsy($element[1]), [Console::ITALIC]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders strong elements.
|
||||
* @param array $element
|
||||
* @return string
|
||||
*/
|
||||
protected function renderStrong($element)
|
||||
{
|
||||
return Console::ansiFormat($this->renderAbsy($element[1]), [Console::BOLD]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the strike through feature.
|
||||
* @param array $element
|
||||
* @return string
|
||||
*/
|
||||
protected function renderStrike($element)
|
||||
{
|
||||
return Console::ansiFormat($this->parseInline($this->renderAbsy($element[1])), [Console::CROSSED_OUT]);
|
||||
}
|
||||
}
|
||||
108
vendor/yiisoft/yii2/console/Request.php
vendored
Normal file
108
vendor/yiisoft/yii2/console/Request.php
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
/**
|
||||
* The console Request represents the environment information for a console application.
|
||||
*
|
||||
* It is a wrapper for the PHP `$_SERVER` variable which holds information about the
|
||||
* currently running PHP script and the command line arguments given to it.
|
||||
*
|
||||
* @property array $params The command line arguments. It does not include the entry script name.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Request extends \yii\base\Request
|
||||
{
|
||||
private $_params;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the command line arguments.
|
||||
* @return array the command line arguments. It does not include the entry script name.
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
if ($this->_params === null) {
|
||||
if (isset($_SERVER['argv'])) {
|
||||
$this->_params = $_SERVER['argv'];
|
||||
array_shift($this->_params);
|
||||
} else {
|
||||
$this->_params = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the command line arguments.
|
||||
* @param array $params the command line arguments
|
||||
*/
|
||||
public function setParams($params)
|
||||
{
|
||||
$this->_params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current request into a route and the associated parameters.
|
||||
* @return array the first element is the route, and the second is the associated parameters.
|
||||
* @throws Exception when parameter is wrong and can not be resolved
|
||||
*/
|
||||
public function resolve()
|
||||
{
|
||||
$rawParams = $this->getParams();
|
||||
$endOfOptionsFound = false;
|
||||
if (isset($rawParams[0])) {
|
||||
$route = array_shift($rawParams);
|
||||
|
||||
if ($route === '--') {
|
||||
$endOfOptionsFound = true;
|
||||
$route = array_shift($rawParams);
|
||||
}
|
||||
} else {
|
||||
$route = '';
|
||||
}
|
||||
|
||||
$params = [];
|
||||
$prevOption = null;
|
||||
foreach ($rawParams as $param) {
|
||||
if ($endOfOptionsFound) {
|
||||
$params[] = $param;
|
||||
} elseif ($param === '--') {
|
||||
$endOfOptionsFound = true;
|
||||
} elseif (preg_match('/^--([\w-]+)(?:=(.*))?$/', $param, $matches)) {
|
||||
$name = $matches[1];
|
||||
if (is_numeric(substr($name, 0, 1))) {
|
||||
throw new Exception('Parameter "' . $name . '" is not valid');
|
||||
}
|
||||
|
||||
if ($name !== Application::OPTION_APPCONFIG) {
|
||||
$params[$name] = isset($matches[2]) ? $matches[2] : true;
|
||||
$prevOption = &$params[$name];
|
||||
}
|
||||
} elseif (preg_match('/^-([\w-]+)(?:=(.*))?$/', $param, $matches)) {
|
||||
$name = $matches[1];
|
||||
if (is_numeric($name)) {
|
||||
$params[] = $param;
|
||||
} else {
|
||||
$params['_aliases'][$name] = isset($matches[2]) ? $matches[2] : true;
|
||||
$prevOption = &$params['_aliases'][$name];
|
||||
}
|
||||
} elseif ($prevOption === true) {
|
||||
// `--option value` syntax
|
||||
$prevOption = $param;
|
||||
} else {
|
||||
$params[] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
return [$route, $params];
|
||||
}
|
||||
}
|
||||
18
vendor/yiisoft/yii2/console/Response.php
vendored
Normal file
18
vendor/yiisoft/yii2/console/Response.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
/**
|
||||
* The console Response represents the result of a console application.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Response extends \yii\base\Response
|
||||
{
|
||||
}
|
||||
145
vendor/yiisoft/yii2/console/UnknownCommandException.php
vendored
Normal file
145
vendor/yiisoft/yii2/console/UnknownCommandException.php
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console;
|
||||
|
||||
use yii\console\controllers\HelpController;
|
||||
|
||||
/**
|
||||
* UnknownCommandException represents an exception caused by incorrect usage of a console command.
|
||||
*
|
||||
* @author Carsten Brandt <mail@cebe.cc>
|
||||
* @since 2.0.11
|
||||
*/
|
||||
class UnknownCommandException extends Exception
|
||||
{
|
||||
/**
|
||||
* @var string the name of the command that could not be recognized.
|
||||
*/
|
||||
public $command;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $application;
|
||||
|
||||
|
||||
/**
|
||||
* Construct the exception.
|
||||
*
|
||||
* @param string $route the route of the command that could not be found.
|
||||
* @param Application $application the console application instance involved.
|
||||
* @param int $code the Exception code.
|
||||
* @param \Throwable|null $previous the previous exception used for the exception chaining.
|
||||
*/
|
||||
public function __construct($route, $application, $code = 0, $previous = null)
|
||||
{
|
||||
$this->command = $route;
|
||||
$this->application = $application;
|
||||
parent::__construct("Unknown command \"$route\".", $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the user-friendly name of this exception
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Unknown command';
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggest alternative commands for [[$command]] based on string similarity.
|
||||
*
|
||||
* Alternatives are searched using the following steps:
|
||||
*
|
||||
* - suggest alternatives that begin with `$command`
|
||||
* - find typos by calculating the Levenshtein distance between the unknown command and all
|
||||
* available commands. The Levenshtein distance is defined as the minimal number of
|
||||
* characters you have to replace, insert or delete to transform str1 into str2.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.levenshtein.php
|
||||
* @return array a list of suggested alternatives sorted by similarity.
|
||||
*/
|
||||
public function getSuggestedAlternatives()
|
||||
{
|
||||
$help = $this->application->createController('help');
|
||||
if ($help === false || $this->command === '') {
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* @var HelpController $helpController
|
||||
* @phpstan-var HelpController<Application> $helpController
|
||||
*/
|
||||
list($helpController, $actionID) = $help;
|
||||
|
||||
$availableActions = [];
|
||||
foreach ($helpController->getCommands() as $command) {
|
||||
$result = $this->application->createController($command);
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
if ($controller->createAction($controller->defaultAction) !== null) {
|
||||
// add the command itself (default action)
|
||||
$availableActions[] = $command;
|
||||
}
|
||||
|
||||
// add all actions of this controller
|
||||
$actions = $helpController->getActions($controller);
|
||||
$prefix = $controller->getUniqueId();
|
||||
foreach ($actions as $action) {
|
||||
$availableActions[] = $prefix . '/' . $action;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->filterBySimilarity($availableActions, $this->command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find suggest alternative commands based on string similarity.
|
||||
*
|
||||
* Alternatives are searched using the following steps:
|
||||
*
|
||||
* - suggest alternatives that begin with `$command`
|
||||
* - find typos by calculating the Levenshtein distance between the unknown command and all
|
||||
* available commands. The Levenshtein distance is defined as the minimal number of
|
||||
* characters you have to replace, insert or delete to transform str1 into str2.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.levenshtein.php
|
||||
* @param array $actions available command names.
|
||||
* @param string $command the command to compare to.
|
||||
* @return array a list of suggested alternatives sorted by similarity.
|
||||
*/
|
||||
private function filterBySimilarity($actions, $command)
|
||||
{
|
||||
$alternatives = [];
|
||||
|
||||
// suggest alternatives that begin with $command first
|
||||
foreach ($actions as $action) {
|
||||
if (strpos($action, $command) === 0) {
|
||||
$alternatives[] = $action;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the Levenshtein distance between the unknown command and all available commands.
|
||||
$distances = array_map(function ($action) use ($command) {
|
||||
$action = strlen($action) > 255 ? substr($action, 0, 255) : $action;
|
||||
$command = strlen($command) > 255 ? substr($command, 0, 255) : $command;
|
||||
return levenshtein($action, $command);
|
||||
}, array_combine($actions, $actions));
|
||||
|
||||
// we assume a typo if the levensthein distance is no more than 3, i.e. 3 replacements needed
|
||||
$relevantTypos = array_filter($distances, function ($distance) {
|
||||
return $distance <= 3;
|
||||
});
|
||||
asort($relevantTypos);
|
||||
$alternatives = array_merge($alternatives, array_flip($relevantTypos));
|
||||
|
||||
return array_unique($alternatives);
|
||||
}
|
||||
}
|
||||
845
vendor/yiisoft/yii2/console/controllers/AssetController.php
vendored
Normal file
845
vendor/yiisoft/yii2/console/controllers/AssetController.php
vendored
Normal file
@ -0,0 +1,845 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\console\Application;
|
||||
use yii\console\Controller;
|
||||
use yii\console\Exception;
|
||||
use yii\console\ExitCode;
|
||||
use yii\helpers\Console;
|
||||
use yii\helpers\FileHelper;
|
||||
use yii\helpers\VarDumper;
|
||||
use yii\web\AssetBundle;
|
||||
|
||||
/**
|
||||
* Allows you to combine and compress your JavaScript and CSS files.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* 1. Create a configuration file using the `template` action:
|
||||
*
|
||||
* yii asset/template /path/to/myapp/config.php
|
||||
*
|
||||
* 2. Edit the created config file, adjusting it for your web application needs.
|
||||
* 3. Run the 'compress' action, using created config:
|
||||
*
|
||||
* yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php
|
||||
*
|
||||
* 4. Adjust your web application config to use compressed assets.
|
||||
*
|
||||
* Note: in the console environment some [path aliases](guide:concept-aliases) like `@webroot` and `@web` may not exist,
|
||||
* so corresponding paths inside the configuration should be specified directly.
|
||||
*
|
||||
* Note: by default this command relies on an external tools to perform actual files compression,
|
||||
* check [[jsCompressor]] and [[cssCompressor]] for more details.
|
||||
*
|
||||
* @property \yii\web\AssetManager $assetManager Asset manager instance. Note that the type of this property
|
||||
* differs in getter and setter. See [[getAssetManager()]] and [[setAssetManager()]] for details.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @author Paul Klimov <klimov.paul@gmail.com>
|
||||
* @since 2.0
|
||||
*
|
||||
* @template T of Application
|
||||
* @extends Controller<T>
|
||||
*/
|
||||
class AssetController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var string controller default action ID.
|
||||
*/
|
||||
public $defaultAction = 'compress';
|
||||
/**
|
||||
* @var array list of asset bundles to be compressed.
|
||||
*/
|
||||
public $bundles = [];
|
||||
/**
|
||||
* @var array list of asset bundles, which represents output compressed files.
|
||||
* You can specify the name of the output compressed file using 'css' and 'js' keys:
|
||||
* For example:
|
||||
*
|
||||
* ```
|
||||
* 'app\config\AllAsset' => [
|
||||
* 'js' => 'js/all-{hash}.js',
|
||||
* 'css' => 'css/all-{hash}.css',
|
||||
* 'depends' => [ ... ],
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* File names can contain placeholder "{hash}", which will be filled by the hash of the resulting file.
|
||||
*
|
||||
* You may specify several target bundles in order to compress different groups of assets.
|
||||
* In this case you should use 'depends' key to specify, which bundles should be covered with particular
|
||||
* target bundle. You may leave 'depends' to be empty for single bundle, which will compress all remaining
|
||||
* bundles in this case.
|
||||
* For example:
|
||||
*
|
||||
* ```
|
||||
* 'allShared' => [
|
||||
* 'js' => 'js/all-shared-{hash}.js',
|
||||
* 'css' => 'css/all-shared-{hash}.css',
|
||||
* 'depends' => [
|
||||
* // Include all assets shared between 'backend' and 'frontend'
|
||||
* 'yii\web\YiiAsset',
|
||||
* 'app\assets\SharedAsset',
|
||||
* ],
|
||||
* ],
|
||||
* 'allBackEnd' => [
|
||||
* 'js' => 'js/all-{hash}.js',
|
||||
* 'css' => 'css/all-{hash}.css',
|
||||
* 'depends' => [
|
||||
* // Include only 'backend' assets:
|
||||
* 'app\assets\AdminAsset'
|
||||
* ],
|
||||
* ],
|
||||
* 'allFrontEnd' => [
|
||||
* 'js' => 'js/all-{hash}.js',
|
||||
* 'css' => 'css/all-{hash}.css',
|
||||
* 'depends' => [], // Include all remaining assets
|
||||
* ],
|
||||
* ```
|
||||
*/
|
||||
public $targets = [];
|
||||
/**
|
||||
* @var string|callable JavaScript file compressor.
|
||||
* If a string, it is treated as shell command template, which should contain
|
||||
* placeholders {from} - source file name - and {to} - output file name.
|
||||
* Otherwise, it is treated as PHP callback, which should perform the compression.
|
||||
*
|
||||
* Default value relies on usage of "Closure Compiler"
|
||||
* @see https://developers.google.com/closure/compiler/
|
||||
*/
|
||||
public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}';
|
||||
/**
|
||||
* @var string|callable CSS file compressor.
|
||||
* If a string, it is treated as shell command template, which should contain
|
||||
* placeholders {from} - source file name - and {to} - output file name.
|
||||
* Otherwise, it is treated as PHP callback, which should perform the compression.
|
||||
*
|
||||
* Default value relies on usage of "YUI Compressor"
|
||||
* @see https://github.com/yui/yuicompressor/
|
||||
*/
|
||||
public $cssCompressor = 'java -jar yuicompressor.jar --type css {from} -o {to}';
|
||||
/**
|
||||
* @var bool whether to delete asset source files after compression.
|
||||
* This option affects only those bundles, which have [[\yii\web\AssetBundle::sourcePath]] is set.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public $deleteSource = false;
|
||||
|
||||
/**
|
||||
* @var array|\yii\web\AssetManager [[\yii\web\AssetManager]] instance or its array configuration, which will be used
|
||||
* for assets processing.
|
||||
*/
|
||||
private $_assetManager = [];
|
||||
|
||||
|
||||
/**
|
||||
* Returns the asset manager instance.
|
||||
* @throws \yii\console\Exception on invalid configuration.
|
||||
* @return \yii\web\AssetManager asset manager instance.
|
||||
*/
|
||||
public function getAssetManager()
|
||||
{
|
||||
if (!is_object($this->_assetManager)) {
|
||||
$options = $this->_assetManager;
|
||||
if (!isset($options['class'])) {
|
||||
$options['class'] = 'yii\\web\\AssetManager';
|
||||
}
|
||||
if (!isset($options['basePath'])) {
|
||||
throw new Exception("Please specify 'basePath' for the 'assetManager' option.");
|
||||
}
|
||||
if (!isset($options['baseUrl'])) {
|
||||
throw new Exception("Please specify 'baseUrl' for the 'assetManager' option.");
|
||||
}
|
||||
|
||||
if (!isset($options['forceCopy'])) {
|
||||
$options['forceCopy'] = true;
|
||||
}
|
||||
|
||||
$this->_assetManager = Yii::createObject($options);
|
||||
}
|
||||
|
||||
return $this->_assetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets asset manager instance or configuration.
|
||||
* @param \yii\web\AssetManager|array $assetManager asset manager instance or its array configuration.
|
||||
* @throws \yii\console\Exception on invalid argument type.
|
||||
*/
|
||||
public function setAssetManager($assetManager)
|
||||
{
|
||||
if (is_scalar($assetManager)) {
|
||||
throw new Exception('"' . get_class($this) . '::assetManager" should be either object or array - "' . gettype($assetManager) . '" given.');
|
||||
}
|
||||
$this->_assetManager = $assetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and compresses the asset files according to the given configuration.
|
||||
* During the process new asset bundle configuration file will be created.
|
||||
* You should replace your original asset bundle configuration with this file in order to use compressed files.
|
||||
* @param string $configFile configuration file name.
|
||||
* @param string $bundleFile output asset bundles configuration file name.
|
||||
*/
|
||||
public function actionCompress($configFile, $bundleFile)
|
||||
{
|
||||
$this->loadConfiguration($configFile);
|
||||
$bundles = $this->loadBundles($this->bundles);
|
||||
$targets = $this->loadTargets($this->targets, $bundles);
|
||||
foreach ($targets as $name => $target) {
|
||||
$this->stdout("Creating output bundle '{$name}':\n");
|
||||
if (!empty($target->js)) {
|
||||
$this->buildTarget($target, 'js', $bundles);
|
||||
}
|
||||
if (!empty($target->css)) {
|
||||
$this->buildTarget($target, 'css', $bundles);
|
||||
}
|
||||
$this->stdout("\n");
|
||||
}
|
||||
|
||||
$targets = $this->adjustDependency($targets, $bundles);
|
||||
$this->saveTargets($targets, $bundleFile);
|
||||
|
||||
if ($this->deleteSource) {
|
||||
$this->deletePublishedAssets($bundles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies configuration from the given file to self instance.
|
||||
* @param string $configFile configuration file name.
|
||||
* @throws \yii\console\Exception on failure.
|
||||
*/
|
||||
protected function loadConfiguration($configFile)
|
||||
{
|
||||
$this->stdout("Loading configuration from '{$configFile}'...\n");
|
||||
$config = require $configFile;
|
||||
foreach ($config as $name => $value) {
|
||||
if (property_exists($this, $name) || $this->canSetProperty($name)) {
|
||||
$this->$name = $value;
|
||||
} else {
|
||||
throw new Exception("Unknown configuration option: $name");
|
||||
}
|
||||
}
|
||||
|
||||
$this->getAssetManager(); // check if asset manager configuration is correct
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates full list of source asset bundles.
|
||||
* @param string[] $bundles list of asset bundle names
|
||||
* @return \yii\web\AssetBundle[] list of source asset bundles.
|
||||
*/
|
||||
protected function loadBundles($bundles)
|
||||
{
|
||||
$this->stdout("Collecting source bundles information...\n");
|
||||
|
||||
$am = $this->getAssetManager();
|
||||
$result = [];
|
||||
foreach ($bundles as $name) {
|
||||
$result[$name] = $am->getBundle($name);
|
||||
}
|
||||
foreach ($result as $bundle) {
|
||||
$this->loadDependency($bundle, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads asset bundle dependencies recursively.
|
||||
* @param \yii\web\AssetBundle $bundle bundle instance
|
||||
* @param array $result already loaded bundles list.
|
||||
* @throws Exception on failure.
|
||||
*/
|
||||
protected function loadDependency($bundle, &$result)
|
||||
{
|
||||
$am = $this->getAssetManager();
|
||||
foreach ($bundle->depends as $name) {
|
||||
if (!isset($result[$name])) {
|
||||
$dependencyBundle = $am->getBundle($name);
|
||||
$result[$name] = false;
|
||||
$this->loadDependency($dependencyBundle, $result);
|
||||
$result[$name] = $dependencyBundle;
|
||||
} elseif ($result[$name] === false) {
|
||||
throw new Exception("A circular dependency is detected for bundle '{$name}': " . $this->composeCircularDependencyTrace($name, $result) . '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates full list of output asset bundles.
|
||||
* @param array $targets output asset bundles configuration.
|
||||
* @param \yii\web\AssetBundle[] $bundles list of source asset bundles.
|
||||
* @return \yii\web\AssetBundle[] list of output asset bundles.
|
||||
* @throws Exception on failure.
|
||||
*/
|
||||
protected function loadTargets($targets, $bundles)
|
||||
{
|
||||
// build the dependency order of bundles
|
||||
$registered = [];
|
||||
foreach ($bundles as $name => $bundle) {
|
||||
$this->registerBundle($bundles, $name, $registered);
|
||||
}
|
||||
$bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1));
|
||||
|
||||
// fill up the target which has empty 'depends'.
|
||||
$referenced = [];
|
||||
foreach ($targets as $name => $target) {
|
||||
if (empty($target['depends'])) {
|
||||
if (!isset($all)) {
|
||||
$all = $name;
|
||||
} else {
|
||||
throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name");
|
||||
}
|
||||
} else {
|
||||
foreach ($target['depends'] as $bundle) {
|
||||
if (!isset($referenced[$bundle])) {
|
||||
$referenced[$bundle] = $name;
|
||||
} else {
|
||||
throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($all)) {
|
||||
$targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced));
|
||||
}
|
||||
|
||||
// adjust the 'depends' order for each target according to the dependency order of bundles
|
||||
// create an AssetBundle object for each target
|
||||
foreach ($targets as $name => $target) {
|
||||
if (!isset($target['basePath'])) {
|
||||
throw new Exception("Please specify 'basePath' for the '$name' target.");
|
||||
}
|
||||
if (!isset($target['baseUrl'])) {
|
||||
throw new Exception("Please specify 'baseUrl' for the '$name' target.");
|
||||
}
|
||||
usort($target['depends'], function ($a, $b) use ($bundleOrders) {
|
||||
if ($bundleOrders[$a] == $bundleOrders[$b]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1;
|
||||
});
|
||||
if (!isset($target['class'])) {
|
||||
$target['class'] = $name;
|
||||
}
|
||||
$targets[$name] = Yii::createObject($target);
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds output asset bundle.
|
||||
* @param \yii\web\AssetBundle $target output asset bundle
|
||||
* @param string $type either 'js' or 'css'.
|
||||
* @param \yii\web\AssetBundle[] $bundles source asset bundles.
|
||||
* @throws Exception on failure.
|
||||
*/
|
||||
protected function buildTarget($target, $type, $bundles)
|
||||
{
|
||||
$inputFiles = [];
|
||||
foreach ($target->depends as $name) {
|
||||
if (isset($bundles[$name])) {
|
||||
if (!$this->isBundleExternal($bundles[$name])) {
|
||||
foreach ($bundles[$name]->$type as $file) {
|
||||
if (is_array($file)) {
|
||||
$inputFiles[] = $bundles[$name]->basePath . '/' . $file[0];
|
||||
} else {
|
||||
$inputFiles[] = $bundles[$name]->basePath . '/' . $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Unknown bundle: '{$name}'");
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($inputFiles)) {
|
||||
$target->$type = [];
|
||||
} else {
|
||||
FileHelper::createDirectory($target->basePath, $this->getAssetManager()->dirMode);
|
||||
$tempFile = $target->basePath . '/' . strtr($target->$type, ['{hash}' => 'temp']);
|
||||
|
||||
if ($type === 'js') {
|
||||
$this->compressJsFiles($inputFiles, $tempFile);
|
||||
} else {
|
||||
$this->compressCssFiles($inputFiles, $tempFile);
|
||||
}
|
||||
|
||||
$targetFile = strtr($target->$type, ['{hash}' => md5_file($tempFile)]);
|
||||
$outputFile = $target->basePath . '/' . $targetFile;
|
||||
rename($tempFile, $outputFile);
|
||||
$target->$type = [$targetFile];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust dependencies between asset bundles in the way source bundles begin to depend on output ones.
|
||||
* @param \yii\web\AssetBundle[] $targets output asset bundles.
|
||||
* @param \yii\web\AssetBundle[] $bundles source asset bundles.
|
||||
* @return \yii\web\AssetBundle[] output asset bundles.
|
||||
*/
|
||||
protected function adjustDependency($targets, $bundles)
|
||||
{
|
||||
$this->stdout("Creating new bundle configuration...\n");
|
||||
|
||||
$map = [];
|
||||
foreach ($targets as $name => $target) {
|
||||
foreach ($target->depends as $bundle) {
|
||||
$map[$bundle] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($targets as $name => $target) {
|
||||
$depends = [];
|
||||
foreach ($target->depends as $bn) {
|
||||
foreach ($bundles[$bn]->depends as $bundle) {
|
||||
$depends[$map[$bundle]] = true;
|
||||
}
|
||||
}
|
||||
unset($depends[$name]);
|
||||
$target->depends = array_keys($depends);
|
||||
}
|
||||
|
||||
// detect possible circular dependencies
|
||||
foreach ($targets as $name => $target) {
|
||||
$registered = [];
|
||||
$this->registerBundle($targets, $name, $registered);
|
||||
}
|
||||
|
||||
foreach ($map as $bundle => $target) {
|
||||
$sourceBundle = $bundles[$bundle];
|
||||
$depends = $sourceBundle->depends;
|
||||
if (!$this->isBundleExternal($sourceBundle)) {
|
||||
$depends[] = $target;
|
||||
}
|
||||
$targetBundle = clone $sourceBundle;
|
||||
$targetBundle->depends = $depends;
|
||||
$targets[$bundle] = $targetBundle;
|
||||
}
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers asset bundles including their dependencies.
|
||||
* @param \yii\web\AssetBundle[] $bundles asset bundles list.
|
||||
* @param string $name bundle name.
|
||||
* @param array $registered stores already registered names.
|
||||
* @throws Exception if circular dependency is detected.
|
||||
*/
|
||||
protected function registerBundle($bundles, $name, &$registered)
|
||||
{
|
||||
if (!isset($registered[$name])) {
|
||||
$registered[$name] = false;
|
||||
$bundle = $bundles[$name];
|
||||
foreach ($bundle->depends as $depend) {
|
||||
$this->registerBundle($bundles, $depend, $registered);
|
||||
}
|
||||
unset($registered[$name]);
|
||||
$registered[$name] = $bundle;
|
||||
} elseif ($registered[$name] === false) {
|
||||
throw new Exception("A circular dependency is detected for target '{$name}': " . $this->composeCircularDependencyTrace($name, $registered) . '.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves new asset bundles configuration.
|
||||
* @param \yii\web\AssetBundle[] $targets list of asset bundles to be saved.
|
||||
* @param string $bundleFile output file name.
|
||||
* @throws \yii\console\Exception on failure.
|
||||
*/
|
||||
protected function saveTargets($targets, $bundleFile)
|
||||
{
|
||||
$array = [];
|
||||
foreach ($targets as $name => $target) {
|
||||
if (isset($this->targets[$name])) {
|
||||
$array[$name] = array_merge($this->targets[$name], [
|
||||
'class' => get_class($target),
|
||||
'sourcePath' => null,
|
||||
'basePath' => $this->targets[$name]['basePath'],
|
||||
'baseUrl' => $this->targets[$name]['baseUrl'],
|
||||
'js' => $target->js,
|
||||
'css' => $target->css,
|
||||
'depends' => [],
|
||||
]);
|
||||
} else {
|
||||
if ($this->isBundleExternal($target)) {
|
||||
$array[$name] = $this->composeBundleConfig($target);
|
||||
} else {
|
||||
$array[$name] = [
|
||||
'sourcePath' => null,
|
||||
'js' => [],
|
||||
'css' => [],
|
||||
'depends' => $target->depends,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$array = VarDumper::export($array);
|
||||
$version = date('Y-m-d H:i:s');
|
||||
$bundleFileContent = <<<EOD
|
||||
<?php
|
||||
/**
|
||||
* This file is generated by the "yii {$this->id}" command.
|
||||
* DO NOT MODIFY THIS FILE DIRECTLY.
|
||||
* @version {$version}
|
||||
*/
|
||||
return {$array};
|
||||
EOD;
|
||||
if (!file_put_contents($bundleFile, $bundleFileContent, LOCK_EX)) {
|
||||
throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'.");
|
||||
}
|
||||
$this->stdout("Output bundle configuration created at '{$bundleFile}'.\n", Console::FG_GREEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses given JavaScript files and combines them into the single one.
|
||||
* @param array $inputFiles list of source file names.
|
||||
* @param string $outputFile output file name.
|
||||
* @throws \yii\console\Exception on failure
|
||||
*/
|
||||
protected function compressJsFiles($inputFiles, $outputFile)
|
||||
{
|
||||
if (empty($inputFiles)) {
|
||||
return;
|
||||
}
|
||||
$this->stdout(" Compressing JavaScript files...\n");
|
||||
if (is_string($this->jsCompressor)) {
|
||||
$tmpFile = $outputFile . '.tmp';
|
||||
$this->combineJsFiles($inputFiles, $tmpFile);
|
||||
$this->stdout((string)shell_exec(strtr($this->jsCompressor, [
|
||||
'{from}' => escapeshellarg($tmpFile),
|
||||
'{to}' => escapeshellarg($outputFile),
|
||||
])));
|
||||
@unlink($tmpFile);
|
||||
} else {
|
||||
call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile);
|
||||
}
|
||||
if (!file_exists($outputFile)) {
|
||||
throw new Exception("Unable to compress JavaScript files into '{$outputFile}'.");
|
||||
}
|
||||
$this->stdout(" JavaScript files compressed into '{$outputFile}'.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses given CSS files and combines them into the single one.
|
||||
* @param array $inputFiles list of source file names.
|
||||
* @param string $outputFile output file name.
|
||||
* @throws \yii\console\Exception on failure
|
||||
*/
|
||||
protected function compressCssFiles($inputFiles, $outputFile)
|
||||
{
|
||||
if (empty($inputFiles)) {
|
||||
return;
|
||||
}
|
||||
$this->stdout(" Compressing CSS files...\n");
|
||||
if (is_string($this->cssCompressor)) {
|
||||
$tmpFile = $outputFile . '.tmp';
|
||||
$this->combineCssFiles($inputFiles, $tmpFile);
|
||||
$this->stdout((string)shell_exec(strtr($this->cssCompressor, [
|
||||
'{from}' => escapeshellarg($tmpFile),
|
||||
'{to}' => escapeshellarg($outputFile),
|
||||
])));
|
||||
@unlink($tmpFile);
|
||||
} else {
|
||||
call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile);
|
||||
}
|
||||
if (!file_exists($outputFile)) {
|
||||
throw new Exception("Unable to compress CSS files into '{$outputFile}'.");
|
||||
}
|
||||
$this->stdout(" CSS files compressed into '{$outputFile}'.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines JavaScript files into a single one.
|
||||
* @param array $inputFiles source file names.
|
||||
* @param string $outputFile output file name.
|
||||
* @throws \yii\console\Exception on failure.
|
||||
*/
|
||||
public function combineJsFiles($inputFiles, $outputFile)
|
||||
{
|
||||
$content = '';
|
||||
foreach ($inputFiles as $file) {
|
||||
// Add a semicolon to source code if trailing semicolon missing.
|
||||
// Notice: It needs a new line before `;` to avoid affection of line comment. (// ...;)
|
||||
$fileContent = rtrim(file_get_contents($file));
|
||||
if (substr($fileContent, -1) !== ';') {
|
||||
$fileContent .= "\n;";
|
||||
}
|
||||
$content .= "/*** BEGIN FILE: $file ***/\n"
|
||||
. $fileContent . "\n"
|
||||
. "/*** END FILE: $file ***/\n";
|
||||
}
|
||||
if (!file_put_contents($outputFile, $content)) {
|
||||
throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines CSS files into a single one.
|
||||
* @param array $inputFiles source file names.
|
||||
* @param string $outputFile output file name.
|
||||
* @throws \yii\console\Exception on failure.
|
||||
*/
|
||||
public function combineCssFiles($inputFiles, $outputFile)
|
||||
{
|
||||
$content = '';
|
||||
$outputFilePath = dirname($this->findRealPath($outputFile));
|
||||
foreach ($inputFiles as $file) {
|
||||
$content .= "/*** BEGIN FILE: $file ***/\n"
|
||||
. $this->adjustCssUrl(file_get_contents($file), dirname($this->findRealPath($file)), $outputFilePath)
|
||||
. "/*** END FILE: $file ***/\n";
|
||||
}
|
||||
if (!file_put_contents($outputFile, $content)) {
|
||||
throw new Exception("Unable to write output CSS file '{$outputFile}'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts CSS content allowing URL references pointing to the original resources.
|
||||
* @param string $cssContent source CSS content.
|
||||
* @param string $inputFilePath input CSS file name.
|
||||
* @param string $outputFilePath output CSS file name.
|
||||
* @return string adjusted CSS content.
|
||||
*/
|
||||
protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath)
|
||||
{
|
||||
$inputFilePath = str_replace('\\', '/', $inputFilePath);
|
||||
$outputFilePath = str_replace('\\', '/', $outputFilePath);
|
||||
|
||||
$sharedPathParts = [];
|
||||
$inputFilePathParts = explode('/', $inputFilePath);
|
||||
$inputFilePathPartsCount = count($inputFilePathParts);
|
||||
$outputFilePathParts = explode('/', $outputFilePath);
|
||||
$outputFilePathPartsCount = count($outputFilePathParts);
|
||||
for ($i = 0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) {
|
||||
if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) {
|
||||
$sharedPathParts[] = $inputFilePathParts[$i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$sharedPath = implode('/', $sharedPathParts);
|
||||
|
||||
$inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/');
|
||||
$outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/');
|
||||
if (empty($inputFileRelativePath)) {
|
||||
$inputFileRelativePathParts = [];
|
||||
} else {
|
||||
$inputFileRelativePathParts = explode('/', $inputFileRelativePath);
|
||||
}
|
||||
if (empty($outputFileRelativePath)) {
|
||||
$outputFileRelativePathParts = [];
|
||||
} else {
|
||||
$outputFileRelativePathParts = explode('/', $outputFileRelativePath);
|
||||
}
|
||||
|
||||
$callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
|
||||
$fullMatch = $matches[0];
|
||||
$inputUrl = $matches[1];
|
||||
|
||||
if (strncmp($inputUrl, '/', 1) === 0 || strncmp($inputUrl, '#', 1) === 0 || preg_match('/^https?:\/\//i', $inputUrl) || preg_match('/^data:/i', $inputUrl)) {
|
||||
return $fullMatch;
|
||||
}
|
||||
if ($inputFileRelativePathParts === $outputFileRelativePathParts) {
|
||||
return $fullMatch;
|
||||
}
|
||||
|
||||
if (empty($outputFileRelativePathParts)) {
|
||||
$outputUrlParts = [];
|
||||
} else {
|
||||
$outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..');
|
||||
}
|
||||
$outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts);
|
||||
|
||||
if (strpos($inputUrl, '/') !== false) {
|
||||
$inputUrlParts = explode('/', $inputUrl);
|
||||
foreach ($inputUrlParts as $key => $inputUrlPart) {
|
||||
if ($inputUrlPart === '..') {
|
||||
array_pop($outputUrlParts);
|
||||
unset($inputUrlParts[$key]);
|
||||
}
|
||||
}
|
||||
$outputUrlParts[] = implode('/', $inputUrlParts);
|
||||
} else {
|
||||
$outputUrlParts[] = $inputUrl;
|
||||
}
|
||||
$outputUrl = implode('/', $outputUrlParts);
|
||||
|
||||
return str_replace($inputUrl, $outputUrl, $fullMatch);
|
||||
};
|
||||
|
||||
$cssContent = preg_replace_callback('/url\(["\']?([^)^"\']*)["\']?\)/i', $callback, $cssContent);
|
||||
|
||||
return $cssContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates template of configuration file for [[actionCompress]].
|
||||
* @param string $configFile output file name.
|
||||
* @return int CLI exit code
|
||||
* @throws \yii\console\Exception on failure.
|
||||
*/
|
||||
public function actionTemplate($configFile)
|
||||
{
|
||||
$jsCompressor = VarDumper::export($this->jsCompressor);
|
||||
$cssCompressor = VarDumper::export($this->cssCompressor);
|
||||
|
||||
$template = <<<EOD
|
||||
<?php
|
||||
/**
|
||||
* Configuration file for the "yii asset" console command.
|
||||
*/
|
||||
|
||||
// In the console environment, some path aliases may not exist. Please define these:
|
||||
// Yii::setAlias('@webroot', __DIR__ . '/../web');
|
||||
// Yii::setAlias('@web', '/');
|
||||
|
||||
return [
|
||||
// Adjust command/callback for JavaScript files compressing:
|
||||
'jsCompressor' => {$jsCompressor},
|
||||
// Adjust command/callback for CSS files compressing:
|
||||
'cssCompressor' => {$cssCompressor},
|
||||
// Whether to delete asset source after compression:
|
||||
'deleteSource' => false,
|
||||
// The list of asset bundles to compress:
|
||||
'bundles' => [
|
||||
// 'app\assets\AppAsset',
|
||||
// 'yii\web\YiiAsset',
|
||||
// 'yii\web\JqueryAsset',
|
||||
],
|
||||
// Asset bundle for compression output:
|
||||
'targets' => [
|
||||
'all' => [
|
||||
'class' => 'yii\web\AssetBundle',
|
||||
'basePath' => '@webroot/assets',
|
||||
'baseUrl' => '@web/assets',
|
||||
'js' => 'js/all-{hash}.js',
|
||||
'css' => 'css/all-{hash}.css',
|
||||
],
|
||||
],
|
||||
// Asset manager configuration:
|
||||
'assetManager' => [
|
||||
//'basePath' => '@webroot/assets',
|
||||
//'baseUrl' => '@web/assets',
|
||||
],
|
||||
];
|
||||
EOD;
|
||||
if (file_exists($configFile)) {
|
||||
if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
}
|
||||
if (!file_put_contents($configFile, $template, LOCK_EX)) {
|
||||
throw new Exception("Unable to write template file '{$configFile}'.");
|
||||
}
|
||||
|
||||
$this->stdout("Configuration file template created at '{$configFile}'.\n\n", Console::FG_GREEN);
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns canonicalized absolute pathname.
|
||||
* Unlike regular `realpath()` this method does not expand symlinks and does not check path existence.
|
||||
* @param string $path raw path
|
||||
* @return string canonicalized absolute pathname
|
||||
*/
|
||||
private function findRealPath($path)
|
||||
{
|
||||
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
|
||||
$pathParts = explode(DIRECTORY_SEPARATOR, $path);
|
||||
|
||||
$realPathParts = [];
|
||||
foreach ($pathParts as $pathPart) {
|
||||
if ($pathPart === '..') {
|
||||
array_pop($realPathParts);
|
||||
} else {
|
||||
$realPathParts[] = $pathPart;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(DIRECTORY_SEPARATOR, $realPathParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AssetBundle $bundle
|
||||
* @return bool whether asset bundle external or not.
|
||||
*/
|
||||
private function isBundleExternal($bundle)
|
||||
{
|
||||
return empty($bundle->sourcePath) && empty($bundle->basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AssetBundle $bundle asset bundle instance.
|
||||
* @return array bundle configuration.
|
||||
*/
|
||||
private function composeBundleConfig($bundle)
|
||||
{
|
||||
$config = Yii::getObjectVars($bundle);
|
||||
$config['class'] = get_class($bundle);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes trace info for bundle circular dependency.
|
||||
* @param string $circularDependencyName name of the bundle, which have circular dependency
|
||||
* @param array $registered list of bundles registered while detecting circular dependency.
|
||||
* @return string bundle circular dependency trace string.
|
||||
*/
|
||||
private function composeCircularDependencyTrace($circularDependencyName, array $registered)
|
||||
{
|
||||
$dependencyTrace = [];
|
||||
$startFound = false;
|
||||
foreach ($registered as $name => $value) {
|
||||
if ($name === $circularDependencyName) {
|
||||
$startFound = true;
|
||||
}
|
||||
if ($startFound && $value === false) {
|
||||
$dependencyTrace[] = $name;
|
||||
}
|
||||
}
|
||||
$dependencyTrace[] = $circularDependencyName;
|
||||
return implode(' -> ', $dependencyTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes bundle asset files, which have been published from `sourcePath`.
|
||||
* @param \yii\web\AssetBundle[] $bundles asset bundles to be processed.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
private function deletePublishedAssets($bundles)
|
||||
{
|
||||
$this->stdout("Deleting source files...\n");
|
||||
|
||||
if ($this->getAssetManager()->linkAssets) {
|
||||
$this->stdout("`AssetManager::linkAssets` option is enabled. Deleting of source files canceled.\n", Console::FG_YELLOW);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($bundles as $bundle) {
|
||||
if ($bundle->sourcePath !== null) {
|
||||
foreach ($bundle->js as $jsFile) {
|
||||
@unlink($bundle->basePath . DIRECTORY_SEPARATOR . $jsFile);
|
||||
}
|
||||
foreach ($bundle->css as $cssFile) {
|
||||
@unlink($bundle->basePath . DIRECTORY_SEPARATOR . $cssFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->stdout("Source files deleted.\n", Console::FG_GREEN);
|
||||
}
|
||||
}
|
||||
1018
vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php
vendored
Normal file
1018
vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
311
vendor/yiisoft/yii2/console/controllers/CacheController.php
vendored
Normal file
311
vendor/yiisoft/yii2/console/controllers/CacheController.php
vendored
Normal file
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\caching\ApcCache;
|
||||
use yii\caching\CacheInterface;
|
||||
use yii\console\Application;
|
||||
use yii\console\Controller;
|
||||
use yii\console\Exception;
|
||||
use yii\console\ExitCode;
|
||||
use yii\helpers\Console;
|
||||
|
||||
/**
|
||||
* Allows you to flush cache.
|
||||
*
|
||||
* see list of available components to flush:
|
||||
*
|
||||
* yii cache
|
||||
*
|
||||
* flush particular components specified by their names:
|
||||
*
|
||||
* yii cache/flush first second third
|
||||
*
|
||||
* flush all cache components that can be found in the system
|
||||
*
|
||||
* yii cache/flush-all
|
||||
*
|
||||
* Note that the command uses cache components defined in your console application configuration file. If components
|
||||
* configured are different from web application, web application cache won't be cleared. In order to fix it please
|
||||
* duplicate web application cache components in console config. You can use any component names.
|
||||
*
|
||||
* APC is not shared between PHP processes so flushing cache from command line has no effect on web.
|
||||
* Flushing web cache could be either done by:
|
||||
*
|
||||
* - Putting a php file under web root and calling it via HTTP
|
||||
* - Using [Cachetool](https://gordalina.github.io/cachetool/)
|
||||
*
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
* @author Mark Jebri <mark.github@yandex.ru>
|
||||
* @since 2.0
|
||||
*
|
||||
* @template T of Application
|
||||
* @extends Controller<T>
|
||||
*/
|
||||
class CacheController extends Controller
|
||||
{
|
||||
/**
|
||||
* Lists the caches that can be flushed.
|
||||
*/
|
||||
public function actionIndex()
|
||||
{
|
||||
$caches = $this->findCaches();
|
||||
|
||||
if (!empty($caches)) {
|
||||
$this->notifyCachesCanBeFlushed($caches);
|
||||
} else {
|
||||
$this->notifyNoCachesFound();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes given cache components.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```
|
||||
* # flushes caches specified by their id: "first", "second", "third"
|
||||
* yii cache/flush first second third
|
||||
* ```
|
||||
*/
|
||||
public function actionFlush()
|
||||
{
|
||||
$cachesInput = func_get_args();
|
||||
|
||||
if (empty($cachesInput)) {
|
||||
throw new Exception('You should specify cache components names');
|
||||
}
|
||||
|
||||
$caches = $this->findCaches($cachesInput);
|
||||
$cachesInfo = [];
|
||||
|
||||
$foundCaches = array_keys($caches);
|
||||
$notFoundCaches = array_diff($cachesInput, array_keys($caches));
|
||||
|
||||
if ($notFoundCaches !== []) {
|
||||
$this->notifyNotFoundCaches($notFoundCaches);
|
||||
}
|
||||
|
||||
if ($foundCaches === []) {
|
||||
$this->notifyNoCachesFound();
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
if (!$this->confirmFlush($foundCaches)) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
foreach ($caches as $name => $class) {
|
||||
$cachesInfo[] = [
|
||||
'name' => $name,
|
||||
'class' => $class,
|
||||
'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false,
|
||||
];
|
||||
}
|
||||
|
||||
$this->notifyFlushed($cachesInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all caches registered in the system.
|
||||
*/
|
||||
public function actionFlushAll()
|
||||
{
|
||||
$caches = $this->findCaches();
|
||||
$cachesInfo = [];
|
||||
|
||||
if (empty($caches)) {
|
||||
$this->notifyNoCachesFound();
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
foreach ($caches as $name => $class) {
|
||||
$cachesInfo[] = [
|
||||
'name' => $name,
|
||||
'class' => $class,
|
||||
'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false,
|
||||
];
|
||||
}
|
||||
|
||||
$this->notifyFlushed($cachesInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears DB schema cache for a given connection component.
|
||||
*
|
||||
* ```
|
||||
* # clears cache schema specified by component id: "db"
|
||||
* yii cache/flush-schema db
|
||||
* ```
|
||||
*
|
||||
* @param string $db id connection component
|
||||
* @return int exit code
|
||||
* @throws Exception
|
||||
* @throws \yii\base\InvalidConfigException
|
||||
*
|
||||
* @since 2.0.1
|
||||
*/
|
||||
public function actionFlushSchema($db = 'db')
|
||||
{
|
||||
$connection = Yii::$app->get($db, false);
|
||||
if ($connection === null) {
|
||||
$this->stdout("Unknown component \"$db\".\n", Console::FG_RED);
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
if (!$connection instanceof \yii\db\Connection) {
|
||||
$this->stdout("\"$db\" component doesn't inherit \\yii\\db\\Connection.\n", Console::FG_RED);
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
} elseif (!$this->confirm("Flush cache schema for \"$db\" connection?")) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
try {
|
||||
$schema = $connection->getSchema();
|
||||
$schema->refresh();
|
||||
$this->stdout("Schema cache for component \"$db\", was flushed.\n\n", Console::FG_GREEN);
|
||||
} catch (\Exception $e) {
|
||||
$this->stdout($e->getMessage() . "\n\n", Console::FG_RED);
|
||||
}
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that given caches are found and can be flushed.
|
||||
* @param array $caches array of cache component classes
|
||||
*/
|
||||
private function notifyCachesCanBeFlushed($caches)
|
||||
{
|
||||
$this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW);
|
||||
|
||||
foreach ($caches as $name => $class) {
|
||||
if ($this->canBeFlushed($class)) {
|
||||
$this->stdout("\t* $name ($class)\n", Console::FG_GREEN);
|
||||
} else {
|
||||
$this->stdout("\t* $name ($class) - can not be flushed via console\n", Console::FG_YELLOW);
|
||||
}
|
||||
}
|
||||
|
||||
$this->stdout("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that there was not found any cache in the system.
|
||||
*/
|
||||
private function notifyNoCachesFound()
|
||||
{
|
||||
$this->stdout("No cache components were found in the system.\n", Console::FG_RED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that given cache components were not found in the system.
|
||||
* @param array $cachesNames
|
||||
*/
|
||||
private function notifyNotFoundCaches($cachesNames)
|
||||
{
|
||||
$this->stdout("The following cache components were NOT found:\n\n", Console::FG_RED);
|
||||
|
||||
foreach ($cachesNames as $name) {
|
||||
$this->stdout("\t* $name \n", Console::FG_GREEN);
|
||||
}
|
||||
|
||||
$this->stdout("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $caches
|
||||
*/
|
||||
private function notifyFlushed($caches)
|
||||
{
|
||||
$this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW);
|
||||
|
||||
foreach ($caches as $cache) {
|
||||
$this->stdout("\t* " . $cache['name'] . ' (' . $cache['class'] . ')', Console::FG_GREEN);
|
||||
|
||||
if (!$cache['is_flushed']) {
|
||||
$this->stdout(" - not flushed\n", Console::FG_RED);
|
||||
} else {
|
||||
$this->stdout("\n");
|
||||
}
|
||||
}
|
||||
|
||||
$this->stdout("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user with confirmation if caches should be flushed.
|
||||
* @param array $cachesNames
|
||||
* @return bool
|
||||
*/
|
||||
private function confirmFlush($cachesNames)
|
||||
{
|
||||
$this->stdout("The following cache components will be flushed:\n\n", Console::FG_YELLOW);
|
||||
|
||||
foreach ($cachesNames as $name) {
|
||||
$this->stdout("\t* $name \n", Console::FG_GREEN);
|
||||
}
|
||||
|
||||
return $this->confirm("\nFlush above cache components?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of caches in the system, keys are cache components names, values are class names.
|
||||
* @param array $cachesNames caches to be found
|
||||
* @return array
|
||||
*/
|
||||
private function findCaches(array $cachesNames = [])
|
||||
{
|
||||
$caches = [];
|
||||
$components = Yii::$app->getComponents();
|
||||
$findAll = ($cachesNames === []);
|
||||
|
||||
foreach ($components as $name => $component) {
|
||||
if (!$findAll && !in_array($name, $cachesNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($component instanceof CacheInterface) {
|
||||
$caches[$name] = get_class($component);
|
||||
} elseif (is_array($component) && isset($component['class']) && $this->isCacheClass($component['class'])) {
|
||||
$caches[$name] = $component['class'];
|
||||
} elseif (is_string($component) && $this->isCacheClass($component)) {
|
||||
$caches[$name] = $component;
|
||||
} elseif ($component instanceof \Closure) {
|
||||
$cache = Yii::$app->get($name);
|
||||
if ($this->isCacheClass($cache)) {
|
||||
$cacheClass = get_class($cache);
|
||||
$caches[$name] = $cacheClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $caches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given class is a Cache class.
|
||||
* @param string $className class name.
|
||||
* @return bool
|
||||
*/
|
||||
private function isCacheClass($className)
|
||||
{
|
||||
return is_subclass_of($className, 'yii\caching\CacheInterface') || $className === 'yii\caching\CacheInterface';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cache of a certain class can be flushed.
|
||||
* @param string $className class name.
|
||||
* @return bool
|
||||
*/
|
||||
private function canBeFlushed($className)
|
||||
{
|
||||
return !is_a($className, ApcCache::className(), true) || PHP_SAPI !== 'cli';
|
||||
}
|
||||
}
|
||||
550
vendor/yiisoft/yii2/console/controllers/FixtureController.php
vendored
Normal file
550
vendor/yiisoft/yii2/console/controllers/FixtureController.php
vendored
Normal file
@ -0,0 +1,550 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\console\Application;
|
||||
use yii\console\Controller;
|
||||
use yii\console\Exception;
|
||||
use yii\console\ExitCode;
|
||||
use yii\helpers\Console;
|
||||
use yii\helpers\FileHelper;
|
||||
use yii\test\Fixture;
|
||||
use yii\test\FixtureTrait;
|
||||
|
||||
/**
|
||||
* Manages fixture data loading and unloading.
|
||||
*
|
||||
* ```
|
||||
* #load fixtures from UsersFixture class with default namespace "tests\unit\fixtures"
|
||||
* yii fixture/load User
|
||||
*
|
||||
* #also a short version of this command (generate action is default)
|
||||
* yii fixture User
|
||||
*
|
||||
* #load all fixtures
|
||||
* yii fixture "*"
|
||||
*
|
||||
* #load all fixtures except User
|
||||
* yii fixture "*, -User"
|
||||
*
|
||||
* #load fixtures with different namespace.
|
||||
* yii fixture/load User --namespace=alias\my\custom\namespace\goes\here
|
||||
* ```
|
||||
*
|
||||
* The `unload` sub-command can be used similarly to unload fixtures.
|
||||
*
|
||||
* @author Mark Jebri <mark.github@yandex.ru>
|
||||
* @since 2.0
|
||||
*
|
||||
* @template T of Application
|
||||
* @extends Controller<T>
|
||||
*/
|
||||
class FixtureController extends Controller
|
||||
{
|
||||
use FixtureTrait;
|
||||
|
||||
/**
|
||||
* @var string controller default action ID.
|
||||
*/
|
||||
public $defaultAction = 'load';
|
||||
/**
|
||||
* @var string default namespace to search fixtures in
|
||||
*/
|
||||
public $namespace = 'tests\unit\fixtures';
|
||||
/**
|
||||
* @var array global fixtures that should be applied when loading and unloading. By default it is set to `InitDbFixture`
|
||||
* that disables and enables integrity check, so your data can be safely loaded.
|
||||
*/
|
||||
public $globalFixtures = [
|
||||
'yii\test\InitDbFixture',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function options($actionID)
|
||||
{
|
||||
return array_merge(parent::options($actionID), [
|
||||
'namespace', 'globalFixtures',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.8
|
||||
*/
|
||||
public function optionAliases()
|
||||
{
|
||||
return array_merge(parent::optionAliases(), [
|
||||
'g' => 'globalFixtures',
|
||||
'n' => 'namespace',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specified fixture data.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```
|
||||
* # load the fixture data specified by User and UserProfile.
|
||||
* # any existing fixture data will be removed first
|
||||
* yii fixture/load "User, UserProfile"
|
||||
*
|
||||
* # load all available fixtures found under 'tests\unit\fixtures'
|
||||
* yii fixture/load "*"
|
||||
*
|
||||
* # load all fixtures except User and UserProfile
|
||||
* yii fixture/load "*, -User, -UserProfile"
|
||||
* ```
|
||||
*
|
||||
* @param array $fixturesInput
|
||||
* @return int return code
|
||||
* @throws Exception if the specified fixture does not exist.
|
||||
*/
|
||||
public function actionLoad(array $fixturesInput = [])
|
||||
{
|
||||
if ($fixturesInput === []) {
|
||||
$this->printHelpMessage();
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
$filtered = $this->filterFixtures($fixturesInput);
|
||||
$except = $filtered['except'];
|
||||
|
||||
if (!$this->needToApplyAll($fixturesInput[0])) {
|
||||
$fixtures = $filtered['apply'];
|
||||
|
||||
$foundFixtures = $this->findFixtures($fixtures);
|
||||
$notFoundFixtures = array_diff($fixtures, $foundFixtures);
|
||||
|
||||
if ($notFoundFixtures !== []) {
|
||||
$this->notifyNotFound($notFoundFixtures);
|
||||
}
|
||||
} else {
|
||||
$foundFixtures = $this->findFixtures();
|
||||
}
|
||||
|
||||
$fixturesToLoad = array_diff($foundFixtures, $except);
|
||||
|
||||
if (!$foundFixtures) {
|
||||
throw new Exception(
|
||||
'No files were found for: "' . implode(', ', $fixturesInput) . "\".\n" .
|
||||
"Check that files exist under fixtures path: \n\"" . $this->getFixturePath() . '".'
|
||||
);
|
||||
}
|
||||
|
||||
if ($fixturesToLoad === []) {
|
||||
$this->notifyNothingToLoad($foundFixtures, $except);
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
if (!$this->confirmLoad($fixturesToLoad, $except)) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
$fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $fixturesToLoad));
|
||||
|
||||
if (!$fixtures) {
|
||||
throw new Exception('No fixtures were found in namespace: "' . $this->namespace . '"' . '');
|
||||
}
|
||||
|
||||
$fixturesObjects = $this->createFixtures($fixtures);
|
||||
|
||||
$this->unloadFixtures($fixturesObjects);
|
||||
$this->loadFixtures($fixturesObjects);
|
||||
$this->notifyLoaded($fixturesObjects);
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the specified fixtures.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```
|
||||
* # unload the fixture data specified by User and UserProfile.
|
||||
* yii fixture/unload "User, UserProfile"
|
||||
*
|
||||
* # unload all fixtures found under 'tests\unit\fixtures'
|
||||
* yii fixture/unload "*"
|
||||
*
|
||||
* # unload all fixtures except User and UserProfile
|
||||
* yii fixture/unload "*, -User, -UserProfile"
|
||||
* ```
|
||||
*
|
||||
* @param array $fixturesInput
|
||||
* @return int return code
|
||||
* @throws Exception if the specified fixture does not exist.
|
||||
*/
|
||||
public function actionUnload(array $fixturesInput = [])
|
||||
{
|
||||
if ($fixturesInput === []) {
|
||||
$this->printHelpMessage();
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
$filtered = $this->filterFixtures($fixturesInput);
|
||||
$except = $filtered['except'];
|
||||
|
||||
if (!$this->needToApplyAll($fixturesInput[0])) {
|
||||
$fixtures = $filtered['apply'];
|
||||
|
||||
$foundFixtures = $this->findFixtures($fixtures);
|
||||
$notFoundFixtures = array_diff($fixtures, $foundFixtures);
|
||||
|
||||
if ($notFoundFixtures !== []) {
|
||||
$this->notifyNotFound($notFoundFixtures);
|
||||
}
|
||||
} else {
|
||||
$foundFixtures = $this->findFixtures();
|
||||
}
|
||||
|
||||
if ($foundFixtures === []) {
|
||||
throw new Exception(
|
||||
'No files were found for: "' . implode(', ', $fixturesInput) . "\".\n" .
|
||||
"Check that files exist under fixtures path: \n\"" . $this->getFixturePath() . '".'
|
||||
);
|
||||
}
|
||||
|
||||
$fixturesToUnload = array_diff($foundFixtures, $except);
|
||||
|
||||
if ($fixturesToUnload === []) {
|
||||
$this->notifyNothingToUnload($foundFixtures, $except);
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
if (!$this->confirmUnload($fixturesToUnload, $except)) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
$fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $fixturesToUnload));
|
||||
|
||||
if ($fixtures === []) {
|
||||
throw new Exception('No fixtures were found in namespace: ' . $this->namespace . '".');
|
||||
}
|
||||
|
||||
$this->unloadFixtures($this->createFixtures($fixtures));
|
||||
$this->notifyUnloaded($fixtures);
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help message.
|
||||
*/
|
||||
private function printHelpMessage()
|
||||
{
|
||||
$this->stdout($this->getHelpSummary() . "\n");
|
||||
|
||||
$helpCommand = Console::ansiFormat('yii help fixture', [Console::FG_CYAN]);
|
||||
$this->stdout("Use $helpCommand to get usage info.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that fixtures were successfully loaded.
|
||||
* @param Fixture[] $fixtures array of loaded fixtures
|
||||
*/
|
||||
private function notifyLoaded($fixtures)
|
||||
{
|
||||
$this->stdout("Fixtures were successfully loaded from namespace:\n", Console::FG_YELLOW);
|
||||
$this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
|
||||
|
||||
$fixtureClassNames = [];
|
||||
|
||||
foreach ($fixtures as $fixture) {
|
||||
$fixtureClassNames[] = $fixture::className();
|
||||
}
|
||||
|
||||
$this->outputList($fixtureClassNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that there are no fixtures to load according input conditions.
|
||||
* @param array $foundFixtures array of found fixtures
|
||||
* @param array $except array of names of fixtures that should not be loaded
|
||||
*/
|
||||
public function notifyNothingToLoad($foundFixtures, $except)
|
||||
{
|
||||
$this->stdout("Fixtures to load could not be found according given conditions:\n\n", Console::FG_RED);
|
||||
$this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
|
||||
$this->stdout("\t" . $this->namespace . "\n", Console::FG_GREEN);
|
||||
|
||||
if (count($foundFixtures)) {
|
||||
$this->stdout("\nFixtures founded under the namespace:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($foundFixtures);
|
||||
}
|
||||
|
||||
if (count($except)) {
|
||||
$this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
|
||||
$this->outputList($except);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that there are no fixtures to unload according input conditions.
|
||||
* @param array $foundFixtures array of found fixtures
|
||||
* @param array $except array of names of fixtures that should not be loaded
|
||||
*/
|
||||
public function notifyNothingToUnload($foundFixtures, $except)
|
||||
{
|
||||
$this->stdout("Fixtures to unload could not be found according to given conditions:\n\n", Console::FG_RED);
|
||||
$this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
|
||||
$this->stdout("\t" . $this->namespace . "\n", Console::FG_GREEN);
|
||||
|
||||
if (count($foundFixtures)) {
|
||||
$this->stdout("\nFixtures found under the namespace:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($foundFixtures);
|
||||
}
|
||||
|
||||
if (count($except)) {
|
||||
$this->stdout("\nFixtures that will NOT be unloaded: \n\n", Console::FG_YELLOW);
|
||||
$this->outputList($except);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that fixtures were successfully unloaded.
|
||||
* @param array $fixtures
|
||||
*/
|
||||
private function notifyUnloaded($fixtures)
|
||||
{
|
||||
$this->stdout("\nFixtures were successfully unloaded from namespace: ", Console::FG_YELLOW);
|
||||
$this->stdout(Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN);
|
||||
$this->outputList($fixtures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user that fixtures were not found under fixtures path.
|
||||
* @param array $fixtures
|
||||
*/
|
||||
private function notifyNotFound($fixtures)
|
||||
{
|
||||
$this->stdout("Some fixtures were not found under path:\n", Console::BG_RED);
|
||||
$this->stdout("\t" . $this->getFixturePath() . "\n\n", Console::FG_GREEN);
|
||||
$this->stdout("Check that they have correct namespace \"{$this->namespace}\" \n", Console::BG_RED);
|
||||
$this->outputList($fixtures);
|
||||
$this->stdout("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user with confirmation if fixtures should be loaded.
|
||||
* @param array $fixtures
|
||||
* @param array $except
|
||||
* @return bool
|
||||
*/
|
||||
private function confirmLoad($fixtures, $except)
|
||||
{
|
||||
$this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
|
||||
$this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);
|
||||
|
||||
if (count($this->globalFixtures)) {
|
||||
$this->stdout("Global fixtures will be used:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($this->globalFixtures);
|
||||
}
|
||||
|
||||
if (count($fixtures)) {
|
||||
$this->stdout("\nFixtures below will be loaded:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($fixtures);
|
||||
}
|
||||
|
||||
if (count($except)) {
|
||||
$this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW);
|
||||
$this->outputList($except);
|
||||
}
|
||||
|
||||
$this->stdout("\nBe aware that:\n", Console::BOLD);
|
||||
$this->stdout("Applying leads to purging of certain data in the database!\n", Console::FG_RED);
|
||||
|
||||
return $this->confirm("\nLoad above fixtures?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user with confirmation for fixtures that should be unloaded.
|
||||
* @param array $fixtures
|
||||
* @param array $except
|
||||
* @return bool
|
||||
*/
|
||||
private function confirmUnload($fixtures, $except)
|
||||
{
|
||||
$this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW);
|
||||
$this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN);
|
||||
|
||||
if (count($this->globalFixtures)) {
|
||||
$this->stdout("Global fixtures will be used:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($this->globalFixtures);
|
||||
}
|
||||
|
||||
if (count($fixtures)) {
|
||||
$this->stdout("\nFixtures below will be unloaded:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($fixtures);
|
||||
}
|
||||
|
||||
if (count($except)) {
|
||||
$this->stdout("\nFixtures that will NOT be unloaded:\n\n", Console::FG_YELLOW);
|
||||
$this->outputList($except);
|
||||
}
|
||||
|
||||
return $this->confirm("\nUnload fixtures?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs data to the console as a list.
|
||||
* @param array $data
|
||||
*/
|
||||
private function outputList($data)
|
||||
{
|
||||
foreach ($data as $index => $item) {
|
||||
$this->stdout("\t" . ($index + 1) . ". {$item}\n", Console::FG_GREEN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if needed to apply all fixtures.
|
||||
* @param string $fixture
|
||||
* @return bool
|
||||
*/
|
||||
public function needToApplyAll($fixture)
|
||||
{
|
||||
return $fixture === '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds fixtures to be loaded, for example "User", if no fixtures were specified then all of them
|
||||
* will be searching by suffix "Fixture.php".
|
||||
* @param array $fixtures fixtures to be loaded
|
||||
* @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists.
|
||||
*/
|
||||
private function findFixtures(array $fixtures = [])
|
||||
{
|
||||
$fixturesPath = $this->getFixturePath();
|
||||
|
||||
$filesToSearch = ['*Fixture.php'];
|
||||
$findAll = ($fixtures === []);
|
||||
|
||||
if (!$findAll) {
|
||||
$filesToSearch = [];
|
||||
|
||||
foreach ($fixtures as $fileName) {
|
||||
$filesToSearch[] = $fileName . 'Fixture.php';
|
||||
}
|
||||
}
|
||||
|
||||
$files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]);
|
||||
$foundFixtures = [];
|
||||
|
||||
foreach ($files as $fixture) {
|
||||
$foundFixtures[] = $this->getFixtureRelativeName($fixture);
|
||||
}
|
||||
|
||||
return $foundFixtures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates fixture's name
|
||||
* Basically, strips [[getFixturePath()]] and `Fixture.php' suffix from fixture's full path.
|
||||
* @see getFixturePath()
|
||||
* @param string $fullFixturePath Full fixture path
|
||||
* @return string Relative fixture name
|
||||
*/
|
||||
private function getFixtureRelativeName($fullFixturePath)
|
||||
{
|
||||
$fixturesPath = FileHelper::normalizePath($this->getFixturePath());
|
||||
$fullFixturePath = FileHelper::normalizePath($fullFixturePath);
|
||||
|
||||
$relativeName = substr($fullFixturePath, strlen($fixturesPath) + 1);
|
||||
$relativeDir = dirname($relativeName) === '.' ? '' : dirname($relativeName) . '/';
|
||||
|
||||
return $relativeDir . basename($fullFixturePath, 'Fixture.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns valid fixtures config that can be used to load them.
|
||||
* @param array $fixtures fixtures to configure
|
||||
* @return array
|
||||
*/
|
||||
private function getFixturesConfig($fixtures)
|
||||
{
|
||||
$config = [];
|
||||
|
||||
foreach ($fixtures as $fixture) {
|
||||
$isNamespaced = (strpos($fixture, '\\') !== false);
|
||||
// replace linux' path slashes to namespace backslashes, in case if $fixture is non-namespaced relative path
|
||||
$fixture = str_replace('/', '\\', $fixture);
|
||||
$fullClassName = $isNamespaced ? $fixture : $this->namespace . '\\' . $fixture;
|
||||
|
||||
if (class_exists($fullClassName)) {
|
||||
$config[] = $fullClassName;
|
||||
} elseif (class_exists($fullClassName . 'Fixture')) {
|
||||
$config[] = $fullClassName . 'Fixture';
|
||||
} else {
|
||||
throw new Exception('Neither fixture "' . $fullClassName . '" nor "' . $fullClassName . 'Fixture" was found.');
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters fixtures by splitting them in two categories: one that should be applied and not.
|
||||
*
|
||||
* If fixture is prefixed with "-", for example "-User", that means that fixture should not be loaded,
|
||||
* if it is not prefixed it is considered as one to be loaded. Returns array:
|
||||
*
|
||||
* ```
|
||||
* [
|
||||
* 'apply' => [
|
||||
* 'User',
|
||||
* ...
|
||||
* ],
|
||||
* 'except' => [
|
||||
* 'Custom',
|
||||
* ...
|
||||
* ],
|
||||
* ]
|
||||
* ```
|
||||
* @param array $fixtures
|
||||
* @return array fixtures array with 'apply' and 'except' elements.
|
||||
*/
|
||||
private function filterFixtures($fixtures)
|
||||
{
|
||||
$filtered = [
|
||||
'apply' => [],
|
||||
'except' => [],
|
||||
];
|
||||
|
||||
foreach ($fixtures as $fixture) {
|
||||
if (mb_strpos($fixture, '-') !== false) {
|
||||
$filtered['except'][] = str_replace('-', '', $fixture);
|
||||
} else {
|
||||
$filtered['apply'][] = $fixture;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fixture path that determined on fixtures namespace.
|
||||
* @throws InvalidConfigException if fixture namespace is invalid
|
||||
* @return string fixture path
|
||||
*/
|
||||
private function getFixturePath()
|
||||
{
|
||||
try {
|
||||
return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace));
|
||||
} catch (InvalidParamException $e) {
|
||||
throw new InvalidConfigException('Invalid fixture namespace: "' . $this->namespace . '". Please, check your FixtureController::namespace parameter');
|
||||
}
|
||||
}
|
||||
}
|
||||
601
vendor/yiisoft/yii2/console/controllers/HelpController.php
vendored
Normal file
601
vendor/yiisoft/yii2/console/controllers/HelpController.php
vendored
Normal file
@ -0,0 +1,601 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Application;
|
||||
use yii\base\Module;
|
||||
use yii\console\Controller;
|
||||
use yii\console\Exception;
|
||||
use yii\console\ExitCode;
|
||||
use yii\helpers\Console;
|
||||
use yii\helpers\Inflector;
|
||||
use yii\console\Application as ConsoleApplication;
|
||||
|
||||
/**
|
||||
* Provides help information about console commands.
|
||||
*
|
||||
* This command displays the available command list in
|
||||
* the application or the detailed instructions about using
|
||||
* a specific command.
|
||||
*
|
||||
* This command can be used as follows on command line:
|
||||
*
|
||||
* ```
|
||||
* yii help [command name]
|
||||
* ```
|
||||
*
|
||||
* In the above, if the command name is not provided, all
|
||||
* available commands will be displayed.
|
||||
*
|
||||
* @property-read array $commands All available command names.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*
|
||||
* @template T of ConsoleApplication
|
||||
* @extends Controller<T>
|
||||
*/
|
||||
class HelpController extends Controller
|
||||
{
|
||||
/**
|
||||
* Displays available commands or the detailed information
|
||||
* about a particular command.
|
||||
*
|
||||
* @param string|null $command The name of the command to show help about.
|
||||
* If not provided, all available commands will be displayed.
|
||||
* @return int the exit status
|
||||
* @throws Exception if the command for help is unknown
|
||||
*/
|
||||
public function actionIndex($command = null)
|
||||
{
|
||||
if ($command !== null) {
|
||||
$result = Yii::$app->createController($command);
|
||||
if ($result === false) {
|
||||
$name = $this->ansiFormat($command, Console::FG_YELLOW);
|
||||
throw new Exception("No help for unknown command \"$name\".");
|
||||
}
|
||||
|
||||
list($controller, $actionID) = $result;
|
||||
|
||||
$actions = $this->getActions($controller);
|
||||
if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
|
||||
$this->getSubCommandHelp($controller, $actionID);
|
||||
} else {
|
||||
$this->getCommandHelp($controller);
|
||||
}
|
||||
} else {
|
||||
$this->getDefaultHelp();
|
||||
}
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available controllers and actions in machine readable format.
|
||||
* This is used for shell completion.
|
||||
* @since 2.0.11
|
||||
*/
|
||||
public function actionList()
|
||||
{
|
||||
foreach ($this->getCommandDescriptions() as $command => $description) {
|
||||
$result = Yii::$app->createController($command);
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$actions = $this->getActions($controller);
|
||||
$prefix = $controller->getUniqueId();
|
||||
if ($controller->createAction($controller->defaultAction) !== null) {
|
||||
$this->stdout("$prefix\n");
|
||||
}
|
||||
foreach ($actions as $action) {
|
||||
$this->stdout("$prefix/$action\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available options for the $action in machine readable format.
|
||||
* This is used for shell completion.
|
||||
*
|
||||
* @param string $action route to action
|
||||
* @since 2.0.11
|
||||
*/
|
||||
public function actionListActionOptions($action)
|
||||
{
|
||||
$result = Yii::$app->createController($action);
|
||||
|
||||
if ($result === false || !($result[0] instanceof Controller)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$action = $controller->createAction($actionID);
|
||||
if ($action === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($controller->getActionArgsHelp($action) as $argument => $help) {
|
||||
$description = preg_replace('~\R~', '', addcslashes($help['comment'], ':')) ?: $argument;
|
||||
$this->stdout($argument . ':' . $description . "\n");
|
||||
}
|
||||
|
||||
$this->stdout("\n");
|
||||
foreach ($controller->getActionOptionsHelp($action) as $argument => $help) {
|
||||
$description = preg_replace('~\R~', '', addcslashes($help['comment'], ':'));
|
||||
$this->stdout('--' . $argument . ($description ? ':' . $description : '') . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays usage information for $action.
|
||||
*
|
||||
* @param string $action route to action
|
||||
* @since 2.0.11
|
||||
*/
|
||||
public function actionUsage($action)
|
||||
{
|
||||
$result = Yii::$app->createController($action);
|
||||
|
||||
if ($result === false || !($result[0] instanceof Controller)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$action = $controller->createAction($actionID);
|
||||
if ($action === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scriptName = $this->getScriptName();
|
||||
if ($action->id === $controller->defaultAction) {
|
||||
$this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
|
||||
} else {
|
||||
$this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
|
||||
}
|
||||
|
||||
foreach ($controller->getActionArgsHelp($action) as $name => $arg) {
|
||||
if ($arg['required']) {
|
||||
$this->stdout(' <' . $name . '>', Console::FG_CYAN);
|
||||
} else {
|
||||
$this->stdout(' [' . $name . ']', Console::FG_CYAN);
|
||||
}
|
||||
}
|
||||
|
||||
$this->stdout("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available command names.
|
||||
* @return array all available command names
|
||||
*/
|
||||
public function getCommands()
|
||||
{
|
||||
$commands = $this->getModuleCommands(Yii::$app);
|
||||
sort($commands);
|
||||
return array_filter(array_unique($commands), function ($command) {
|
||||
$result = Yii::$app->createController($command);
|
||||
if ($result === false || !$result[0] instanceof Controller) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$actions = $this->getActions($controller);
|
||||
return $actions !== [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of commands an their descriptions.
|
||||
* @return array all available commands as keys and their description as values.
|
||||
*/
|
||||
protected function getCommandDescriptions()
|
||||
{
|
||||
$descriptions = [];
|
||||
foreach ($this->getCommands() as $command) {
|
||||
$result = Yii::$app->createController($command);
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$descriptions[$command] = $controller->getHelpSummary();
|
||||
}
|
||||
|
||||
return $descriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available actions of the specified controller.
|
||||
* @param Controller $controller the controller instance
|
||||
* @return array all available action IDs.
|
||||
*
|
||||
* @phpstan-param Controller<Module> $controller
|
||||
* @psalm-param Controller<Module> $controller
|
||||
*/
|
||||
public function getActions($controller)
|
||||
{
|
||||
$actions = array_keys($controller->actions());
|
||||
$class = new \ReflectionClass($controller);
|
||||
foreach ($class->getMethods() as $method) {
|
||||
$name = $method->getName();
|
||||
if ($name !== 'actions' && $method->isPublic() && !$method->isStatic() && strncmp($name, 'action', 6) === 0) {
|
||||
$actions[] = $this->camel2id(substr($name, 6));
|
||||
}
|
||||
}
|
||||
sort($actions);
|
||||
|
||||
return array_unique($actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available commands of a specified module.
|
||||
* @param Module $module the module instance
|
||||
* @return array the available command names
|
||||
*/
|
||||
protected function getModuleCommands($module)
|
||||
{
|
||||
$prefix = $module instanceof Application ? '' : $module->getUniqueId() . '/';
|
||||
|
||||
$commands = [];
|
||||
foreach (array_keys($module->controllerMap) as $id) {
|
||||
$commands[] = $prefix . $id;
|
||||
}
|
||||
|
||||
foreach ($module->getModules() as $id => $child) {
|
||||
if (($child = $module->getModule($id)) === null) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->getModuleCommands($child) as $command) {
|
||||
$commands[] = $command;
|
||||
}
|
||||
}
|
||||
|
||||
$controllerPath = $module->getControllerPath();
|
||||
if (is_dir($controllerPath)) {
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($controllerPath, \RecursiveDirectoryIterator::KEY_AS_PATHNAME));
|
||||
$iterator = new \RegexIterator($iterator, '/.*Controller\.php$/', \RecursiveRegexIterator::GET_MATCH);
|
||||
foreach ($iterator as $matches) {
|
||||
$file = $matches[0];
|
||||
$relativePath = str_replace($controllerPath, '', $file);
|
||||
$class = strtr($relativePath, [
|
||||
'/' => '\\',
|
||||
'.php' => '',
|
||||
]);
|
||||
$controllerClass = $module->controllerNamespace . $class;
|
||||
if ($this->validateControllerClass($controllerClass)) {
|
||||
$dir = ltrim(pathinfo($relativePath, PATHINFO_DIRNAME), '\\/');
|
||||
|
||||
$command = Inflector::camel2id(substr(basename($file), 0, -14), '-', true);
|
||||
if (!empty($dir)) {
|
||||
$command = $dir . '/' . $command;
|
||||
}
|
||||
$commands[] = $prefix . $command;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given class is a valid console controller class.
|
||||
* @param string $controllerClass
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateControllerClass($controllerClass)
|
||||
{
|
||||
if (class_exists($controllerClass)) {
|
||||
$class = new \ReflectionClass($controllerClass);
|
||||
return !$class->isAbstract() && $class->isSubclassOf('yii\console\Controller');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays all available commands.
|
||||
*/
|
||||
protected function getDefaultHelp()
|
||||
{
|
||||
$commands = $this->getCommandDescriptions();
|
||||
$this->stdout($this->getDefaultHelpHeader());
|
||||
if (empty($commands)) {
|
||||
$this->stdout("\nNo commands are found.\n\n", Console::BOLD);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
|
||||
$maxLength = 0;
|
||||
foreach ($commands as $command => $description) {
|
||||
$result = Yii::$app->createController($command);
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$actions = $this->getActions($controller);
|
||||
$prefix = $controller->getUniqueId();
|
||||
foreach ($actions as $action) {
|
||||
$string = $prefix . '/' . $action;
|
||||
if ($action === $controller->defaultAction) {
|
||||
$string .= ' (default)';
|
||||
}
|
||||
$maxLength = max($maxLength, strlen($string));
|
||||
}
|
||||
}
|
||||
foreach ($commands as $command => $description) {
|
||||
$result = Yii::$app->createController($command);
|
||||
/**
|
||||
* @var Controller $controller
|
||||
* @phpstan-var Controller<Application> $controller
|
||||
*/
|
||||
list($controller, $actionID) = $result;
|
||||
$actions = $this->getActions($controller);
|
||||
$this->stdout('- ' . $this->ansiFormat($command, Console::FG_YELLOW));
|
||||
$this->stdout(str_repeat(' ', $maxLength + 4 - strlen($command)));
|
||||
$this->stdout(Console::wrapText($description, $maxLength + 4 + 2), Console::BOLD);
|
||||
$this->stdout("\n");
|
||||
$prefix = $controller->getUniqueId();
|
||||
foreach ($actions as $action) {
|
||||
$string = ' ' . $prefix . '/' . $action;
|
||||
$this->stdout(' ' . $this->ansiFormat($string, Console::FG_GREEN));
|
||||
if ($action === $controller->defaultAction) {
|
||||
$string .= ' (default)';
|
||||
$this->stdout(' (default)', Console::FG_YELLOW);
|
||||
}
|
||||
$summary = $controller->getActionHelpSummary($controller->createAction($action));
|
||||
if ($summary !== '') {
|
||||
$this->stdout(str_repeat(' ', $maxLength + 4 - strlen($string)));
|
||||
$this->stdout(Console::wrapText($summary, $maxLength + 4 + 2));
|
||||
}
|
||||
$this->stdout("\n");
|
||||
}
|
||||
$this->stdout("\n");
|
||||
}
|
||||
$scriptName = $this->getScriptName();
|
||||
$this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
|
||||
$this->stdout("\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
|
||||
. $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the overall information of the command.
|
||||
* @param Controller $controller the controller instance
|
||||
*
|
||||
* @phpstan-param Controller<Module> $controller
|
||||
* @psalm-param Controller<Module> $controller
|
||||
*/
|
||||
protected function getCommandHelp($controller)
|
||||
{
|
||||
$controller->color = $this->color;
|
||||
|
||||
$this->stdout("\nDESCRIPTION\n", Console::BOLD);
|
||||
$comment = $controller->getHelp();
|
||||
if ($comment !== '') {
|
||||
$this->stdout("\n$comment\n\n");
|
||||
}
|
||||
|
||||
$actions = $this->getActions($controller);
|
||||
if (!empty($actions)) {
|
||||
$this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
|
||||
$prefix = $controller->getUniqueId();
|
||||
|
||||
$maxlen = 5;
|
||||
foreach ($actions as $action) {
|
||||
$len = strlen($prefix . '/' . $action) + 2 + ($action === $controller->defaultAction ? 10 : 0);
|
||||
$maxlen = max($maxlen, $len);
|
||||
}
|
||||
foreach ($actions as $action) {
|
||||
$this->stdout('- ' . $this->ansiFormat($prefix . '/' . $action, Console::FG_YELLOW));
|
||||
$len = strlen($prefix . '/' . $action) + 2;
|
||||
if ($action === $controller->defaultAction) {
|
||||
$this->stdout(' (default)', Console::FG_GREEN);
|
||||
$len += 10;
|
||||
}
|
||||
$summary = $controller->getActionHelpSummary($controller->createAction($action));
|
||||
if ($summary !== '') {
|
||||
$this->stdout(str_repeat(' ', $maxlen - $len + 2) . Console::wrapText($summary, $maxlen + 2));
|
||||
}
|
||||
$this->stdout("\n");
|
||||
}
|
||||
$scriptName = $this->getScriptName();
|
||||
$this->stdout("\nTo see the detailed information about individual sub-commands, enter:\n");
|
||||
$this->stdout("\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
|
||||
. $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the detailed information of a command action.
|
||||
* @param Controller $controller the controller instance
|
||||
* @param string $actionID action ID
|
||||
* @throws Exception if the action does not exist
|
||||
*
|
||||
* @phpstan-param Controller<Module> $controller
|
||||
* @psalm-param Controller<Module> $controller
|
||||
*/
|
||||
protected function getSubCommandHelp($controller, $actionID)
|
||||
{
|
||||
$action = $controller->createAction($actionID);
|
||||
if ($action === null) {
|
||||
$name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW);
|
||||
throw new Exception("No help for unknown sub-command \"$name\".");
|
||||
}
|
||||
|
||||
$description = $controller->getActionHelp($action);
|
||||
if ($description !== '') {
|
||||
$this->stdout("\nDESCRIPTION\n", Console::BOLD);
|
||||
$this->stdout("\n$description\n\n");
|
||||
}
|
||||
|
||||
$this->stdout("\nUSAGE\n\n", Console::BOLD);
|
||||
$scriptName = $this->getScriptName();
|
||||
if ($action->id === $controller->defaultAction) {
|
||||
$this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
|
||||
} else {
|
||||
$this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
|
||||
}
|
||||
|
||||
$args = $controller->getActionArgsHelp($action);
|
||||
foreach ($args as $name => $arg) {
|
||||
if ($arg['required']) {
|
||||
$this->stdout(' <' . $name . '>', Console::FG_CYAN);
|
||||
} else {
|
||||
$this->stdout(' [' . $name . ']', Console::FG_CYAN);
|
||||
}
|
||||
}
|
||||
|
||||
$options = $controller->getActionOptionsHelp($action);
|
||||
$options[\yii\console\Application::OPTION_APPCONFIG] = [
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
|
||||
];
|
||||
ksort($options);
|
||||
|
||||
$this->stdout(' [...options...]', Console::FG_RED);
|
||||
$this->stdout("\n\n");
|
||||
|
||||
if (!empty($args)) {
|
||||
foreach ($args as $name => $arg) {
|
||||
$this->stdout($this->formatOptionHelp(
|
||||
'- ' . $this->ansiFormat($name, Console::FG_CYAN),
|
||||
$arg['required'],
|
||||
$arg['type'],
|
||||
$arg['default'],
|
||||
$arg['comment']
|
||||
) . "\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
$this->stdout("\nOPTIONS\n\n", Console::BOLD);
|
||||
foreach ($options as $name => $option) {
|
||||
$this->stdout($this->formatOptionHelp(
|
||||
$this->ansiFormat(
|
||||
'--' . $name . $this->formatOptionAliases($controller, $name),
|
||||
Console::FG_RED,
|
||||
empty($option['required']) ? Console::FG_RED : Console::BOLD
|
||||
),
|
||||
!empty($option['required']),
|
||||
$option['type'],
|
||||
$option['default'],
|
||||
$option['comment']
|
||||
) . "\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a well-formed string for an argument or option.
|
||||
* @param string $name the name of the argument or option
|
||||
* @param bool $required whether the argument is required
|
||||
* @param string $type the type of the option or argument
|
||||
* @param mixed $defaultValue the default value of the option or argument
|
||||
* @param string $comment comment about the option or argument
|
||||
* @return string the formatted string for the argument or option
|
||||
*/
|
||||
protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
|
||||
{
|
||||
$comment = trim((string)$comment);
|
||||
$type = trim((string)$type);
|
||||
if (strncmp($type, 'bool', 4) === 0) {
|
||||
$type = 'boolean, 0 or 1';
|
||||
}
|
||||
|
||||
if ($defaultValue !== null && !is_array($defaultValue)) {
|
||||
if ($type === null) {
|
||||
$type = gettype($defaultValue);
|
||||
}
|
||||
if (is_bool($defaultValue)) {
|
||||
// show as integer to avoid confusion
|
||||
$defaultValue = (int) $defaultValue;
|
||||
}
|
||||
if (is_string($defaultValue)) {
|
||||
$defaultValue = "'" . $defaultValue . "'";
|
||||
} else {
|
||||
$defaultValue = var_export($defaultValue, true);
|
||||
}
|
||||
$doc = "$type (defaults to $defaultValue)";
|
||||
} else {
|
||||
$doc = $type;
|
||||
}
|
||||
|
||||
if ($doc === '') {
|
||||
$doc = $comment;
|
||||
} elseif ($comment !== '') {
|
||||
$doc .= "\n" . preg_replace('/^/m', ' ', $comment);
|
||||
}
|
||||
|
||||
$name = $required ? "$name (required)" : $name;
|
||||
|
||||
return $doc === '' ? $name : "$name: $doc";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Controller $controller the controller instance
|
||||
* @param string $option the option name
|
||||
* @return string the formatted string for the alias argument or option
|
||||
* @since 2.0.8
|
||||
*
|
||||
* @phpstan-param Controller<Module> $controller
|
||||
* @psalm-param Controller<Module> $controller
|
||||
*/
|
||||
protected function formatOptionAliases($controller, $option)
|
||||
{
|
||||
foreach ($controller->optionAliases() as $name => $value) {
|
||||
if (Inflector::camel2id($value, '-', true) === $option) {
|
||||
return ', -' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the name of the cli script currently running.
|
||||
*/
|
||||
protected function getScriptName()
|
||||
{
|
||||
return basename(Yii::$app->request->scriptFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a default help header.
|
||||
* @return string default help header.
|
||||
* @since 2.0.11
|
||||
*/
|
||||
protected function getDefaultHelpHeader()
|
||||
{
|
||||
return "\nThis is Yii version " . \Yii::getVersion() . ".\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a CamelCase action name into an ID in lowercase.
|
||||
* Words in the ID are concatenated using the specified character '-'.
|
||||
* For example, 'CreateUser' will be converted to 'create-user'.
|
||||
* @param string $name the string to be converted
|
||||
* @return string the resulting ID
|
||||
*/
|
||||
private function camel2id($name)
|
||||
{
|
||||
return mb_strtolower(trim(preg_replace('/\p{Lu}/u', '-\0', $name), '-'), 'UTF-8');
|
||||
}
|
||||
}
|
||||
1015
vendor/yiisoft/yii2/console/controllers/MessageController.php
vendored
Normal file
1015
vendor/yiisoft/yii2/console/controllers/MessageController.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
619
vendor/yiisoft/yii2/console/controllers/MigrateController.php
vendored
Normal file
619
vendor/yiisoft/yii2/console/controllers/MigrateController.php
vendored
Normal file
@ -0,0 +1,619 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Action;
|
||||
use yii\console\Application;
|
||||
use yii\db\Connection;
|
||||
use yii\db\Query;
|
||||
use yii\di\Instance;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\helpers\Console;
|
||||
use yii\helpers\Inflector;
|
||||
|
||||
/**
|
||||
* Manages application migrations.
|
||||
*
|
||||
* A migration means a set of persistent changes to the application environment
|
||||
* that is shared among different developers. For example, in an application
|
||||
* backed by a database, a migration may refer to a set of changes to
|
||||
* the database, such as creating a new table, adding a new table column.
|
||||
*
|
||||
* This command provides support for tracking the migration history, upgrading
|
||||
* or downloading with migrations, and creating new migration skeletons.
|
||||
*
|
||||
* The migration history is stored in a database table named
|
||||
* as [[migrationTable]]. The table will be automatically created the first time
|
||||
* this command is executed, if it does not exist. You may also manually
|
||||
* create it as follows:
|
||||
*
|
||||
* ```
|
||||
* CREATE TABLE migration (
|
||||
* version varchar(180) PRIMARY KEY,
|
||||
* apply_time integer
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* Below are some common usages of this command:
|
||||
*
|
||||
* ```
|
||||
* # creates a new migration named 'create_user_table'
|
||||
* yii migrate/create create_user_table
|
||||
*
|
||||
* # applies ALL new migrations
|
||||
* yii migrate
|
||||
*
|
||||
* # reverts the last applied migration
|
||||
* yii migrate/down
|
||||
* ```
|
||||
*
|
||||
* Since 2.0.10 you can use namespaced migrations. In order to enable this feature you should configure [[migrationNamespaces]]
|
||||
* property for the controller at application configuration:
|
||||
*
|
||||
* ```
|
||||
* return [
|
||||
* 'controllerMap' => [
|
||||
* 'migrate' => [
|
||||
* 'class' => 'yii\console\controllers\MigrateController',
|
||||
* 'migrationNamespaces' => [
|
||||
* 'app\migrations',
|
||||
* 'some\extension\migrations',
|
||||
* ],
|
||||
* //'migrationPath' => null, // allows to disable not namespaced migration completely
|
||||
* ],
|
||||
* ],
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*
|
||||
* @template T of Application
|
||||
* @extends BaseMigrateController<T>
|
||||
*/
|
||||
class MigrateController extends BaseMigrateController
|
||||
{
|
||||
/**
|
||||
* Maximum length of a migration name.
|
||||
* @since 2.0.13
|
||||
*/
|
||||
public const MAX_NAME_LENGTH = 180;
|
||||
/**
|
||||
* @var string the name of the table for keeping applied migration information.
|
||||
*/
|
||||
public $migrationTable = '{{%migration}}';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $templateFile = '@yii/views/migration.php';
|
||||
/**
|
||||
* @var array a set of template paths for generating migration code automatically.
|
||||
*
|
||||
* The key is the template type, the value is a path or the alias. Supported types are:
|
||||
* - `create_table`: table creating template
|
||||
* - `drop_table`: table dropping template
|
||||
* - `add_column`: adding new column template
|
||||
* - `drop_column`: dropping column template
|
||||
* - `create_junction`: create junction template
|
||||
*
|
||||
* @since 2.0.7
|
||||
*/
|
||||
public $generatorTemplateFiles = [
|
||||
'create_table' => '@yii/views/createTableMigration.php',
|
||||
'drop_table' => '@yii/views/dropTableMigration.php',
|
||||
'add_column' => '@yii/views/addColumnMigration.php',
|
||||
'drop_column' => '@yii/views/dropColumnMigration.php',
|
||||
'create_junction' => '@yii/views/createTableMigration.php',
|
||||
];
|
||||
/**
|
||||
* @var bool indicates whether the table names generated should consider
|
||||
* the `tablePrefix` setting of the DB connection. For example, if the table
|
||||
* name is `post` the generator wil return `{{%post}}`.
|
||||
* @since 2.0.8
|
||||
*/
|
||||
public $useTablePrefix = true;
|
||||
/**
|
||||
* @var array column definition strings used for creating migration code.
|
||||
*
|
||||
* The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Delimiter is `,`.
|
||||
* For example, `--fields="name:string(12):notNull:unique"`
|
||||
* produces a string column of size 12 which is not null and unique values.
|
||||
*
|
||||
* Note: primary key is added automatically and is named id by default.
|
||||
* If you want to use another name you may specify it explicitly like
|
||||
* `--fields="id_key:primaryKey,name:string(12):notNull:unique"`
|
||||
* @since 2.0.7
|
||||
*/
|
||||
public $fields = [];
|
||||
/**
|
||||
* @var Connection|array|string the DB connection object or the application component ID of the DB connection to use
|
||||
* when applying migrations. Starting from version 2.0.3, this can also be a configuration array
|
||||
* for creating the object.
|
||||
*/
|
||||
public $db = 'db';
|
||||
/**
|
||||
* @var string the comment for the table being created.
|
||||
* @since 2.0.14
|
||||
*/
|
||||
public $comment = '';
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function options($actionID)
|
||||
{
|
||||
return array_merge(
|
||||
parent::options($actionID),
|
||||
['migrationTable', 'db'], // global for all actions
|
||||
$actionID === 'create'
|
||||
? ['templateFile', 'fields', 'useTablePrefix', 'comment']
|
||||
: []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.8
|
||||
*/
|
||||
public function optionAliases()
|
||||
{
|
||||
return array_merge(parent::optionAliases(), [
|
||||
'C' => 'comment',
|
||||
'f' => 'fields',
|
||||
'p' => 'migrationPath',
|
||||
't' => 'migrationTable',
|
||||
'F' => 'templateFile',
|
||||
'P' => 'useTablePrefix',
|
||||
'c' => 'compact',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked right before an action is to be executed (after all possible filters.)
|
||||
* It checks the existence of the [[migrationPath]].
|
||||
* @param Action $action the action to be executed.
|
||||
* @return bool whether the action should continue to be executed.
|
||||
*
|
||||
* @phpstan-param Action<$this> $action
|
||||
* @psalm-param Action<$this> $action
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
if (parent::beforeAction($action)) {
|
||||
$this->db = Instance::ensure($this->db, Connection::className());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new migration instance.
|
||||
* @param string $class the migration class name
|
||||
* @return \yii\db\Migration the migration instance
|
||||
*/
|
||||
protected function createMigration($class)
|
||||
{
|
||||
$this->includeMigrationFile($class);
|
||||
|
||||
return Yii::createObject([
|
||||
'class' => $class,
|
||||
'db' => $this->db,
|
||||
'compact' => $this->compact,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getMigrationHistory($limit)
|
||||
{
|
||||
if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
|
||||
$this->createMigrationHistoryTable();
|
||||
}
|
||||
$query = (new Query())
|
||||
->select(['version', 'apply_time'])
|
||||
->from($this->migrationTable)
|
||||
->orderBy(['apply_time' => SORT_DESC, 'version' => SORT_DESC]);
|
||||
|
||||
if (empty($this->migrationNamespaces)) {
|
||||
$query->limit($limit);
|
||||
$rows = $query->all($this->db);
|
||||
$history = ArrayHelper::map($rows, 'version', 'apply_time');
|
||||
unset($history[self::BASE_MIGRATION]);
|
||||
return $history;
|
||||
}
|
||||
|
||||
$rows = $query->all($this->db);
|
||||
|
||||
$history = [];
|
||||
foreach ($rows as $key => $row) {
|
||||
if ($row['version'] === self::BASE_MIGRATION) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/m?(\d{6}_?\d{6})(\D.*)?$/is', $row['version'], $matches)) {
|
||||
$time = str_replace('_', '', $matches[1]);
|
||||
$row['canonicalVersion'] = $time;
|
||||
} else {
|
||||
$row['canonicalVersion'] = $row['version'];
|
||||
}
|
||||
$row['apply_time'] = (int) $row['apply_time'];
|
||||
$history[] = $row;
|
||||
}
|
||||
|
||||
usort($history, function ($a, $b) {
|
||||
if ($a['apply_time'] === $b['apply_time']) {
|
||||
if (($compareResult = strcasecmp($b['canonicalVersion'], $a['canonicalVersion'])) !== 0) {
|
||||
return $compareResult;
|
||||
}
|
||||
|
||||
return strcasecmp($b['version'], $a['version']);
|
||||
}
|
||||
|
||||
return ($a['apply_time'] > $b['apply_time']) ? -1 : +1;
|
||||
});
|
||||
|
||||
$history = array_slice($history, 0, $limit);
|
||||
|
||||
$history = ArrayHelper::map($history, 'version', 'apply_time');
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the migration history table.
|
||||
*/
|
||||
protected function createMigrationHistoryTable()
|
||||
{
|
||||
$tableName = $this->db->schema->getRawTableName($this->migrationTable);
|
||||
$this->stdout("Creating migration history table \"$tableName\"...", Console::FG_YELLOW);
|
||||
$this->db->createCommand()->createTable($this->migrationTable, [
|
||||
'version' => 'varchar(' . static::MAX_NAME_LENGTH . ') NOT NULL PRIMARY KEY',
|
||||
'apply_time' => 'integer',
|
||||
])->execute();
|
||||
$this->db->createCommand()->insert($this->migrationTable, [
|
||||
'version' => self::BASE_MIGRATION,
|
||||
'apply_time' => time(),
|
||||
])->execute();
|
||||
$this->stdout("Done.\n", Console::FG_GREEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function addMigrationHistory($version)
|
||||
{
|
||||
$command = $this->db->createCommand();
|
||||
$command->insert($this->migrationTable, [
|
||||
'version' => $version,
|
||||
'apply_time' => time(),
|
||||
])->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.13
|
||||
*/
|
||||
protected function truncateDatabase()
|
||||
{
|
||||
$db = $this->db;
|
||||
$schemas = $db->schema->getTableSchemas();
|
||||
|
||||
// First drop all foreign keys,
|
||||
foreach ($schemas as $schema) {
|
||||
foreach ($schema->foreignKeys as $name => $foreignKey) {
|
||||
$db->createCommand()->dropForeignKey($name, $schema->name)->execute();
|
||||
$this->stdout("Foreign key $name dropped.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Then drop the tables:
|
||||
foreach ($schemas as $schema) {
|
||||
try {
|
||||
$db->createCommand()->dropTable($schema->name)->execute();
|
||||
$this->stdout("Table {$schema->name} dropped.\n");
|
||||
} catch (\Exception $e) {
|
||||
if ($this->isViewRelated($e->getMessage())) {
|
||||
$db->createCommand()->dropView($schema->name)->execute();
|
||||
$this->stdout("View {$schema->name} dropped.\n");
|
||||
} else {
|
||||
$this->stdout("Cannot drop {$schema->name} Table .\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the error message is related to deleting a view or not
|
||||
* @param string $errorMessage
|
||||
* @return bool
|
||||
*/
|
||||
private function isViewRelated($errorMessage)
|
||||
{
|
||||
$dropViewErrors = [
|
||||
'DROP VIEW to delete view', // SQLite
|
||||
'SQLSTATE[42S02]', // MySQL
|
||||
'is a view. Use DROP VIEW', // Microsoft SQL Server
|
||||
];
|
||||
|
||||
foreach ($dropViewErrors as $dropViewError) {
|
||||
if (strpos($errorMessage, $dropViewError) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function removeMigrationHistory($version)
|
||||
{
|
||||
$command = $this->db->createCommand();
|
||||
$command->delete($this->migrationTable, [
|
||||
'version' => $version,
|
||||
])->execute();
|
||||
}
|
||||
|
||||
private $_migrationNameLimit;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.13
|
||||
*/
|
||||
protected function getMigrationNameLimit()
|
||||
{
|
||||
if ($this->_migrationNameLimit !== null) {
|
||||
return $this->_migrationNameLimit;
|
||||
}
|
||||
$tableSchema = $this->db->schema ? $this->db->schema->getTableSchema($this->migrationTable, true) : null;
|
||||
if ($tableSchema !== null) {
|
||||
return $this->_migrationNameLimit = $tableSchema->columns['version']->size;
|
||||
}
|
||||
|
||||
return static::MAX_NAME_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes table name for generator.
|
||||
* When name is preceded with underscore name case is kept - otherwise it's converted from camelcase to underscored.
|
||||
* Last underscore is always trimmed so if there should be underscore at the end of name use two of them.
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
private function normalizeTableName($name)
|
||||
{
|
||||
if (substr($name, -1) === '_') {
|
||||
$name = substr($name, 0, -1);
|
||||
}
|
||||
|
||||
if (strncmp($name, '_', 1) === 0) {
|
||||
return substr($name, 1);
|
||||
}
|
||||
|
||||
return Inflector::underscore($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.8
|
||||
*/
|
||||
protected function generateMigrationSourceCode($params)
|
||||
{
|
||||
$parsedFields = $this->parseFields();
|
||||
$fields = $parsedFields['fields'];
|
||||
$foreignKeys = $parsedFields['foreignKeys'];
|
||||
|
||||
$name = $params['name'];
|
||||
if ($params['namespace']) {
|
||||
$name = substr($name, (strrpos($name, '\\') ?: -1) + 1);
|
||||
}
|
||||
|
||||
$templateFile = $this->templateFile;
|
||||
$table = null;
|
||||
if (preg_match('/^create_?junction_?(?:table)?_?(?:for)?(.+)_?and(.+)_?tables?$/i', $name, $matches)) {
|
||||
$templateFile = $this->generatorTemplateFiles['create_junction'];
|
||||
$firstTable = $this->normalizeTableName($matches[1]);
|
||||
$secondTable = $this->normalizeTableName($matches[2]);
|
||||
|
||||
$fields = array_merge(
|
||||
[
|
||||
[
|
||||
'property' => $firstTable . '_id',
|
||||
'decorators' => 'integer()',
|
||||
],
|
||||
[
|
||||
'property' => $secondTable . '_id',
|
||||
'decorators' => 'integer()',
|
||||
],
|
||||
],
|
||||
$fields,
|
||||
[
|
||||
[
|
||||
'property' => 'PRIMARY KEY(' .
|
||||
$firstTable . '_id, ' .
|
||||
$secondTable . '_id)',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$foreignKeys[$firstTable . '_id']['table'] = $firstTable;
|
||||
$foreignKeys[$secondTable . '_id']['table'] = $secondTable;
|
||||
$foreignKeys[$firstTable . '_id']['column'] = null;
|
||||
$foreignKeys[$secondTable . '_id']['column'] = null;
|
||||
$table = $firstTable . '_' . $secondTable;
|
||||
} elseif (preg_match('/^add(.+)columns?_?to(.+)table$/i', $name, $matches)) {
|
||||
$templateFile = $this->generatorTemplateFiles['add_column'];
|
||||
$table = $this->normalizeTableName($matches[2]);
|
||||
} elseif (preg_match('/^drop(.+)columns?_?from(.+)table$/i', $name, $matches)) {
|
||||
$templateFile = $this->generatorTemplateFiles['drop_column'];
|
||||
$table = $this->normalizeTableName($matches[2]);
|
||||
} elseif (preg_match('/^create(.+)table$/i', $name, $matches)) {
|
||||
$this->addDefaultPrimaryKey($fields);
|
||||
$templateFile = $this->generatorTemplateFiles['create_table'];
|
||||
$table = $this->normalizeTableName($matches[1]);
|
||||
} elseif (preg_match('/^drop(.+)table$/i', $name, $matches)) {
|
||||
$this->addDefaultPrimaryKey($fields);
|
||||
$templateFile = $this->generatorTemplateFiles['drop_table'];
|
||||
$table = $this->normalizeTableName($matches[1]);
|
||||
}
|
||||
|
||||
foreach ($foreignKeys as $column => $foreignKey) {
|
||||
$relatedColumn = $foreignKey['column'];
|
||||
$relatedTable = $foreignKey['table'];
|
||||
// Since 2.0.11 if related column name is not specified,
|
||||
// we're trying to get it from table schema
|
||||
// @see https://github.com/yiisoft/yii2/issues/12748
|
||||
if ($relatedColumn === null) {
|
||||
$relatedColumn = 'id';
|
||||
try {
|
||||
$this->db = Instance::ensure($this->db, Connection::className());
|
||||
$relatedTableSchema = $this->db->getTableSchema($relatedTable);
|
||||
if ($relatedTableSchema !== null) {
|
||||
$primaryKeyCount = count($relatedTableSchema->primaryKey);
|
||||
if ($primaryKeyCount === 1) {
|
||||
$relatedColumn = $relatedTableSchema->primaryKey[0];
|
||||
} elseif ($primaryKeyCount > 1) {
|
||||
$this->stdout("Related table for field \"{$column}\" exists, but primary key is composite. Default name \"id\" will be used for related field\n", Console::FG_YELLOW);
|
||||
} elseif ($primaryKeyCount === 0) {
|
||||
$this->stdout("Related table for field \"{$column}\" exists, but does not have a primary key. Default name \"id\" will be used for related field.\n", Console::FG_YELLOW);
|
||||
}
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
$this->stdout("Cannot initialize database component to try reading referenced table schema for field \"{$column}\". Default name \"id\" will be used for related field.\n", Console::FG_YELLOW);
|
||||
}
|
||||
}
|
||||
$foreignKeys[$column] = [
|
||||
'idx' => $this->generateTableName("idx-$table-$column"),
|
||||
'fk' => $this->generateTableName("fk-$table-$column"),
|
||||
'relatedTable' => $this->generateTableName($relatedTable),
|
||||
'relatedColumn' => $relatedColumn,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->renderFile(Yii::getAlias($templateFile), array_merge($params, [
|
||||
'table' => $this->generateTableName($table),
|
||||
'fields' => $fields,
|
||||
'foreignKeys' => $foreignKeys,
|
||||
'tableComment' => $this->comment,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* If `useTablePrefix` equals true, then the table name will contain the
|
||||
* prefix format.
|
||||
*
|
||||
* @param string $tableName the table name to generate.
|
||||
* @return string
|
||||
* @since 2.0.8
|
||||
*/
|
||||
protected function generateTableName($tableName)
|
||||
{
|
||||
if (!$this->useTablePrefix) {
|
||||
return $tableName;
|
||||
}
|
||||
|
||||
return '{{%' . $tableName . '}}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the command line migration fields.
|
||||
* @return array parse result with following fields:
|
||||
*
|
||||
* - fields: array, parsed fields
|
||||
* - foreignKeys: array, detected foreign keys
|
||||
*
|
||||
* @since 2.0.7
|
||||
*/
|
||||
protected function parseFields()
|
||||
{
|
||||
$fields = [];
|
||||
$foreignKeys = [];
|
||||
|
||||
foreach ($this->fields as $index => $field) {
|
||||
$chunks = $this->splitFieldIntoChunks($field);
|
||||
$property = array_shift($chunks);
|
||||
|
||||
foreach ($chunks as $i => &$chunk) {
|
||||
if (strncmp($chunk, 'foreignKey', 10) === 0) {
|
||||
preg_match('/foreignKey\((\w*)\s?(\w*)\)/', $chunk, $matches);
|
||||
$foreignKeys[$property] = [
|
||||
'table' => isset($matches[1])
|
||||
? $matches[1]
|
||||
: preg_replace('/_id$/', '', $property),
|
||||
'column' => !empty($matches[2])
|
||||
? $matches[2]
|
||||
: null,
|
||||
];
|
||||
|
||||
unset($chunks[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match('/^(.+?)\(([^(]+)\)$/', $chunk)) {
|
||||
$chunk .= '()';
|
||||
}
|
||||
}
|
||||
$fields[] = [
|
||||
'property' => $property,
|
||||
'decorators' => implode('->', $chunks),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'fields' => $fields,
|
||||
'foreignKeys' => $foreignKeys,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits field into chunks
|
||||
*
|
||||
* @param string $field
|
||||
* @return string[]|false
|
||||
*/
|
||||
protected function splitFieldIntoChunks($field)
|
||||
{
|
||||
$originalDefaultValue = null;
|
||||
$defaultValue = null;
|
||||
preg_match_all('/defaultValue\(["\'].*?:?.*?["\']\)/', $field, $matches, PREG_SET_ORDER, 0);
|
||||
if (isset($matches[0][0])) {
|
||||
$originalDefaultValue = $matches[0][0];
|
||||
$defaultValue = str_replace(':', '{{colon}}', $originalDefaultValue);
|
||||
$field = str_replace($originalDefaultValue, $defaultValue, $field);
|
||||
}
|
||||
|
||||
$chunks = preg_split('/\s?:\s?/', $field);
|
||||
|
||||
if (is_array($chunks) && $defaultValue !== null && $originalDefaultValue !== null) {
|
||||
foreach ($chunks as $key => $chunk) {
|
||||
$chunks[$key] = str_replace($defaultValue, $originalDefaultValue, $chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return $chunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default primary key to fields list if there's no primary key specified.
|
||||
* @param array $fields parsed fields
|
||||
* @since 2.0.7
|
||||
*/
|
||||
protected function addDefaultPrimaryKey(&$fields)
|
||||
{
|
||||
foreach ($fields as $field) {
|
||||
if ($field['property'] === 'id' || false !== strripos($field['decorators'], 'primarykey()')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
array_unshift($fields, ['property' => 'id', 'decorators' => 'primaryKey()']);
|
||||
}
|
||||
}
|
||||
142
vendor/yiisoft/yii2/console/controllers/ServeController.php
vendored
Normal file
142
vendor/yiisoft/yii2/console/controllers/ServeController.php
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\controllers;
|
||||
|
||||
use Yii;
|
||||
use yii\console\Application;
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
use yii\helpers\Console;
|
||||
|
||||
/**
|
||||
* Runs PHP built-in web server.
|
||||
*
|
||||
* In order to access server from remote machines use 0.0.0.0:8000. That is especially useful when running server in
|
||||
* a virtual machine.
|
||||
*
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
* @since 2.0.7
|
||||
*
|
||||
* @template T of Application
|
||||
* @extends Controller<T>
|
||||
*/
|
||||
class ServeController extends Controller
|
||||
{
|
||||
public const EXIT_CODE_NO_DOCUMENT_ROOT = 2;
|
||||
public const EXIT_CODE_NO_ROUTING_FILE = 3;
|
||||
public const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_SERVER = 4;
|
||||
public const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS = 5;
|
||||
/**
|
||||
* @var int port to serve on.
|
||||
*/
|
||||
public $port = 8080;
|
||||
/**
|
||||
* @var string path or [path alias](guide:concept-aliases) to directory to serve
|
||||
*/
|
||||
public $docroot = '@app/web';
|
||||
/**
|
||||
* @var string path or [path alias](guide:concept-aliases) to router script.
|
||||
* See https://www.php.net/manual/en/features.commandline.webserver.php
|
||||
*/
|
||||
public $router;
|
||||
|
||||
|
||||
/**
|
||||
* Runs PHP built-in web server.
|
||||
*
|
||||
* @param string $address address to serve on. Either "host" or "host:port".
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function actionIndex($address = 'localhost')
|
||||
{
|
||||
$documentRoot = Yii::getAlias($this->docroot);
|
||||
$router = $this->router !== null ? Yii::getAlias($this->router) : null;
|
||||
|
||||
if (strpos($address, ':') === false) {
|
||||
$address = $address . ':' . $this->port;
|
||||
}
|
||||
|
||||
if (!is_dir($documentRoot)) {
|
||||
$this->stdout("Document root \"$documentRoot\" does not exist.\n", Console::FG_RED);
|
||||
return self::EXIT_CODE_NO_DOCUMENT_ROOT;
|
||||
}
|
||||
|
||||
if ($this->isAddressTaken($address)) {
|
||||
$this->stdout("http://$address is taken by another process.\n", Console::FG_RED);
|
||||
return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
|
||||
}
|
||||
|
||||
if ($this->router !== null && !file_exists($router)) {
|
||||
$this->stdout("Routing file \"$router\" does not exist.\n", Console::FG_RED);
|
||||
return self::EXIT_CODE_NO_ROUTING_FILE;
|
||||
}
|
||||
|
||||
$this->stdout("Server started on http://{$address}/\n");
|
||||
$this->stdout("Document root is \"{$documentRoot}\"\n");
|
||||
if ($this->router) {
|
||||
$this->stdout("Routing file is \"$router\"\n");
|
||||
}
|
||||
$this->stdout("Quit the server with CTRL-C or COMMAND-C.\n");
|
||||
|
||||
$command = '"' . PHP_BINARY . '"' . " -S {$address} -t \"{$documentRoot}\"";
|
||||
|
||||
if ($this->router !== null && $router !== '') {
|
||||
$command .= " \"{$router}\"";
|
||||
}
|
||||
|
||||
$this->runCommand($command);
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function options($actionID)
|
||||
{
|
||||
return array_merge(parent::options($actionID), [
|
||||
'docroot',
|
||||
'router',
|
||||
'port',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.8
|
||||
*/
|
||||
public function optionAliases()
|
||||
{
|
||||
return array_merge(parent::optionAliases(), [
|
||||
't' => 'docroot',
|
||||
'p' => 'port',
|
||||
'r' => 'router',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address server address
|
||||
* @return bool if address is already in use
|
||||
*/
|
||||
protected function isAddressTaken($address)
|
||||
{
|
||||
list($hostname, $port) = explode(':', $address);
|
||||
$fp = @fsockopen($hostname, $port, $errno, $errstr, 3);
|
||||
if ($fp === false) {
|
||||
return false;
|
||||
}
|
||||
fclose($fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function runCommand($command)
|
||||
{
|
||||
passthru($command);
|
||||
}
|
||||
}
|
||||
2
vendor/yiisoft/yii2/console/runtime/.gitignore
vendored
Normal file
2
vendor/yiisoft/yii2/console/runtime/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
441
vendor/yiisoft/yii2/console/widgets/Table.php
vendored
Normal file
441
vendor/yiisoft/yii2/console/widgets/Table.php
vendored
Normal file
@ -0,0 +1,441 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license https://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\console\widgets;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Widget;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\helpers\Console;
|
||||
|
||||
/**
|
||||
* Table class displays a table in console.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```
|
||||
* $table = new Table();
|
||||
*
|
||||
* echo $table
|
||||
* ->setHeaders(['test1', 'test2', 'test3'])
|
||||
* ->setRows([
|
||||
* ['col1', 'col2', 'col3'],
|
||||
* ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
|
||||
* ])
|
||||
* ->run();
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* echo Table::widget([
|
||||
* 'headers' => ['test1', 'test2', 'test3'],
|
||||
* 'rows' => [
|
||||
* ['col1', 'col2', 'col3'],
|
||||
* ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
|
||||
* ],
|
||||
* ]);
|
||||
*
|
||||
* @property-write array $chars Table chars.
|
||||
* @property-write array $headers Table headers.
|
||||
* @property-write string $listPrefix List prefix.
|
||||
* @property-write array $rows Table rows.
|
||||
* @property-write int $screenWidth Screen width.
|
||||
*
|
||||
* @author Daniel Gomez Pan <pana_1990@hotmail.com>
|
||||
* @since 2.0.13
|
||||
*/
|
||||
class Table extends Widget
|
||||
{
|
||||
public const DEFAULT_CONSOLE_SCREEN_WIDTH = 120;
|
||||
public const CONSOLE_SCROLLBAR_OFFSET = 3;
|
||||
public const CHAR_TOP = 'top';
|
||||
public const CHAR_TOP_MID = 'top-mid';
|
||||
public const CHAR_TOP_LEFT = 'top-left';
|
||||
public const CHAR_TOP_RIGHT = 'top-right';
|
||||
public const CHAR_BOTTOM = 'bottom';
|
||||
public const CHAR_BOTTOM_MID = 'bottom-mid';
|
||||
public const CHAR_BOTTOM_LEFT = 'bottom-left';
|
||||
public const CHAR_BOTTOM_RIGHT = 'bottom-right';
|
||||
public const CHAR_LEFT = 'left';
|
||||
public const CHAR_LEFT_MID = 'left-mid';
|
||||
public const CHAR_MID = 'mid';
|
||||
public const CHAR_MID_MID = 'mid-mid';
|
||||
public const CHAR_RIGHT = 'right';
|
||||
public const CHAR_RIGHT_MID = 'right-mid';
|
||||
public const CHAR_MIDDLE = 'middle';
|
||||
/**
|
||||
* @var array table headers
|
||||
* @since 2.0.19
|
||||
*/
|
||||
protected $headers = [];
|
||||
/**
|
||||
* @var array table rows
|
||||
* @since 2.0.19
|
||||
*/
|
||||
protected $rows = [];
|
||||
/**
|
||||
* @var array table chars
|
||||
* @since 2.0.19
|
||||
*/
|
||||
protected $chars = [
|
||||
self::CHAR_TOP => '═',
|
||||
self::CHAR_TOP_MID => '╤',
|
||||
self::CHAR_TOP_LEFT => '╔',
|
||||
self::CHAR_TOP_RIGHT => '╗',
|
||||
self::CHAR_BOTTOM => '═',
|
||||
self::CHAR_BOTTOM_MID => '╧',
|
||||
self::CHAR_BOTTOM_LEFT => '╚',
|
||||
self::CHAR_BOTTOM_RIGHT => '╝',
|
||||
self::CHAR_LEFT => '║',
|
||||
self::CHAR_LEFT_MID => '╟',
|
||||
self::CHAR_MID => '─',
|
||||
self::CHAR_MID_MID => '┼',
|
||||
self::CHAR_RIGHT => '║',
|
||||
self::CHAR_RIGHT_MID => '╢',
|
||||
self::CHAR_MIDDLE => '│',
|
||||
];
|
||||
/**
|
||||
* @var array table column widths
|
||||
* @since 2.0.19
|
||||
*/
|
||||
protected $columnWidths = [];
|
||||
/**
|
||||
* @var int screen width
|
||||
* @since 2.0.19
|
||||
*/
|
||||
protected $screenWidth;
|
||||
/**
|
||||
* @var string list prefix
|
||||
* @since 2.0.19
|
||||
*/
|
||||
protected $listPrefix = '• ';
|
||||
|
||||
|
||||
/**
|
||||
* Set table headers.
|
||||
*
|
||||
* @param array $headers table headers
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaders(array $headers)
|
||||
{
|
||||
$this->headers = array_values($headers);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set table rows.
|
||||
*
|
||||
* @param array $rows table rows
|
||||
* @return $this
|
||||
*/
|
||||
public function setRows(array $rows)
|
||||
{
|
||||
$this->rows = array_map(function ($row) {
|
||||
return array_map(function ($value) {
|
||||
return empty($value) && !is_numeric($value)
|
||||
? ' '
|
||||
: (is_array($value)
|
||||
? array_values($value)
|
||||
: $value);
|
||||
}, array_values($row));
|
||||
}, $rows);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set table chars.
|
||||
*
|
||||
* @param array $chars table chars
|
||||
* @return $this
|
||||
*/
|
||||
public function setChars(array $chars)
|
||||
{
|
||||
$this->chars = $chars;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set screen width.
|
||||
*
|
||||
* @param int $width screen width
|
||||
* @return $this
|
||||
*/
|
||||
public function setScreenWidth($width)
|
||||
{
|
||||
$this->screenWidth = $width;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set list prefix.
|
||||
*
|
||||
* @param string $listPrefix list prefix
|
||||
* @return $this
|
||||
*/
|
||||
public function setListPrefix($listPrefix)
|
||||
{
|
||||
$this->listPrefix = $listPrefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the rendered table
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->calculateRowsSize();
|
||||
$headerCount = count($this->headers);
|
||||
|
||||
$buffer = $this->renderSeparator(
|
||||
$this->chars[self::CHAR_TOP_LEFT],
|
||||
$this->chars[self::CHAR_TOP_MID],
|
||||
$this->chars[self::CHAR_TOP],
|
||||
$this->chars[self::CHAR_TOP_RIGHT]
|
||||
);
|
||||
// Header
|
||||
if ($headerCount > 0) {
|
||||
$buffer .= $this->renderRow(
|
||||
$this->headers,
|
||||
$this->chars[self::CHAR_LEFT],
|
||||
$this->chars[self::CHAR_MIDDLE],
|
||||
$this->chars[self::CHAR_RIGHT]
|
||||
);
|
||||
}
|
||||
|
||||
// Content
|
||||
foreach ($this->rows as $i => $row) {
|
||||
if ($i > 0 || $headerCount > 0) {
|
||||
$buffer .= $this->renderSeparator(
|
||||
$this->chars[self::CHAR_LEFT_MID],
|
||||
$this->chars[self::CHAR_MID_MID],
|
||||
$this->chars[self::CHAR_MID],
|
||||
$this->chars[self::CHAR_RIGHT_MID]
|
||||
);
|
||||
}
|
||||
$buffer .= $this->renderRow(
|
||||
$row,
|
||||
$this->chars[self::CHAR_LEFT],
|
||||
$this->chars[self::CHAR_MIDDLE],
|
||||
$this->chars[self::CHAR_RIGHT]
|
||||
);
|
||||
}
|
||||
|
||||
$buffer .= $this->renderSeparator(
|
||||
$this->chars[self::CHAR_BOTTOM_LEFT],
|
||||
$this->chars[self::CHAR_BOTTOM_MID],
|
||||
$this->chars[self::CHAR_BOTTOM],
|
||||
$this->chars[self::CHAR_BOTTOM_RIGHT]
|
||||
);
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a row of data into a string.
|
||||
*
|
||||
* @param array $row row of data
|
||||
* @param string $spanLeft character for left border
|
||||
* @param string $spanMiddle character for middle border
|
||||
* @param string $spanRight character for right border
|
||||
* @return string
|
||||
* @see \yii\console\widgets\Table::render()
|
||||
*/
|
||||
protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight)
|
||||
{
|
||||
$size = $this->columnWidths;
|
||||
|
||||
$buffer = '';
|
||||
$arrayPointer = [];
|
||||
$renderedChunkTexts = [];
|
||||
for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
|
||||
$buffer .= $spanLeft . ' ';
|
||||
foreach ($size as $index => $cellSize) {
|
||||
$cell = isset($row[$index]) ? $row[$index] : null;
|
||||
$prefix = '';
|
||||
if ($index !== 0) {
|
||||
$buffer .= $spanMiddle . ' ';
|
||||
}
|
||||
|
||||
$arrayFromMultilineString = false;
|
||||
if (is_string($cell)) {
|
||||
$cellLines = explode(PHP_EOL, $cell);
|
||||
if (count($cellLines) > 1) {
|
||||
$cell = $cellLines;
|
||||
$arrayFromMultilineString = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($cell)) {
|
||||
if (empty($renderedChunkTexts[$index])) {
|
||||
$renderedChunkTexts[$index] = '';
|
||||
$start = 0;
|
||||
$prefix = $arrayFromMultilineString ? '' : $this->listPrefix;
|
||||
if (!isset($arrayPointer[$index])) {
|
||||
$arrayPointer[$index] = 0;
|
||||
}
|
||||
} else {
|
||||
$start = mb_strwidth($renderedChunkTexts[$index], Yii::$app->charset);
|
||||
}
|
||||
$chunk = Console::ansiColorizedSubstr(
|
||||
$cell[$arrayPointer[$index]],
|
||||
$start,
|
||||
$cellSize - 2 - Console::ansiStrwidth($prefix)
|
||||
);
|
||||
$renderedChunkTexts[$index] .= Console::stripAnsiFormat($chunk);
|
||||
$fullChunkText = Console::stripAnsiFormat($cell[$arrayPointer[$index]]);
|
||||
if (isset($cell[$arrayPointer[$index] + 1]) && $renderedChunkTexts[$index] === $fullChunkText) {
|
||||
$arrayPointer[$index]++;
|
||||
$renderedChunkTexts[$index] = '';
|
||||
}
|
||||
} else {
|
||||
$chunk = Console::ansiColorizedSubstr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2);
|
||||
}
|
||||
$chunk = $prefix . $chunk;
|
||||
$repeat = $cellSize - Console::ansiStrwidth($chunk) - 1;
|
||||
$buffer .= $chunk;
|
||||
if ($repeat >= 0) {
|
||||
$buffer .= str_repeat(' ', $repeat);
|
||||
}
|
||||
}
|
||||
$buffer .= "$spanRight\n";
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders separator.
|
||||
*
|
||||
* @param string $spanLeft character for left border
|
||||
* @param string $spanMid character for middle border
|
||||
* @param string $spanMidMid character for middle-middle border
|
||||
* @param string $spanRight character for right border
|
||||
* @return string the generated separator row
|
||||
* @see \yii\console\widgets\Table::render()
|
||||
*/
|
||||
protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight)
|
||||
{
|
||||
$separator = $spanLeft;
|
||||
foreach ($this->columnWidths as $index => $rowSize) {
|
||||
if ($index !== 0) {
|
||||
$separator .= $spanMid;
|
||||
}
|
||||
$separator .= str_repeat($spanMidMid, $rowSize);
|
||||
}
|
||||
$separator .= $spanRight . "\n";
|
||||
return $separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the size of rows to draw anchor of columns in console.
|
||||
*
|
||||
* @see \yii\console\widgets\Table::render()
|
||||
*/
|
||||
protected function calculateRowsSize()
|
||||
{
|
||||
$this->columnWidths = $columns = [];
|
||||
$totalWidth = 0;
|
||||
$screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET;
|
||||
|
||||
$headerCount = count($this->headers);
|
||||
if (empty($this->rows)) {
|
||||
$rowColCount = 0;
|
||||
} else {
|
||||
$rowColCount = max(array_map('count', $this->rows));
|
||||
}
|
||||
$count = max($headerCount, $rowColCount);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$columns[] = ArrayHelper::getColumn($this->rows, $i);
|
||||
if ($i < $headerCount) {
|
||||
$columns[$i][] = $this->headers[$i];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$columnWidth = max(array_map(function ($val) {
|
||||
if (is_array($val)) {
|
||||
return max(array_map('yii\helpers\Console::ansiStrwidth', $val)) + Console::ansiStrwidth($this->listPrefix);
|
||||
}
|
||||
if (is_string($val)) {
|
||||
return max(array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val)));
|
||||
}
|
||||
return Console::ansiStrwidth($val);
|
||||
}, $column)) + 2;
|
||||
$this->columnWidths[] = $columnWidth;
|
||||
$totalWidth += $columnWidth;
|
||||
}
|
||||
|
||||
if ($totalWidth > $screenWidth) {
|
||||
$minWidth = 3;
|
||||
$fixWidths = [];
|
||||
$relativeWidth = $screenWidth / $totalWidth;
|
||||
foreach ($this->columnWidths as $j => $width) {
|
||||
$scaledWidth = (int) ($width * $relativeWidth);
|
||||
if ($scaledWidth < $minWidth) {
|
||||
$fixWidths[$j] = 3;
|
||||
}
|
||||
}
|
||||
|
||||
$totalFixWidth = array_sum($fixWidths);
|
||||
$relativeWidth = ($screenWidth - $totalFixWidth) / ($totalWidth - $totalFixWidth);
|
||||
foreach ($this->columnWidths as $j => $width) {
|
||||
if (!array_key_exists($j, $fixWidths)) {
|
||||
$this->columnWidths[$j] = (int) ($width * $relativeWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the height of a row.
|
||||
*
|
||||
* @param array $row
|
||||
* @return int maximum row per cell
|
||||
* @see \yii\console\widgets\Table::render()
|
||||
*/
|
||||
protected function calculateRowHeight($row)
|
||||
{
|
||||
$rowsPerCell = array_map(function ($size, $columnWidth) {
|
||||
if (is_array($columnWidth)) {
|
||||
$rows = 0;
|
||||
foreach ($columnWidth as $width) {
|
||||
$rows += $size == 2 ? 0 : ceil($width / ($size - 2));
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
return $size == 2 || $columnWidth == 0 ? 0 : ceil($columnWidth / ($size - 2));
|
||||
}, $this->columnWidths, array_map(function ($val) {
|
||||
if (is_array($val)) {
|
||||
return array_map('yii\helpers\Console::ansiStrwidth', $val);
|
||||
}
|
||||
if (is_string($val)) {
|
||||
return array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val));
|
||||
}
|
||||
return Console::ansiStrwidth($val);
|
||||
}, $row));
|
||||
return max($rowsPerCell);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting screen width.
|
||||
* If it is not able to determine screen width, default value `123` will be set.
|
||||
*
|
||||
* @return int screen width
|
||||
*/
|
||||
protected function getScreenWidth()
|
||||
{
|
||||
if (!$this->screenWidth) {
|
||||
$size = Console::getScreenSize();
|
||||
$this->screenWidth = isset($size[0])
|
||||
? $size[0]
|
||||
: self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET;
|
||||
}
|
||||
return $this->screenWidth;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user