正文
获取用户请求
PHP并未提供集中的、统一的界面以获取用户请求,而是分散在 $_SERVER
、 $_POST
等变量和其他代码中。 这种局面Yii是怎么处理的呢?
那么对于任何Yii应用而言,初始化后第一件正事,就是获取用户请求。 这个代码在 yii\base\Application::run()
中:
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
// 获取用户请求,并进行处理,处理的过程也是产生响应内容的过程
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
// 将响应内容发送回用户
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
你可能会说$this->getRequest()
就是用于获取用户请求的。其实这是一个getter,用于获取Application的request组件 (component) 。
Yii用这个组件来代表用户请求, 他承载着所有的用户输入信息。
我们知道,Yii应用有命令行(Console)应用和Web应用之分。因此,这个Request类其实涉及到了以下的类:
yii\base\Request
Request类基类yii\console\Request
表示Console应用的的Requestyii\web\Request
表示Web应用的Request
基类Request
基类是对Console应用和Web应用Request的抽象,他仅仅定义了两个属性和一个抽象函数:
namespace yii\base;
use Yii;
abstract class Request extends Component
{
// 属性scriptFile,用于表示入口脚本
private $_scriptFile;
// 属性isConsoleRequest,用于表示是否是命令行应用
private $_isConsoleRequest;
// 抽象函数,要求子类来实现
// 这个函数的功能主要是为了把Request解析成路由和相应的参数
abstract public function resolve();
// isConsoleRequest属性的getter函数
// 使用 PHP_SAPI 常量判断当前应用是否是命令行应用
public function getIsConsoleRequest()
{
// PHP_SAPI 不为 'cli' 的,则不是命令行
return $this->_isConsoleRequest !== null ? $this->_isConsoleRequest : PHP_SAPI === 'cli';
}
// isConsoleRequest属性的setter函数
public function setIsConsoleRequest($value)
{
$this->_isConsoleRequest = $value;
}
// scriptFile属性的getter函数
// 通过 $_SERVER['SCRIPT_FILENAME'] 来获取入口脚本名
public function getScriptFile()
{
if ($this->_scriptFile === null) {
if (isset($_SERVER['SCRIPT_FILENAME'])) {
$this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
} else {
throw new InvalidConfigException('Unable to determine the entry script file path.');
}
}
return $this->_scriptFile;
}
// scriptFile属性的setter函数
public function setScriptFile($value)
{
$scriptFile = realpath(Yii::getAlias($value));
if ($scriptFile !== false && is_file($scriptFile)) {
$this->_scriptFile = $scriptFile;
} else {
throw new InvalidConfigException('Unable to determine the entry script file path.');
}
}
}
yii\base\Request
通过getter和setter提供了两个可读写的属性, isConsoleRequest 和 scriptFile 。
同时,要求子类实现一个 resolve()
方法。
基类的代码相对简单,主要涉及到PHP的一些知识,如 PHP_SAPI
、 $_SERVER['SCRIPT_FILENAME']
等。
命令行应用Request
命令行应用Request由 yii\console\Request
负责实现,相比较于 yii\base\Request
稍有丰富:
namespace yii\console;
class Request extends \yii\base\Request
{
// 属性 params,用于表示命令行参数
private $_params;
// params属性的getter函数
// 通过 $_SERVER['argv'] 来获取命令行参数
public function getParams()
{
if (!isset($this->_params)) {
if (isset($_SERVER['argv'])) {
$this->_params = $_SERVER['argv'];
// 删除数组的第一个元素,这个元素是PHP脚本名。
// 因此,属性params中全部是参数,不带脚本名
array_shift($this->_params);
} else {
$this->_params = [];
}
}
return $this->_params;
}
// params属性的setter函数
public function setParams($params)
{
$this->_params = $params;
}
// 父类抽象函数的实现
public function resolve()
{
// 获取全部的命令行参数
$rawParams = $this->getParams();
// 第一个命令行参数作为路由
if (isset($rawParams[0])) {
$route = $rawParams[0];
array_shift($rawParams);
} else {
$route = '';
}
$params = [];
// 遍历剩余的全部命令行参数
foreach ($rawParams as $param) {
// 正则匹配每一个参数
if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
// 参数名
$name = $matches[1];
// yii\console\Application::OPTION_APPCONFIG = 'appconfig'
if ($name !== Application::OPTION_APPCONFIG) {
$params[$name] = isset($matches[3]) ? $matches[3] : true;
}
// 无名参数,直接作为参数值
} else {
$params[] = $param;
}
}
return [$route, $params];
}
}
相比较于 yii\base\Request
, yii\console\Request
提供了一个 params 属性, 该属性以数组形式保存了入口脚本 Yii 的命令行参数。
这是通过 $_SERVER['argv']
获取的。 注意 params 属性不保存入口脚本名。入口脚本名由基类的 scriptName 属性保存。
同时, yii\console\Request
还实现了父类的 resolve()
抽象函数, 这个函数主要做了这么几件事:
- 将 params 属性的第一个元素作为路由。如果入口脚本未提供任何参数,也即 params 是个空数组, 那么将路由置为一个空字符串。
- 遍历 params 中剩余的参数,使用正则匹配Yii应用的参数名和参数值,看看是不是
--参数名=参数值
形式。 其中,以--
打头的任意字母、数字、下划线的组合,就是参数名。 紧跟参数名的 = 后面的内容,则为参数值。 对于仅有参数名,没有参数值的,视参数值为 true 。 - 如果正则匹配不成功,则将这个命令行参数作为Yii应用的一个无名参数的值。
- 如果第二步中的参数名为 appconfig 则忽略该参数,Console Application会专门针对该参数进行处理。
- 上面步骤中的参数和参数值,被保存进一个数组中。数组的键表示参数名,数组的值表示参数值。
- 最终
resolve()
返回一个数组,第一个元素是一个表示路由的字符串,第二元素则是参数数组。 该方法由Application在处理Request时调用。
关于 appconfig 参数的问题,只要在调用 yii 时,指定了 appconfig 参数, 就表明不使用默认的参数配置文件,
而使用该参数所指定的配置文件。相关的代码在 yii\console\Application
中:
// 定义一个常量
const OPTION_APPCONFIG = 'appconfig';
// yii\console\Application类的构造函数
public function __construct($config = [])
{
// 重点看这句,会调用loadConfig() 成员函数
$config = $this->loadConfig($config);
parent::__construct($config);
}
// 如果指定的配置文件存在,那么返回其配置数组
// 否则,返回构造函数调用时的数组
protected function loadConfig($config)
{
if (!empty($_SERVER['argv'])) {
// 设定了一个字符串 "--appconfig="
$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);
} else {
die("The configuration file does not exist: $path\n");
}
}
}
}
return $config;
}
讲完了Request基类和命令行应用的Request只是热身而已,接下来要讲的Web应用Request才是重头。
路由处理
在 yii\base\Application::run()
中:
// 获取用户请求,并进行处理,处理的过程也是产生响应内容的过程
$response = $this->handleRequest($this->getRequest());
handleRequest()
在 yii\base\Application::run()
中是一个 abstract
类,由继承类实现:
/**
* Handles the specified request.
*
* This method should return an instance of [[Response]] or its child class
* which represents the handling result of the request.
*
* @param Request $request the request to be handled
* @return Response the resulting response
*/
abstract public function handleRequest($request);
web访问实现
在 yii\web\Application
中:
/**
* Handles the specified request.
* @param Request $request the request to be handled
* @return Response the resulting response
* @throws NotFoundHttpException if the requested route is invalid
*/
public function handleRequest($request)
{
if (empty($this->catchAll)) {
try {
list($route, $params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
$url = $e->url;
if (is_array($url)) {
if (isset($url[0])) {
// ensure the route is absolute
$url[0] = '/' . ltrim($url[0], '/');
}
$url += $request->getQueryParams();
}
return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} else {
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::debug("Route requested: '$route'", __METHOD__);
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);
if ($result instanceof Response) {
return $result;
}
$response = $this->getResponse();
if ($result !== null) {
$response->data = $result;
}
return $response;
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
}
}
在 yii\web\Request
中resolve()
解析请求,返回 $route
路由 和 $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 NotFoundHttpException if the request cannot be resolved.
*/
public function resolve()
{
$result = Yii::$app->getUrlManager()->parseRequest($this);
if ($result !== false) {
list($route, $params) = $result;
if ($this->_queryParams === null) {
$_GET = $params + $_GET; // preserve numeric keys
} else {
$this->_queryParams = $params + $this->_queryParams;
}
return [$route, $this->getQueryParams()];
}
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
}
Yii::$app->getUrlManager()->parseRequest($this)
就是 调用 yii\web\UrlManager
的 parseRequest()
解析请求:
/**
* Parses the user request.
* @param Request $request the request component
* @return array|bool the route and the associated parameters. The latter is always empty
* if [[enablePrettyUrl]] is `false`. `false` is returned if the current request cannot be successfully parsed.
*/
public function parseRequest($request)
{
/* URL美化功能启用 */
if ($this->enablePrettyUrl) {
/* @var $rule UrlRule */
foreach ($this->rules as $rule) {
$result = $rule->parseRequest($this, $request);
if (YII_DEBUG) {
Yii::debug([
'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
'match' => $result !== false,
'parent' => null,
], __METHOD__);
}
if ($result !== false) {
return $result;
}
}
if ($this->enableStrictParsing) {
return false;
}
Yii::debug('No matching URL rules. Using default URL parsing logic.', __METHOD__);
$suffix = (string) $this->suffix;
$pathInfo = $request->getPathInfo();
$normalized = false;
if ($this->normalizer !== false) {
$pathInfo = $this->normalizer->normalizePathInfo($pathInfo, $suffix, $normalized);
}
if ($suffix !== '' && $pathInfo !== '') {
$n = strlen($this->suffix);
if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) {
$pathInfo = substr($pathInfo, 0, -$n);
if ($pathInfo === '') {
// suffix alone is not allowed
return false;
}
} else {
// suffix doesn't match
return false;
}
}
if ($normalized) {
// pathInfo was changed by normalizer - we need also normalize route
return $this->normalizer->normalizeRoute([$pathInfo, []]);
}
return [$pathInfo, []];
}
/* URL美化功能未启用 */
Yii::debug('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
$route = $request->getQueryParam($this->routeParam, '');
if (is_array($route)) {
$route = '';
}
return [(string) $route, []];
}
里面 $rule->parseRequest($this, $request)
再调用 yii\web\UrlRule
的 parseRequest()
:
/**
* Parses the given request and returns the corresponding route and parameters.
* @param UrlManager $manager the URL manager
* @param Request $request the request component
* @return array|bool the parsing result. The route and the parameters are returned as an array.
* If `false`, it means this rule cannot be used to parse this path info.
*/
public function parseRequest($manager, $request)
{
if ($this->mode === self::CREATION_ONLY) {
return false;
}
if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) {
return false;
}
$suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix);
$pathInfo = $request->getPathInfo();
$normalized = false;
if ($this->hasNormalizer($manager)) {
$pathInfo = $this->getNormalizer($manager)->normalizePathInfo($pathInfo, $suffix, $normalized);
}
if ($suffix !== '' && $pathInfo !== '') {
$n = strlen($suffix);
if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
$pathInfo = substr($pathInfo, 0, -$n);
if ($pathInfo === '') {
// suffix alone is not allowed
return false;
}
} else {
return false;
}
}
if ($this->host !== null) {
$pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo);
}
if (!preg_match($this->pattern, $pathInfo, $matches)) {
return false;
}
$matches = $this->substitutePlaceholderNames($matches);
foreach ($this->defaults as $name => $value) {
if (!isset($matches[$name]) || $matches[$name] === '') {
$matches[$name] = $value;
}
}
$params = $this->defaults;
$tr = [];
foreach ($matches as $name => $value) {
if (isset($this->_routeParams[$name])) {
$tr[$this->_routeParams[$name]] = $value;
unset($params[$name]);
} elseif (isset($this->_paramRules[$name])) {
$params[$name] = $value;
}
}
if ($this->_routeRule !== null) {
$route = strtr($this->route, $tr);
} else {
$route = $this->route;
}
Yii::debug("Request parsed with URL rule: {$this->name}", __METHOD__);
if ($normalized) {
// pathInfo was changed by normalizer - we need also normalize route
return $this->getNormalizer($manager)->normalizeRoute([$route, $params]);
}
return [$route, $params];
}
在 yii\web\Application
的 handleRequest()
中 $result = $this->runAction($route, $params);
运行 yii\base\Module
的处理方法:
/**
* 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]].
* @param string $route the route that specifies the action.
* @param array $params the parameters to be passed to the action
* @return mixed the result of the action.
* @throws InvalidRouteException if the requested route cannot be resolved into an action successfully.
*/
public function runAction($route, $params = [])
{
$parts = $this->createController($route);
if (is_array($parts)) {
/* @var $controller Controller */
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
$result = $controller->runAction($actionID, $params);
if ($oldController !== null) {
Yii::$app->controller = $oldController;
}
return $result;
}
$id = $this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
}
/**
* Creates a controller instance based on the given route.
*
* The route should be relative to this module. The method implements the following algorithm
* to resolve the given route:
*
* 1. If the route is empty, use [[defaultRoute]];
* 2. If the first segment of the route is found in [[controllerMap]], create a controller
* based on the corresponding configuration found in [[controllerMap]];
* 3. If the first segment of the route is a valid module ID as declared in [[modules]],
* call the module's `createController()` with the rest part of the route;
* 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
* or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
*
* If any of the above steps resolves into a controller, it is returned together with the rest
* part of the route which will be treated as the action ID. Otherwise, `false` will be returned.
*
* @param string $route the route consisting of module, controller and action IDs.
* @return array|bool If the controller is created successfully, it will be returned together
* with the requested action ID. Otherwise `false` will be returned.
* @throws InvalidConfigException if the controller class and its file do not match.
*/
public function createController($route)
{
if ($route === '') {
$route = $this->defaultRoute;
}
// double slashes or leading/ending slashes may cause substr problem
$route = trim($route, '/');
if (strpos($route, '//') !== false) {
return false;
}
if (strpos($route, '/') !== false) {
list($id, $route) = explode('/', $route, 2);
} else {
$id = $route;
$route = '';
}
// module and controller map take precedence
if (isset($this->controllerMap[$id])) {
$controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
return [$controller, $route];
}
$module = $this->getModule($id);
if ($module !== null) {
return $module->createController($route);
}
if (($pos = strrpos($route, '/')) !== false) {
$id .= '/' . substr($route, 0, $pos);
$route = substr($route, $pos + 1);
}
$controller = $this->createControllerByID($id);
if ($controller === null && $route !== '') {
$controller = $this->createControllerByID($id . '/' . $route);
$route = '';
}
return $controller === null ? false : [$controller, $route];
}
yii\base\Controller
中:
/**
* Runs an action within this controller 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 mixed the result of the action.
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @see createAction()
*/
public function runAction($id, $params = [])
{
$action = $this->createAction($id);
if ($action === null) {
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
Yii::debug('Route to run: ' . $action->getUniqueId(), __METHOD__);
if (Yii::$app->requestedAction === null) {
Yii::$app->requestedAction = $action;
}
$oldAction = $this->action;
$this->action = $action;
$modules = [];
$runAction = true;
// call beforeAction on modules
foreach ($this->getModules() as $module) {
if ($module->beforeAction($action)) {
array_unshift($modules, $module);
} else {
$runAction = false;
break;
}
}
$result = null;
if ($runAction && $this->beforeAction($action)) {
// run the action
$result = $action->runWithParams($params);
$result = $this->afterAction($action, $result);
// call afterAction on modules
foreach ($modules as $module) {
/* @var $module Module */
$result = $module->afterAction($action, $result);
}
}
if ($oldAction !== null) {
$this->action = $oldAction;
}
return $result;
}
/**
* Creates an action based on the given action ID.
* The method first checks if the action ID has been declared in [[actions()]]. If so,
* it will use the configuration declared there to create the action object.
* If not, it will look for a controller method whose name is in the format of `actionXyz`
* where `xyz` is the action ID. If found, an [[InlineAction]] representing that
* method will be created and returned.
* @param string $id the action ID.
* @return Action|null the newly created action instance. Null if the ID doesn't resolve into any action.
*/
public function createAction($id)
{
if ($id === '') {
$id = $this->defaultAction;
}
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
return Yii::createObject($actionMap[$id], [$id, $this]);
}
if (preg_match('/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/', $id)) {
$methodName = 'action' . str_replace(' ', '', ucwords(str_replace('-', ' ', $id)));
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->isPublic() && $method->getName() === $methodName) {
return new InlineAction($id, $this, $methodName);
}
}
}
return null;
}
yii\base\Action
中:
/**
* Runs this action with the specified parameters.
* This method is mainly invoked by the controller.
*
* @param array $params the parameters to be bound to the action's run() method.
* @return mixed the result of the action
* @throws InvalidConfigException if the action class does not have a run() method
*/
public function runWithParams($params)
{
if (!method_exists($this, 'run')) {
throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
}
$args = $this->controller->bindActionParams($this, $params);
Yii::debug('Running action: ' . get_class($this) . '::run(), invoked by ' . get_class($this->controller), __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}
if ($this->beforeRun()) {
$result = call_user_func_array([$this, 'run'], $args);
$this->afterRun();
return $result;
}
return null;
}
源码
yii\base\Request
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
/**
* Request represents a request that is handled by an [[Application]].
*
* For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
*
* @property bool $isConsoleRequest The value indicating whether the current request is made via console.
* @property string $scriptFile Entry script file path (processed w/ realpath()).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
abstract class Request extends Component
{
private $_scriptFile;
private $_isConsoleRequest;
/**
* 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.
*/
abstract public function resolve();
/**
* Returns a value indicating whether the current request is made via command line.
* @return bool the value indicating whether the current request is made via console
*/
public function getIsConsoleRequest()
{
return $this->_isConsoleRequest !== null ? $this->_isConsoleRequest : PHP_SAPI === 'cli';
}
/**
* Sets the value indicating whether the current request is made via command line.
* @param bool $value the value indicating whether the current request is made via command line
*/
public function setIsConsoleRequest($value)
{
$this->_isConsoleRequest = $value;
}
/**
* Returns entry script file path.
* @return string entry script file path (processed w/ realpath())
* @throws InvalidConfigException if the entry script file path cannot be determined automatically.
*/
public function getScriptFile()
{
if ($this->_scriptFile === null) {
if (isset($_SERVER['SCRIPT_FILENAME'])) {
$this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
} else {
throw new InvalidConfigException('Unable to determine the entry script file path.');
}
}
return $this->_scriptFile;
}
/**
* Sets the entry script file path.
* The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable.
* However, for some server configurations, this may not be correct or feasible.
* This setter is provided so that the entry script file path can be manually specified.
* @param string $value the entry script file path. This can be either a file path or a [path alias](guide:concept-aliases).
* @throws InvalidConfigException if the provided entry script file path is invalid.
*/
public function setScriptFile($value)
{
$scriptFile = realpath(Yii::getAlias($value));
if ($scriptFile !== false && is_file($scriptFile)) {
$this->_scriptFile = $scriptFile;
} else {
throw new InvalidConfigException('Unable to determine the entry script file path.');
}
}
}
yii\console\Request
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://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];
}
}
yii\web\Request
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\InvalidConfigException;
use yii\validators\IpValidator;
/**
* The web Request class represents an HTTP request.
*
* It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
* Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
* parameters sent via other HTTP methods like PUT or DELETE.
*
* Request is configured as an application component in [[\yii\web\Application]] by default.
* You can access that instance via `Yii::$app->request`.
*
* For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
*
* @property-read string $absoluteUrl The currently requested absolute URL.
* @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
* highest scores will be returned first. The array keys are the content types, while the array values are the
* corresponding quality score and other parameters as given in the header.
* @property array $acceptableLanguages The languages ordered by the preference level. The first element
* represents the most preferred language.
* @property-read array $authCredentials That contains exactly two elements: - 0: the username sent via HTTP
* authentication, `null` if the username is not given - 1: the password sent via HTTP authentication, `null` if
* the password is not given.
* @property-read string|null $authPassword The password sent via HTTP authentication, `null` if the password
* is not given.
* @property-read string|null $authUser The username sent via HTTP authentication, `null` if the username is
* not given.
* @property string $baseUrl The relative URL for the application.
* @property array|object $bodyParams The request parameters given in the request body. Note that the type of
* this property differs in getter and setter. See [[getBodyParams()]] and [[setBodyParams()]] for details.
* @property-read string $contentType Request content-type. Empty string is returned if this information is
* not available.
* @property-read CookieCollection $cookies The cookie collection.
* @property-read string $csrfToken The token used to perform CSRF validation.
* @property-read string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is
* returned if no such header is sent.
* @property-read array $eTags The entity tags.
* @property-read HeaderCollection $headers The header collection.
* @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
* (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
* [[getHostInfo()]] for security related notes on this property.
* @property-read string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`).
* @property-read bool $isAjax Whether this is an AJAX (XMLHttpRequest) request.
* @property-read bool $isDelete Whether this is a DELETE request.
* @property-read bool $isFlash Whether this is an Adobe Flash or Adobe Flex request.
* @property-read bool $isGet Whether this is a GET request.
* @property-read bool $isHead Whether this is a HEAD request.
* @property-read bool $isOptions Whether this is a OPTIONS request.
* @property-read bool $isPatch Whether this is a PATCH request.
* @property-read bool $isPjax Whether this is a PJAX request.
* @property-read bool $isPost Whether this is a POST request.
* @property-read bool $isPut Whether this is a PUT request.
* @property-read bool $isSecureConnection If the request is sent via secure channel (https).
* @property-read string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value
* returned is turned into upper case.
* @property-read string|null $origin URL origin of a CORS request, `null` if not available.
* @property string $pathInfo Part of the request URL that is after the entry script and before the question
* mark. Note, the returned path info is already URL-decoded.
* @property int $port Port number for insecure requests.
* @property array $queryParams The request GET parameter values.
* @property-read string $queryString Part of the request URL that is after the question mark.
* @property string $rawBody The request body.
* @property-read string|null $referrer URL referrer, null if not available.
* @property-read string|null $remoteHost Remote host name, `null` if not available.
* @property-read string|null $remoteIP Remote IP address, `null` if not available.
* @property string $scriptFile The entry script file path.
* @property string $scriptUrl The relative URL of the entry script.
* @property int $securePort Port number for secure requests.
* @property-read string $serverName Server name, null if not available.
* @property-read int|null $serverPort Server port number, null if not available.
* @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
* depending on the client.
* @property-read string|null $userAgent User agent, null if not available.
* @property-read string|null $userHost User host name, null if not available.
* @property-read string|null $userIP User IP address, null if not available.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
* @SuppressWarnings(PHPMD.SuperGlobals)
*/
class Request extends \yii\base\Request
{
/**
* The name of the HTTP header for sending CSRF token.
*/
const CSRF_HEADER = 'X-CSRF-Token';
/**
* The length of the CSRF token mask.
* @deprecated since 2.0.12. The mask length is now equal to the token length.
*/
const CSRF_MASK_LENGTH = 8;
/**
* @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
* When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
* from the same application. If not, a 400 HTTP exception will be raised.
*
* Note, this feature requires that the user client accepts cookie. Also, to use this feature,
* forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
* You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
*
* In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
* `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
* You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
*
* @see Controller::enableCsrfValidation
* @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
*/
public $enableCsrfValidation = true;
/**
* @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
* This property is used only when [[enableCsrfValidation]] is true.
*/
public $csrfParam = '_csrf';
/**
* @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
* both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
*/
public $csrfCookie = ['httpOnly' => true];
/**
* @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
* in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
* security, it requires starting a session for every page, which will degrade your site performance.
*/
public $enableCsrfCookie = true;
/**
* @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
*/
public $enableCookieValidation = true;
/**
* @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
*/
public $cookieValidationKey;
/**
* @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
* request tunneled through POST. Defaults to '_method'.
* @see getMethod()
* @see getBodyParams()
*/
public $methodParam = '_method';
/**
* @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
* The array keys are the request `Content-Types`, and the array values are the
* corresponding configurations for [[Yii::createObject|creating the parser objects]].
* A parser must implement the [[RequestParserInterface]].
*
* To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
*
* ```
* [
* 'application/json' => 'yii\web\JsonParser',
* ]
* ```
*
* To register a parser for parsing all request types you can use `'*'` as the array key.
* This one will be used as a fallback in case no other types match.
*
* @see getBodyParams()
*/
public $parsers = [];
/**
* @var array the configuration for trusted security related headers.
*
* An array key is an IPv4 or IPv6 IP address in CIDR notation for matching a client.
*
* An array value is a list of headers to trust. These will be matched against
* [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
* The case of the header names must be the same as specified in [[secureHeaders]].
*
* For example, to trust all headers listed in [[secureHeaders]] for IP addresses
* in range `192.168.0.0-192.168.0.254` write the following:
*
* ```php
* [
* '192.168.0.0/24',
* ]
* ```
*
* To trust just the `X-Forwarded-For` header from `10.0.0.1`, use:
*
* ```
* [
* '10.0.0.1' => ['X-Forwarded-For']
* ]
* ```
*
* Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
* Matches are tried in order and searching is stopped when IP matches.
*
* > Info: Matching is performed using [[IpValidator]].
* See [[IpValidator::::setRanges()|IpValidator::setRanges()]]
* and [[IpValidator::networks]] for advanced matching.
*
* @see secureHeaders
* @since 2.0.13
*/
public $trustedHosts = [];
/**
* @var array lists of headers that are, by default, subject to the trusted host configuration.
* These headers will be filtered unless explicitly allowed in [[trustedHosts]].
* If the list contains the `Forwarded` header, processing will be done according to RFC 7239.
* The match of header names is case-insensitive.
* @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
* @see https://datatracker.ietf.org/doc/html/rfc7239
* @see trustedHosts
* @since 2.0.13
*/
public $secureHeaders = [
// Common:
'X-Forwarded-For',
'X-Forwarded-Host',
'X-Forwarded-Proto',
// Microsoft:
'Front-End-Https',
'X-Rewrite-Url',
// ngrok:
'X-Original-Host',
];
/**
* @var string[] List of headers where proxies store the real client IP.
* It's not advisable to put insecure headers here.
* To use the `Forwarded` header according to RFC 7239, the header must be added to [[secureHeaders]] list.
* The match of header names is case-insensitive.
* @see trustedHosts
* @see secureHeaders
* @since 2.0.13
*/
public $ipHeaders = [
'X-Forwarded-For', // Common
];
/**
* @var array list of headers to check for determining whether the connection is made via HTTPS.
* The array keys are header names and the array value is a list of header values that indicate a secure connection.
* The match of header names and values is case-insensitive.
* It's not advisable to put insecure headers here.
* @see trustedHosts
* @see secureHeaders
* @since 2.0.13
*/
public $secureProtocolHeaders = [
'X-Forwarded-Proto' => ['https'], // Common
'Front-End-Https' => ['on'], // Microsoft
];
/**
* @var CookieCollection Collection of request cookies.
*/
private $_cookies;
/**
* @var HeaderCollection Collection of request headers.
*/
private $_headers;
/**
* 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 NotFoundHttpException if the request cannot be resolved.
*/
public function resolve()
{
$result = Yii::$app->getUrlManager()->parseRequest($this);
if ($result !== false) {
list($route, $params) = $result;
if ($this->_queryParams === null) {
$_GET = $params + $_GET; // preserve numeric keys
} else {
$this->_queryParams = $params + $this->_queryParams;
}
return [$route, $this->getQueryParams()];
}
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
}
/**
* Filters headers according to the [[trustedHosts]].
* @param HeaderCollection $headerCollection
* @since 2.0.13
*/
protected function filterHeaders(HeaderCollection $headerCollection)
{
$trustedHeaders = $this->getTrustedHeaders();
// remove all secure headers unless they are trusted
foreach ($this->secureHeaders as $secureHeader) {
if (!in_array($secureHeader, $trustedHeaders)) {
$headerCollection->remove($secureHeader);
}
}
}
/**
* Trusted headers according to the [[trustedHosts]].
* @return array
* @since 2.0.28
*/
protected function getTrustedHeaders()
{
// do not trust any of the [[secureHeaders]] by default
$trustedHeaders = [];
// check if the client is a trusted host
if (!empty($this->trustedHosts)) {
$validator = $this->getIpValidator();
$ip = $this->getRemoteIP();
foreach ($this->trustedHosts as $cidr => $headers) {
if (!is_array($headers)) {
$cidr = $headers;
$headers = $this->secureHeaders;
}
$validator->setRanges($cidr);
if ($validator->validate($ip)) {
$trustedHeaders = $headers;
break;
}
}
}
return $trustedHeaders;
}
/**
* Creates instance of [[IpValidator]].
* You can override this method to adjust validator or implement different matching strategy.
*
* @return IpValidator
* @since 2.0.13
*/
protected function getIpValidator()
{
return new IpValidator();
}
/**
* Returns the header collection.
* The header collection contains incoming HTTP headers.
* @return HeaderCollection the header collection
*/
public function getHeaders()
{
if ($this->_headers === null) {
$this->_headers = new HeaderCollection();
if (function_exists('getallheaders')) {
$headers = getallheaders();
foreach ($headers as $name => $value) {
$this->_headers->add($name, $value);
}
} elseif (function_exists('http_get_request_headers')) {
$headers = http_get_request_headers();
foreach ($headers as $name => $value) {
$this->_headers->add($name, $value);
}
} else {
// ['prefix' => length]
$headerPrefixes = ['HTTP_' => 5, 'REDIRECT_HTTP_' => 14];
foreach ($_SERVER as $name => $value) {
foreach ($headerPrefixes as $prefix => $length) {
if (strncmp($name, $prefix, $length) === 0) {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, $length)))));
$this->_headers->add($name, $value);
continue 2;
}
}
}
}
$this->filterHeaders($this->_headers);
}
return $this->_headers;
}
/**
* Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
* @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
* The value returned is turned into upper case.
*/
public function getMethod()
{
if (
isset($_POST[$this->methodParam])
// Never allow to downgrade request from WRITE methods (POST, PATCH, DELETE, etc)
// to read methods (GET, HEAD, OPTIONS) for security reasons.
&& !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
) {
return strtoupper($_POST[$this->methodParam]);
}
if ($this->headers->has('X-Http-Method-Override')) {
return strtoupper($this->headers->get('X-Http-Method-Override'));
}
if (isset($_SERVER['REQUEST_METHOD'])) {
return strtoupper($_SERVER['REQUEST_METHOD']);
}
return 'GET';
}
/**
* Returns whether this is a GET request.
* @return bool whether this is a GET request.
*/
public function getIsGet()
{
return $this->getMethod() === 'GET';
}
/**
* Returns whether this is an OPTIONS request.
* @return bool whether this is a OPTIONS request.
*/
public function getIsOptions()
{
return $this->getMethod() === 'OPTIONS';
}
/**
* Returns whether this is a HEAD request.
* @return bool whether this is a HEAD request.
*/
public function getIsHead()
{
return $this->getMethod() === 'HEAD';
}
/**
* Returns whether this is a POST request.
* @return bool whether this is a POST request.
*/
public function getIsPost()
{
return $this->getMethod() === 'POST';
}
/**
* Returns whether this is a DELETE request.
* @return bool whether this is a DELETE request.
*/
public function getIsDelete()
{
return $this->getMethod() === 'DELETE';
}
/**
* Returns whether this is a PUT request.
* @return bool whether this is a PUT request.
*/
public function getIsPut()
{
return $this->getMethod() === 'PUT';
}
/**
* Returns whether this is a PATCH request.
* @return bool whether this is a PATCH request.
*/
public function getIsPatch()
{
return $this->getMethod() === 'PATCH';
}
/**
* Returns whether this is an AJAX (XMLHttpRequest) request.
*
* Note that in case of cross domain requests, browser doesn't set the X-Requested-With header by default:
* https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
*
* In case you are using `fetch()`, pass header manually:
*
* ```
* fetch(url, {
* method: 'GET',
* headers: {'X-Requested-With': 'XMLHttpRequest'}
* })
* ```
*
* @return bool whether this is an AJAX (XMLHttpRequest) request.
*/
public function getIsAjax()
{
return $this->headers->get('X-Requested-With') === 'XMLHttpRequest';
}
/**
* Returns whether this is a PJAX request.
* @return bool whether this is a PJAX request
*/
public function getIsPjax()
{
return $this->getIsAjax() && $this->headers->has('X-Pjax');
}
/**
* Returns whether this is an Adobe Flash or Flex request.
* @return bool whether this is an Adobe Flash or Adobe Flex request.
*/
public function getIsFlash()
{
$userAgent = $this->headers->get('User-Agent', '');
return stripos($userAgent, 'Shockwave') !== false
|| stripos($userAgent, 'Flash') !== false;
}
private $_rawBody;
/**
* Returns the raw HTTP request body.
* @return string the request body
*/
public function getRawBody()
{
if ($this->_rawBody === null) {
$this->_rawBody = file_get_contents('php://input');
}
return $this->_rawBody;
}
/**
* Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
* @param string $rawBody the request body
*/
public function setRawBody($rawBody)
{
$this->_rawBody = $rawBody;
}
private $_bodyParams;
/**
* Returns the request parameters given in the request body.
*
* Request parameters are determined using the parsers configured in [[parsers]] property.
* If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
* to parse the [[rawBody|request body]].
* @return array|object the request parameters given in the request body.
* @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
* @see getMethod()
* @see getBodyParam()
* @see setBodyParams()
*/
public function getBodyParams()
{
if ($this->_bodyParams === null) {
if (isset($_POST[$this->methodParam])) {
$this->_bodyParams = $_POST;
unset($this->_bodyParams[$this->methodParam]);
return $this->_bodyParams;
}
$rawContentType = $this->getContentType();
if (($pos = strpos((string)$rawContentType, ';')) !== false) {
// e.g. text/html; charset=UTF-8
$contentType = substr($rawContentType, 0, $pos);
} else {
$contentType = $rawContentType;
}
if (isset($this->parsers[$contentType])) {
$parser = Yii::createObject($this->parsers[$contentType]);
if (!($parser instanceof RequestParserInterface)) {
throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
}
$this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
} elseif (isset($this->parsers['*'])) {
$parser = Yii::createObject($this->parsers['*']);
if (!($parser instanceof RequestParserInterface)) {
throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
}
$this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
} elseif ($this->getMethod() === 'POST') {
// PHP has already parsed the body so we have all params in $_POST
$this->_bodyParams = $_POST;
} else {
$this->_bodyParams = [];
mb_parse_str($this->getRawBody(), $this->_bodyParams);
}
}
return $this->_bodyParams;
}
/**
* Sets the request body parameters.
* @param array $values the request body parameters (name-value pairs)
* @see getBodyParam()
* @see getBodyParams()
*/
public function setBodyParams($values)
{
$this->_bodyParams = $values;
}
/**
* Returns the named request body parameter value.
* If the parameter does not exist, the second parameter passed to this method will be returned.
* @param string $name the parameter name
* @param mixed $defaultValue the default parameter value if the parameter does not exist.
* @return mixed the parameter value
* @see getBodyParams()
* @see setBodyParams()
*/
public function getBodyParam($name, $defaultValue = null)
{
$params = $this->getBodyParams();
if (is_object($params)) {
// unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
try {
return $params->{$name};
} catch (\Exception $e) {
return $defaultValue;
}
}
return isset($params[$name]) ? $params[$name] : $defaultValue;
}
/**
* Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
*
* @param string $name the parameter name
* @param mixed $defaultValue the default parameter value if the parameter does not exist.
* @return array|mixed
*/
public function post($name = null, $defaultValue = null)
{
if ($name === null) {
return $this->getBodyParams();
}
return $this->getBodyParam($name, $defaultValue);
}
private $_queryParams;
/**
* Returns the request parameters given in the [[queryString]].
*
* This method will return the contents of `$_GET` if params where not explicitly set.
* @return array the request GET parameter values.
* @see setQueryParams()
*/
public function getQueryParams()
{
if ($this->_queryParams === null) {
return $_GET;
}
return $this->_queryParams;
}
/**
* Sets the request [[queryString]] parameters.
* @param array $values the request query parameters (name-value pairs)
* @see getQueryParam()
* @see getQueryParams()
*/
public function setQueryParams($values)
{
$this->_queryParams = $values;
}
/**
* Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
*
* @param string $name the parameter name
* @param mixed $defaultValue the default parameter value if the parameter does not exist.
* @return array|mixed
*/
public function get($name = null, $defaultValue = null)
{
if ($name === null) {
return $this->getQueryParams();
}
return $this->getQueryParam($name, $defaultValue);
}
/**
* Returns the named GET parameter value.
* If the GET parameter does not exist, the second parameter passed to this method will be returned.
* @param string $name the GET parameter name.
* @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
* @return mixed the GET parameter value
* @see getBodyParam()
*/
public function getQueryParam($name, $defaultValue = null)
{
$params = $this->getQueryParams();
return isset($params[$name]) ? $params[$name] : $defaultValue;
}
private $_hostInfo;
private $_hostName;
/**
* Returns the schema and host part of the current request URL.
*
* The returned URL does not have an ending slash.
*
* By default this value is based on the user request information. This method will
* return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
* You may want to check out the [PHP documentation](https://www.php.net/manual/en/reserved.variables.server.php)
* for more information on these variables.
*
* You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
*
* > Warning: Dependent on the server configuration this information may not be
* > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
* > If the webserver is configured to serve the same site independent of the value of
* > the `Host` header, this value is not reliable. In such situations you should either
* > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
* > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
* > application level in order to protect against such kind of attack.
*
* @property string|null schema and hostname part (with port number if needed) of the request URL
* (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
* See [[getHostInfo()]] for security related notes on this property.
* @return string|null schema and hostname part (with port number if needed) of the request URL
* (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
* @see setHostInfo()
*/
public function getHostInfo()
{
if ($this->_hostInfo === null) {
$secure = $this->getIsSecureConnection();
$http = $secure ? 'https' : 'http';
if ($this->getSecureForwardedHeaderTrustedPart('host') !== null) {
$this->_hostInfo = $http . '://' . $this->getSecureForwardedHeaderTrustedPart('host');
} elseif ($this->headers->has('X-Forwarded-Host')) {
$this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Forwarded-Host'))[0]);
} elseif ($this->headers->has('X-Original-Host')) {
$this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Original-Host'))[0]);
} elseif ($this->headers->has('Host')) {
$this->_hostInfo = $http . '://' . $this->headers->get('Host');
} elseif (isset($_SERVER['SERVER_NAME'])) {
$this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
$port = $secure ? $this->getSecurePort() : $this->getPort();
if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
$this->_hostInfo .= ':' . $port;
}
}
}
return $this->_hostInfo;
}
/**
* Sets the schema and host part of the application URL.
* This setter is provided in case the schema and hostname cannot be determined
* on certain Web servers.
* @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
* @see getHostInfo() for security related notes on this property.
*/
public function setHostInfo($value)
{
$this->_hostName = null;
$this->_hostInfo = $value === null ? null : rtrim($value, '/');
}
/**
* Returns the host part of the current request URL.
* Value is calculated from current [[getHostInfo()|hostInfo]] property.
*
* > Warning: The content of this value may not be reliable, dependent on the server
* > configuration. Please refer to [[getHostInfo()]] for more information.
*
* @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
* @see getHostInfo()
* @since 2.0.10
*/
public function getHostName()
{
if ($this->_hostName === null) {
$this->_hostName = parse_url((string)$this->getHostInfo(), PHP_URL_HOST);
}
return $this->_hostName;
}
private $_baseUrl;
/**
* Returns the relative URL for the application.
* This is similar to [[scriptUrl]] except that it does not include the script file name,
* and the ending slashes are removed.
* @return string the relative URL for the application
* @see setScriptUrl()
*/
public function getBaseUrl()
{
if ($this->_baseUrl === null) {
$this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
}
return $this->_baseUrl;
}
/**
* Sets the relative URL for the application.
* By default the URL is determined based on the entry script URL.
* This setter is provided in case you want to change this behavior.
* @param string $value the relative URL for the application
*/
public function setBaseUrl($value)
{
$this->_baseUrl = $value;
}
private $_scriptUrl;
/**
* Returns the relative URL of the entry script.
* The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
* @return string the relative URL of the entry script.
* @throws InvalidConfigException if unable to determine the entry script URL
*/
public function getScriptUrl()
{
if ($this->_scriptUrl === null) {
$scriptFile = $this->getScriptFile();
$scriptName = basename($scriptFile);
if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
$this->_scriptUrl = $_SERVER['PHP_SELF'];
} elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
$this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
} elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
$this->_scriptUrl = str_replace([$_SERVER['DOCUMENT_ROOT'], '\\'], ['', '/'], $scriptFile);
} else {
throw new InvalidConfigException('Unable to determine the entry script URL.');
}
}
return $this->_scriptUrl;
}
/**
* Sets the relative URL for the application entry script.
* This setter is provided in case the entry script URL cannot be determined
* on certain Web servers.
* @param string $value the relative URL for the application entry script.
*/
public function setScriptUrl($value)
{
$this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
}
private $_scriptFile;
/**
* Returns the entry script file path.
* The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
* @return string the entry script file path
* @throws InvalidConfigException
*/
public function getScriptFile()
{
if (isset($this->_scriptFile)) {
return $this->_scriptFile;
}
if (isset($_SERVER['SCRIPT_FILENAME'])) {
return $_SERVER['SCRIPT_FILENAME'];
}
throw new InvalidConfigException('Unable to determine the entry script file path.');
}
/**
* Sets the entry script file path.
* The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
* If your server configuration does not return the correct value, you may configure
* this property to make it right.
* @param string $value the entry script file path.
*/
public function setScriptFile($value)
{
$this->_scriptFile = $value;
}
private $_pathInfo;
/**
* Returns the path info of the currently requested URL.
* A path info refers to the part that is after the entry script and before the question mark (query string).
* The starting and ending slashes are both removed.
* @return string part of the request URL that is after the entry script and before the question mark.
* Note, the returned path info is already URL-decoded.
* @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
*/
public function getPathInfo()
{
if ($this->_pathInfo === null) {
$this->_pathInfo = $this->resolvePathInfo();
}
return $this->_pathInfo;
}
/**
* Sets the path info of the current request.
* This method is mainly provided for testing purpose.
* @param string $value the path info of the current request
*/
public function setPathInfo($value)
{
$this->_pathInfo = $value === null ? null : ltrim($value, '/');
}
/**
* Resolves the path info part of the currently requested URL.
* A path info refers to the part that is after the entry script and before the question mark (query string).
* The starting slashes are both removed (ending slashes will be kept).
* @return string part of the request URL that is after the entry script and before the question mark.
* Note, the returned path info is decoded.
* @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
*/
protected function resolvePathInfo()
{
$pathInfo = $this->getUrl();
if (($pos = strpos($pathInfo, '?')) !== false) {
$pathInfo = substr($pathInfo, 0, $pos);
}
$pathInfo = urldecode($pathInfo);
// try to encode in UTF8 if not so
// http://w3.org/International/questions/qa-forms-utf-8.html
if (!preg_match('%^(?:
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)*$%xs', $pathInfo)
) {
$pathInfo = $this->utf8Encode($pathInfo);
}
$scriptUrl = $this->getScriptUrl();
$baseUrl = $this->getBaseUrl();
if (strpos($pathInfo, $scriptUrl) === 0) {
$pathInfo = substr($pathInfo, strlen($scriptUrl));
} elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
$pathInfo = substr($pathInfo, strlen($baseUrl));
} elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
$pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
} else {
throw new InvalidConfigException('Unable to determine the path info of the current request.');
}
if (strncmp($pathInfo, '/', 1) === 0) {
$pathInfo = substr($pathInfo, 1);
}
return (string) $pathInfo;
}
/**
* Encodes an ISO-8859-1 string to UTF-8
* @param string $s
* @return string the UTF-8 translation of `s`.
* @see https://github.com/symfony/polyfill-php72/blob/master/Php72.php#L24
*/
private function utf8Encode($s)
{
$s .= $s;
$len = \strlen($s);
for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
switch (true) {
case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
}
}
return substr($s, 0, $j);
}
/**
* Returns the currently requested absolute URL.
* This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
* @return string the currently requested absolute URL.
*/
public function getAbsoluteUrl()
{
return $this->getHostInfo() . $this->getUrl();
}
private $_url;
/**
* Returns the currently requested relative URL.
* This refers to the portion of the URL that is after the [[hostInfo]] part.
* It includes the [[queryString]] part if any.
* @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
* @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
*/
public function getUrl()
{
if ($this->_url === null) {
$this->_url = $this->resolveRequestUri();
}
return $this->_url;
}
/**
* Sets the currently requested relative URL.
* The URI must refer to the portion that is after [[hostInfo]].
* Note that the URI should be URL-encoded.
* @param string $value the request URI to be set
*/
public function setUrl($value)
{
$this->_url = $value;
}
/**
* Resolves the request URI portion for the currently requested URL.
* This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
* The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
* @return string|bool the request URI portion for the currently requested URL.
* Note that the URI returned may be URL-encoded depending on the client.
* @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
*/
protected function resolveRequestUri()
{
if ($this->headers->has('X-Rewrite-Url')) { // IIS
$requestUri = $this->headers->get('X-Rewrite-Url');
} elseif (isset($_SERVER['REQUEST_URI'])) {
$requestUri = $_SERVER['REQUEST_URI'];
if ($requestUri !== '' && $requestUri[0] !== '/') {
$requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
}
} elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
$requestUri = $_SERVER['ORIG_PATH_INFO'];
if (!empty($_SERVER['QUERY_STRING'])) {
$requestUri .= '?' . $_SERVER['QUERY_STRING'];
}
} else {
throw new InvalidConfigException('Unable to determine the request URI.');
}
return $requestUri;
}
/**
* Returns part of the request URL that is after the question mark.
* @return string part of the request URL that is after the question mark
*/
public function getQueryString()
{
return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
}
/**
* Return if the request is sent via secure channel (https).
* @return bool if the request is sent via secure channel (https)
*/
public function getIsSecureConnection()
{
if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)) {
return true;
}
if (($proto = $this->getSecureForwardedHeaderTrustedPart('proto')) !== null) {
return strcasecmp($proto, 'https') === 0;
}
foreach ($this->secureProtocolHeaders as $header => $values) {
if (($headerValue = $this->headers->get($header, null)) !== null) {
foreach ($values as $value) {
if (strcasecmp($headerValue, $value) === 0) {
return true;
}
}
}
}
return false;
}
/**
* Returns the server name.
* @return string server name, null if not available
*/
public function getServerName()
{
return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
}
/**
* Returns the server port number.
* @return int|null server port number, null if not available
*/
public function getServerPort()
{
return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
}
/**
* Returns the URL referrer.
* @return string|null URL referrer, null if not available
*/
public function getReferrer()
{
return $this->headers->get('Referer');
}
/**
* Returns the URL origin of a CORS request.
*
* The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
*
* Note that the origin request header indicates where a fetch originates from.
* It doesn't include any path information, but only the server name.
* It is sent with a CORS requests, as well as with POST requests.
* It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
* Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
*
* @return string|null URL origin of a CORS request, `null` if not available.
* @see getHeaders()
* @since 2.0.13
*/
public function getOrigin()
{
return $this->getHeaders()->get('origin');
}
/**
* Returns the user agent.
* @return string|null user agent, null if not available
*/
public function getUserAgent()
{
return $this->headers->get('User-Agent');
}
/**
* Returns the user IP address from [[ipHeaders]].
* @return string|null user IP address, null if not available
* @see ipHeaders
* @since 2.0.28
*/
protected function getUserIpFromIpHeaders()
{
$ip = $this->getSecureForwardedHeaderTrustedPart('for');
if ($ip !== null && preg_match(
'/^\[?(?P<ip>(?:(?:(?:[0-9a-f]{1,4}:){1,6}(?:[0-9a-f]{1,4})?(?:(?::[0-9a-f]{1,4}){1,6}))|(?:[\d]{1,3}\.){3}[\d]{1,3}))\]?(?::(?P<port>[\d]+))?$/',
$ip,
$matches
)) {
$ip = $this->getUserIpFromIpHeader($matches['ip']);
if ($ip !== null) {
return $ip;
}
}
foreach ($this->ipHeaders as $ipHeader) {
if ($this->headers->has($ipHeader)) {
$ip = $this->getUserIpFromIpHeader($this->headers->get($ipHeader));
if ($ip !== null) {
return $ip;
}
}
}
return null;
}
/**
* Returns the user IP address.
* The IP is determined using headers and / or `$_SERVER` variables.
* @return string|null user IP address, null if not available
*/
public function getUserIP()
{
$ip = $this->getUserIpFromIpHeaders();
return $ip === null ? $this->getRemoteIP() : $ip;
}
/**
* Return user IP's from IP header.
*
* @param string $ips comma separated IP list
* @return string|null IP as string. Null is returned if IP can not be determined from header.
* @see getUserHost()
* @see ipHeaders
* @see getTrustedHeaders()
* @since 2.0.28
*/
protected function getUserIpFromIpHeader($ips)
{
$ips = trim($ips);
if ($ips === '') {
return null;
}
$ips = preg_split('/\s*,\s*/', $ips, -1, PREG_SPLIT_NO_EMPTY);
krsort($ips);
$validator = $this->getIpValidator();
$resultIp = null;
foreach ($ips as $ip) {
$validator->setRanges('any');
if (!$validator->validate($ip) /* checking IP format */) {
break;
}
$resultIp = $ip;
$isTrusted = false;
foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
if (!is_array($trustedCidrOrHeaders)) {
$trustedCidr = $trustedCidrOrHeaders;
}
$validator->setRanges($trustedCidr);
if ($validator->validate($ip) /* checking trusted range */) {
$isTrusted = true;
break;
}
}
if (!$isTrusted) {
break;
}
}
return $resultIp;
}
/**
* Returns the user host name.
* The HOST is determined using headers and / or `$_SERVER` variables.
* @return string|null user host name, null if not available
*/
public function getUserHost()
{
$userIp = $this->getUserIpFromIpHeaders();
if($userIp === null) {
return $this->getRemoteHost();
}
return gethostbyaddr($userIp);
}
/**
* Returns the IP on the other end of this connection.
* This is always the next hop, any headers are ignored.
* @return string|null remote IP address, `null` if not available.
* @since 2.0.13
*/
public function getRemoteIP()
{
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
}
/**
* Returns the host name of the other end of this connection.
* This is always the next hop, any headers are ignored.
* @return string|null remote host name, `null` if not available
* @see getUserHost()
* @see getRemoteIP()
* @since 2.0.13
*/
public function getRemoteHost()
{
return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
}
/**
* @return string|null the username sent via HTTP authentication, `null` if the username is not given
* @see getAuthCredentials() to get both username and password in one call
*/
public function getAuthUser()
{
return $this->getAuthCredentials()[0];
}
/**
* @return string|null the password sent via HTTP authentication, `null` if the password is not given
* @see getAuthCredentials() to get both username and password in one call
*/
public function getAuthPassword()
{
return $this->getAuthCredentials()[1];
}
/**
* @return array that contains exactly two elements:
* - 0: the username sent via HTTP authentication, `null` if the username is not given
* - 1: the password sent via HTTP authentication, `null` if the password is not given
* @see getAuthUser() to get only username
* @see getAuthPassword() to get only password
* @since 2.0.13
*/
public function getAuthCredentials()
{
$username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
$password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
if ($username !== null || $password !== null) {
return [$username, $password];
}
/**
* Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
* To make it work, add one of the following lines to to your .htaccess file:
*
* SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
* --OR--
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
*/
$auth_token = $this->getHeaders()->get('Authorization');
if ($auth_token !== null && strncasecmp($auth_token, 'basic', 5) === 0) {
$parts = array_map(function ($value) {
return strlen($value) === 0 ? null : $value;
}, explode(':', base64_decode(mb_substr($auth_token, 6)), 2));
if (count($parts) < 2) {
return [$parts[0], null];
}
return $parts;
}
return [null, null];
}
private $_port;
/**
* Returns the port to use for insecure requests.
* Defaults to 80, or the port specified by the server if the current
* request is insecure.
* @return int port number for insecure requests.
* @see setPort()
*/
public function getPort()
{
if ($this->_port === null) {
$serverPort = $this->getServerPort();
$this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
}
return $this->_port;
}
/**
* Sets the port to use for insecure requests.
* This setter is provided in case a custom port is necessary for certain
* server configurations.
* @param int $value port number.
*/
public function setPort($value)
{
if ($value != $this->_port) {
$this->_port = (int) $value;
$this->_hostInfo = null;
}
}
private $_securePort;
/**
* Returns the port to use for secure requests.
* Defaults to 443, or the port specified by the server if the current
* request is secure.
* @return int port number for secure requests.
* @see setSecurePort()
*/
public function getSecurePort()
{
if ($this->_securePort === null) {
$serverPort = $this->getServerPort();
$this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
}
return $this->_securePort;
}
/**
* Sets the port to use for secure requests.
* This setter is provided in case a custom port is necessary for certain
* server configurations.
* @param int $value port number.
*/
public function setSecurePort($value)
{
if ($value != $this->_securePort) {
$this->_securePort = (int) $value;
$this->_hostInfo = null;
}
}
private $_contentTypes;
/**
* Returns the content types acceptable by the end user.
*
* This is determined by the `Accept` HTTP header. For example,
*
* ```php
* $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
* $types = $request->getAcceptableContentTypes();
* print_r($types);
* // displays:
* // [
* // 'application/json' => ['q' => 1, 'version' => '1.0'],
* // 'application/xml' => ['q' => 1, 'version' => '2.0'],
* // 'text/plain' => ['q' => 0.5],
* // ]
* ```
*
* @return array the content types ordered by the quality score. Types with the highest scores
* will be returned first. The array keys are the content types, while the array values
* are the corresponding quality score and other parameters as given in the header.
*/
public function getAcceptableContentTypes()
{
if ($this->_contentTypes === null) {
if ($this->headers->get('Accept') !== null) {
$this->_contentTypes = $this->parseAcceptHeader($this->headers->get('Accept'));
} else {
$this->_contentTypes = [];
}
}
return $this->_contentTypes;
}
/**
* Sets the acceptable content types.
* Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
* @param array $value the content types that are acceptable by the end user. They should
* be ordered by the preference level.
* @see getAcceptableContentTypes()
* @see parseAcceptHeader()
*/
public function setAcceptableContentTypes($value)
{
$this->_contentTypes = $value;
}
/**
* Returns request content-type
* The Content-Type header field indicates the MIME type of the data
* contained in [[getRawBody()]] or, in the case of the HEAD method, the
* media type that would have been sent had the request been a GET.
* For the MIME-types the user expects in response, see [[acceptableContentTypes]].
* @return string request content-type. Empty string is returned if this information is not available.
* @link https://tools.ietf.org/html/rfc2616#section-14.17
* HTTP 1.1 header field definitions
*/
public function getContentType()
{
if (isset($_SERVER['CONTENT_TYPE'])) {
return $_SERVER['CONTENT_TYPE'];
}
//fix bug https://bugs.php.net/bug.php?id=66606
return $this->headers->get('Content-Type') ?: '';
}
private $_languages;
/**
* Returns the languages acceptable by the end user.
* This is determined by the `Accept-Language` HTTP header.
* @return array the languages ordered by the preference level. The first element
* represents the most preferred language.
*/
public function getAcceptableLanguages()
{
if ($this->_languages === null) {
if ($this->headers->has('Accept-Language')) {
$this->_languages = array_keys($this->parseAcceptHeader($this->headers->get('Accept-Language')));
} else {
$this->_languages = [];
}
}
return $this->_languages;
}
/**
* @param array $value the languages that are acceptable by the end user. They should
* be ordered by the preference level.
*/
public function setAcceptableLanguages($value)
{
$this->_languages = $value;
}
/**
* Parses the given `Accept` (or `Accept-Language`) header.
*
* This method will return the acceptable values with their quality scores and the corresponding parameters
* as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
* while the array values consisting of the corresponding quality scores and parameters. The acceptable
* values with the highest quality scores will be returned first. For example,
*
* ```php
* $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
* $accepts = $request->parseAcceptHeader($header);
* print_r($accepts);
* // displays:
* // [
* // 'application/json' => ['q' => 1, 'version' => '1.0'],
* // 'application/xml' => ['q' => 1, 'version' => '2.0'],
* // 'text/plain' => ['q' => 0.5],
* // ]
* ```
*
* @param string $header the header to be parsed
* @return array the acceptable values ordered by their quality score. The values with the highest scores
* will be returned first.
*/
public function parseAcceptHeader($header)
{
$accepts = [];
foreach (explode(',', $header) as $i => $part) {
$params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
if (empty($params)) {
continue;
}
$values = [
'q' => [$i, array_shift($params), 1],
];
foreach ($params as $param) {
if (strpos($param, '=') !== false) {
list($key, $value) = explode('=', $param, 2);
if ($key === 'q') {
$values['q'][2] = (float) $value;
} else {
$values[$key] = $value;
}
} else {
$values[] = $param;
}
}
$accepts[] = $values;
}
usort($accepts, function ($a, $b) {
$a = $a['q']; // index, name, q
$b = $b['q'];
if ($a[2] > $b[2]) {
return -1;
}
if ($a[2] < $b[2]) {
return 1;
}
if ($a[1] === $b[1]) {
return $a[0] > $b[0] ? 1 : -1;
}
if ($a[1] === '*/*') {
return 1;
}
if ($b[1] === '*/*') {
return -1;
}
$wa = $a[1][strlen($a[1]) - 1] === '*';
$wb = $b[1][strlen($b[1]) - 1] === '*';
if ($wa xor $wb) {
return $wa ? 1 : -1;
}
return $a[0] > $b[0] ? 1 : -1;
});
$result = [];
foreach ($accepts as $accept) {
$name = $accept['q'][1];
$accept['q'] = $accept['q'][2];
$result[$name] = $accept;
}
return $result;
}
/**
* Returns the user-preferred language that should be used by this application.
* The language resolution is based on the user preferred languages and the languages
* supported by the application. The method will try to find the best match.
* @param array $languages a list of the languages supported by the application. If this is empty, the current
* application language will be returned without further processing.
* @return string the language that the application should use.
*/
public function getPreferredLanguage(array $languages = [])
{
if (empty($languages)) {
return Yii::$app->language;
}
foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
$acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
foreach ($languages as $language) {
$normalizedLanguage = str_replace('_', '-', strtolower($language));
if (
$normalizedLanguage === $acceptableLanguage // en-us==en-us
|| strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
|| strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
) {
return $language;
}
}
}
return reset($languages);
}
/**
* Gets the Etags.
*
* @return array The entity tags
*/
public function getETags()
{
if ($this->headers->has('If-None-Match')) {
return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
}
return [];
}
/**
* Returns the cookie collection.
*
* Through the returned cookie collection, you may access a cookie using the following syntax:
*
* ```php
* $cookie = $request->cookies['name']
* if ($cookie !== null) {
* $value = $cookie->value;
* }
*
* // alternatively
* $value = $request->cookies->getValue('name');
* ```
*
* @return CookieCollection the cookie collection.
*/
public function getCookies()
{
if ($this->_cookies === null) {
$this->_cookies = new CookieCollection($this->loadCookies(), [
'readOnly' => true,
]);
}
return $this->_cookies;
}
/**
* Converts `$_COOKIE` into an array of [[Cookie]].
* @return array the cookies obtained from request
* @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
*/
protected function loadCookies()
{
$cookies = [];
if ($this->enableCookieValidation) {
if ($this->cookieValidationKey == '') {
throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
}
foreach ($_COOKIE as $name => $value) {
if (!is_string($value)) {
continue;
}
$data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
if ($data === false) {
continue;
}
if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) {
$data = @unserialize($data, ['allowed_classes' => false]);
} else {
$data = @unserialize($data);
}
if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
$cookies[$name] = Yii::createObject([
'class' => 'yii\web\Cookie',
'name' => $name,
'value' => $data[1],
'expire' => null,
]);
}
}
} else {
foreach ($_COOKIE as $name => $value) {
$cookies[$name] = Yii::createObject([
'class' => 'yii\web\Cookie',
'name' => $name,
'value' => $value,
'expire' => null,
]);
}
}
return $cookies;
}
private $_csrfToken;
/**
* Returns the token used to perform CSRF validation.
*
* This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
* along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
* @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
* this method is called, a new CSRF token will be generated and persisted (in session or cookie).
* @return string the token used to perform CSRF validation.
*/
public function getCsrfToken($regenerate = false)
{
if ($this->_csrfToken === null || $regenerate) {
$token = $this->loadCsrfToken();
if ($regenerate || empty($token)) {
$token = $this->generateCsrfToken();
}
$this->_csrfToken = Yii::$app->security->maskToken($token);
}
return $this->_csrfToken;
}
/**
* Loads the CSRF token from cookie or session.
* @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
* does not have CSRF token.
*/
protected function loadCsrfToken()
{
if ($this->enableCsrfCookie) {
return $this->getCookies()->getValue($this->csrfParam);
}
return Yii::$app->getSession()->get($this->csrfParam);
}
/**
* Generates an unmasked random token used to perform CSRF validation.
* @return string the random token for CSRF validation.
*/
protected function generateCsrfToken()
{
$token = Yii::$app->getSecurity()->generateRandomString();
if ($this->enableCsrfCookie) {
$cookie = $this->createCsrfCookie($token);
Yii::$app->getResponse()->getCookies()->add($cookie);
} else {
Yii::$app->getSession()->set($this->csrfParam, $token);
}
return $token;
}
/**
* @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
*/
public function getCsrfTokenFromHeader()
{
return $this->headers->get(static::CSRF_HEADER);
}
/**
* Creates a cookie with a randomly generated CSRF token.
* Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
* @param string $token the CSRF token
* @return Cookie the generated cookie
* @see enableCsrfValidation
*/
protected function createCsrfCookie($token)
{
$options = $this->csrfCookie;
return Yii::createObject(array_merge($options, [
'class' => 'yii\web\Cookie',
'name' => $this->csrfParam,
'value' => $token,
]));
}
/**
* Performs the CSRF validation.
*
* This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
* This method is mainly called in [[Controller::beforeAction()]].
*
* Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
* is among GET, HEAD or OPTIONS.
*
* @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
* the [[csrfParam]] POST field or HTTP header.
* This parameter is available since version 2.0.4.
* @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
*/
public function validateCsrfToken($clientSuppliedToken = null)
{
$method = $this->getMethod();
// only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
return true;
}
$trueToken = $this->getCsrfToken();
if ($clientSuppliedToken !== null) {
return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
}
return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
|| $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
}
/**
* Validates CSRF token.
*
* @param string $clientSuppliedToken The masked client-supplied token.
* @param string $trueToken The masked true token.
* @return bool
*/
private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
{
if (!is_string($clientSuppliedToken)) {
return false;
}
$security = Yii::$app->security;
return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
}
/**
* Gets first `Forwarded` header value for token
*
* @param string $token Header token
*
* @return string|null
*
* @since 2.0.31
*/
protected function getSecureForwardedHeaderTrustedPart($token)
{
$token = strtolower($token);
if ($parts = $this->getSecureForwardedHeaderTrustedParts()) {
$lastElement = array_pop($parts);
if ($lastElement && isset($lastElement[$token])) {
return $lastElement[$token];
}
}
return null;
}
/**
* Gets only trusted `Forwarded` header parts
*
* @return array
*
* @since 2.0.31
*/
protected function getSecureForwardedHeaderTrustedParts()
{
$validator = $this->getIpValidator();
$trustedHosts = [];
foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
if (!is_array($trustedCidrOrHeaders)) {
$trustedCidr = $trustedCidrOrHeaders;
}
$trustedHosts[] = $trustedCidr;
}
$validator->setRanges($trustedHosts);
return array_filter($this->getSecureForwardedHeaderParts(), function ($headerPart) use ($validator) {
return isset($headerPart['for']) ? !$validator->validate($headerPart['for']) : true;
});
}
private $_secureForwardedHeaderParts;
/**
* Returns decoded forwarded header
*
* @return array
*
* @since 2.0.31
*/
protected function getSecureForwardedHeaderParts()
{
if ($this->_secureForwardedHeaderParts !== null) {
return $this->_secureForwardedHeaderParts;
}
if (count(preg_grep('/^forwarded$/i', $this->secureHeaders)) === 0) {
return $this->_secureForwardedHeaderParts = [];
}
/*
* First header is always correct, because proxy CAN add headers
* after last one is found.
* Keep in mind that it is NOT enforced, therefore we cannot be
* sure, that this is really a first one.
*
* FPM keeps last header sent which is a bug. You need to merge
* headers together on your web server before letting FPM handle it
* @see https://bugs.php.net/bug.php?id=78844
*/
$forwarded = $this->headers->get('Forwarded', '');
if ($forwarded === '') {
return $this->_secureForwardedHeaderParts = [];
}
preg_match_all('/(?:[^",]++|"[^"]++")+/', $forwarded, $forwardedElements);
foreach ($forwardedElements[0] as $forwardedPairs) {
preg_match_all('/(?P<key>\w+)\s*=\s*(?:(?P<value>[^",;]*[^",;\s])|"(?P<value2>[^"]+)")/', $forwardedPairs,
$matches, PREG_SET_ORDER);
$this->_secureForwardedHeaderParts[] = array_reduce($matches, function ($carry, $item) {
$value = $item['value'];
if (isset($item['value2']) && $item['value2'] !== '') {
$value = $item['value2'];
}
$carry[strtolower($item['key'])] = $value;
return $carry;
}, []);
}
return $this->_secureForwardedHeaderParts;
}
}
yii\base\Action
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
/**
* Action is the base class for all controller action classes.
*
* Action provides a way to reuse action method code. An action method in an Action
* class can be used in multiple controllers or in different projects.
*
* Derived classes must implement a method named `run()`. This method
* will be invoked by the controller when the action is requested.
* The `run()` method can have parameters which will be filled up
* with user input values automatically according to their names.
* For example, if the `run()` method is declared as follows:
*
*
* public function run($id, $type = 'book') { ... }
*
*
* And the parameters provided for the action are: `['id' => 1]`.
* Then the `run()` method will be invoked as `run(1)` automatically.
*
* For more details and usage information on Action, see the [guide article on actions](guide:structure-controllers).
*
* @property-read string $uniqueId The unique ID of this action among the whole application.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Action extends Component
{
/**
* @var string ID of the action
*/
public $id;
/**
* @var Controller|\yii\web\Controller|\yii\console\Controller the controller that owns this action
*/
public $controller;
/**
* Constructor.
*
* @param string $id the ID of this action
* @param Controller $controller the controller that owns this action
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($id, $controller, $config = [])
{
$this->id = $id;
$this->controller = $controller;
parent::__construct($config);
}
/**
* Returns the unique ID of this action among the whole application.
*
* @return string the unique ID of this action among the whole application.
*/
public function getUniqueId()
{
return $this->controller->getUniqueId() . '/' . $this->id;
}
/**
* Runs this action with the specified parameters.
* This method is mainly invoked by the controller.
*
* @param array $params the parameters to be bound to the action's run() method.
* @return mixed the result of the action
* @throws InvalidConfigException if the action class does not have a run() method
*/
public function runWithParams($params)
{
if (!method_exists($this, 'run')) {
throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
}
$args = $this->controller->bindActionParams($this, $params);
Yii::debug('Running action: ' . get_class($this) . '::run(), invoked by ' . get_class($this->controller), __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}
if ($this->beforeRun()) {
$result = call_user_func_array([$this, 'run'], $args);
$this->afterRun();
return $result;
}
return null;
}
/**
* This method is called right before `run()` is executed.
* You may override this method to do preparation work for the action run.
* If the method returns false, it will cancel the action.
*
* @return bool whether to run the action.
*/
protected function beforeRun()
{
return true;
}
/**
* This method is called right after `run()` is executed.
* You may override this method to do post-processing work for the action run.
*/
protected function afterRun()
{
}
}
yii\base\Controller
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use yii\di\Instance;
use yii\di\NotInstantiableException;
/**
* Controller is the base class for classes containing controller logic.
*
* For more details and usage information on Controller, see the [guide article on controllers](guide:structure-controllers).
*
* @property-read Module[] $modules All ancestor modules that this controller is located within.
* @property-read string $route The route (module ID, controller ID and action ID) of the current request.
* @property-read string $uniqueId The controller ID that is prefixed with the module ID (if any).
* @property View|\yii\web\View $view The view object that can be used to render views or view files.
* @property string $viewPath The directory containing the view files for this controller.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Controller extends Component implements ViewContextInterface
{
/**
* @event ActionEvent an event raised right before executing a controller action.
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
*/
const EVENT_BEFORE_ACTION = 'beforeAction';
/**
* @event ActionEvent an event raised right after executing a controller action.
*/
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var string the ID of this controller.
*/
public $id;
/**
* @var Module the module that this controller belongs to.
*/
public $module;
/**
* @var string the ID of the action that is used when the action ID is not specified
* in the request. Defaults to 'index'.
*/
public $defaultAction = 'index';
/**
* @var null|string|false the name of the layout to be applied to this controller's views.
* This property mainly affects the behavior of [[render()]].
* Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value.
* If false, no layout will be applied.
*/
public $layout;
/**
* @var Action|null the action that is currently being executed. This property will be set
* by [[run()]] when it is called by [[Application]] to run an action.
*/
public $action;
/**
* @var Request|array|string The request.
* @since 2.0.36
*/
public $request = 'request';
/**
* @var Response|array|string The response.
* @since 2.0.36
*/
public $response = 'response';
/**
* @var View|null the view object that can be used to render views or view files.
*/
private $_view;
/**
* @var string|null the root directory that contains view files for this controller.
*/
private $_viewPath;
/**
* @param string $id the ID of this controller.
* @param Module $module the module that this controller belongs to.
* @param array $config name-value pairs that will be used to initialize the object properties.
*/
public function __construct($id, $module, $config = [])
{
$this->id = $id;
$this->module = $module;
parent::__construct($config);
}
/**
* {@inheritdoc}
* @since 2.0.36
*/
public function init()
{
parent::init();
$this->request = Instance::ensure($this->request, Request::className());
$this->response = Instance::ensure($this->response, Response::className());
}
/**
* Declares external actions for the controller.
*
* This method is meant to be overwritten to declare external actions for the controller.
* It should return an array, with array keys being action IDs, and array values the corresponding
* action class names or action configuration arrays. For example,
*
* ```php
* return [
* 'action1' => 'app\components\Action1',
* 'action2' => [
* 'class' => 'app\components\Action2',
* 'property1' => 'value1',
* 'property2' => 'value2',
* ],
* ];
* ```
*
* [[\Yii::createObject()]] will be used later to create the requested action
* using the configuration provided here.
* @return array
*/
public function actions()
{
return [];
}
/**
* Runs an action within this controller 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 mixed the result of the action.
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @see createAction()
*/
public function runAction($id, $params = [])
{
$action = $this->createAction($id);
if ($action === null) {
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
Yii::debug('Route to run: ' . $action->getUniqueId(), __METHOD__);
if (Yii::$app->requestedAction === null) {
Yii::$app->requestedAction = $action;
}
$oldAction = $this->action;
$this->action = $action;
$modules = [];
$runAction = true;
// call beforeAction on modules
foreach ($this->getModules() as $module) {
if ($module->beforeAction($action)) {
array_unshift($modules, $module);
} else {
$runAction = false;
break;
}
}
$result = null;
if ($runAction && $this->beforeAction($action)) {
// run the action
$result = $action->runWithParams($params);
$result = $this->afterAction($action, $result);
// call afterAction on modules
foreach ($modules as $module) {
/* @var $module Module */
$result = $module->afterAction($action, $result);
}
}
if ($oldAction !== null) {
$this->action = $oldAction;
}
return $result;
}
/**
* Runs a request specified in terms of a route.
* The route can be either an ID of an action within this controller or a complete route consisting
* of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of
* the route will start from the application; otherwise, it will start from the parent module of this controller.
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @param array $params the parameters to be passed to the action.
* @return mixed the result of the action.
* @see runAction()
*/
public function run($route, $params = [])
{
$pos = strpos($route, '/');
if ($pos === false) {
return $this->runAction($route, $params);
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
}
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
/**
* Binds the parameters to the action.
* This method is invoked by [[Action]] when it begins to run with the given parameters.
* @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.
*/
public function bindActionParams($action, $params)
{
return [];
}
/**
* Creates an action based on the given action ID.
* The method first checks if the action ID has been declared in [[actions()]]. If so,
* it will use the configuration declared there to create the action object.
* If not, it will look for a controller method whose name is in the format of `actionXyz`
* where `xyz` is the action ID. If found, an [[InlineAction]] representing that
* method will be created and returned.
* @param string $id the action ID.
* @return Action|null the newly created action instance. Null if the ID doesn't resolve into any action.
*/
public function createAction($id)
{
if ($id === '') {
$id = $this->defaultAction;
}
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
return Yii::createObject($actionMap[$id], [$id, $this]);
}
if (preg_match('/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/', $id)) {
$methodName = 'action' . str_replace(' ', '', ucwords(str_replace('-', ' ', $id)));
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->isPublic() && $method->getName() === $methodName) {
return new InlineAction($id, $this, $methodName);
}
}
}
return null;
}
/**
* This method is invoked right before an action is executed.
*
* The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method
* will determine whether the action should continue to run.
*
* In case the action should not run, the request should be handled inside of the `beforeAction` code
* by either providing the necessary output or redirecting the request. Otherwise the response will be empty.
*
* If you override this method, your code should look like the following:
*
* ```php
* public function beforeAction($action)
* {
* // your custom code here, if you want the code to run before action filters,
* // which are triggered on the [[EVENT_BEFORE_ACTION]] event, e.g. PageCache or AccessControl
*
* if (!parent::beforeAction($action)) {
* return false;
* }
*
* // other custom code here
*
* return true; // or false to not run the action
* }
* ```
*
* @param Action $action the action to be executed.
* @return bool whether the action should continue to run.
*/
public function beforeAction($action)
{
$event = new ActionEvent($action);
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
}
/**
* This method is invoked right after an action is executed.
*
* The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method
* will be used as the action return value.
*
* If you override this method, your code should look like the following:
*
* ```php
* public function afterAction($action, $result)
* {
* $result = parent::afterAction($action, $result);
* // your custom code here
* return $result;
* }
* ```
*
* @param Action $action the action just executed.
* @param mixed $result the action return result.
* @return mixed the processed action result.
*/
public function afterAction($action, $result)
{
$event = new ActionEvent($action);
$event->result = $result;
$this->trigger(self::EVENT_AFTER_ACTION, $event);
return $event->result;
}
/**
* Returns all ancestor modules of this controller.
* The first module in the array is the outermost one (i.e., the application instance),
* while the last is the innermost one.
* @return Module[] all ancestor modules that this controller is located within.
*/
public function getModules()
{
$modules = [$this->module];
$module = $this->module;
while ($module->module !== null) {
array_unshift($modules, $module->module);
$module = $module->module;
}
return $modules;
}
/**
* Returns the unique ID of the controller.
* @return string the controller ID that is prefixed with the module ID (if any).
*/
public function getUniqueId()
{
return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
}
/**
* Returns the route of the current request.
* @return string the route (module ID, controller ID and action ID) of the current request.
*/
public function getRoute()
{
return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
}
/**
* Renders a view and applies layout if available.
*
* The view to be rendered can be specified in one of the following formats:
*
* - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* To determine which layout should be applied, the following two steps are conducted:
*
* 1. In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* 2. In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be:
*
* - a [path alias](guide:concept-aliases) (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout file will be looked for under the
* [[Module::layoutPath|layout path]] of the context module.
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @param string $view the view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* These parameters will not be available in the layout.
* @return string the rendering result.
* @throws InvalidArgumentException if the view file or the layout file does not exist.
*/
public function render($view, $params = [])
{
$content = $this->getView()->render($view, $params, $this);
return $this->renderContent($content);
}
/**
* Renders a static string by applying a layout.
* @param string $content the static string being rendered
* @return string the rendering result of the layout with the given static string as the `$content` variable.
* If the layout is disabled, the string will be returned back.
* @since 2.0.1
*/
public function renderContent($content)
{
$layoutFile = $this->findLayoutFile($this->getView());
if ($layoutFile !== false) {
return $this->getView()->renderFile($layoutFile, ['content' => $content], $this);
}
return $content;
}
/**
* Renders a view without applying layout.
* This method differs from [[render()]] in that it does not apply any layout.
* @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidArgumentException if the view file does not exist.
*/
public function renderPartial($view, $params = [])
{
return $this->getView()->render($view, $params, $this);
}
/**
* Renders a view file.
* @param string $file the view file to be rendered. This can be either a file path or a [path alias](guide:concept-aliases).
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidArgumentException if the view file does not exist.
*/
public function renderFile($file, $params = [])
{
return $this->getView()->renderFile($file, $params, $this);
}
/**
* Returns the view object that can be used to render views or view files.
* The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
* this view object to implement the actual view rendering.
* If not set, it will default to the "view" application component.
* @return View|\yii\web\View the view object that can be used to render views or view files.
*/
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
/**
* Sets the view object to be used by this controller.
* @param View|\yii\web\View $view the view object that can be used to render views or view files.
*/
public function setView($view)
{
$this->_view = $view;
}
/**
* Returns the directory containing view files for this controller.
* The default implementation returns the directory named as controller [[id]] under the [[module]]'s
* [[viewPath]] directory.
* @return string the directory containing the view files for this controller.
*/
public function getViewPath()
{
if ($this->_viewPath === null) {
$this->_viewPath = $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}
return $this->_viewPath;
}
/**
* Sets the directory that contains the view files.
* @param string $path the root directory of view files.
* @throws InvalidArgumentException if the directory is invalid
* @since 2.0.7
*/
public function setViewPath($path)
{
$this->_viewPath = Yii::getAlias($path);
}
/**
* Finds the applicable layout file.
* @param View $view the view object to render the layout file.
* @return string|bool the layout file path, or false if layout is not needed.
* Please refer to [[render()]] on how to specify this parameter.
* @throws InvalidArgumentException if an invalid path alias is used to specify the layout.
*/
public function findLayoutFile($view)
{
$module = $this->module;
$layout = null;
if (is_string($this->layout)) {
$layout = $this->layout;
} elseif ($this->layout === null) {
while ($module !== null && $module->layout === null) {
$module = $module->module;
}
if ($module !== null && is_string($module->layout)) {
$layout = $module->layout;
}
}
if ($layout === null) {
return false;
}
if (strncmp($layout, '@', 1) === 0) {
$file = Yii::getAlias($layout);
} elseif (strncmp($layout, '/', 1) === 0) {
$file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1);
} else {
$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
}
if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
return $file;
}
$path = $file . '.' . $view->defaultExtension;
if ($view->defaultExtension !== 'php' && !is_file($path)) {
$path = $file . '.php';
}
return $path;
}
/**
* Fills parameters based on types and names in action method signature.
* @param \ReflectionType $type The reflected type of the action parameter.
* @param string $name The name of the parameter.
* @param array &$args The array of arguments for the action, this function may append items to it.
* @param array &$requestedParams The array with requested params, this function may write specific keys to it.
* @throws ErrorException when we cannot load a required service.
* @throws InvalidConfigException Thrown when there is an error in the DI configuration.
* @throws NotInstantiableException Thrown when a definition cannot be resolved to a concrete class
* (for example an interface type hint) without a proper definition in the container.
* @since 2.0.36
*/
final protected function bindInjectedParams(\ReflectionType $type, $name, &$args, &$requestedParams)
{
// Since it is not a builtin type it must be DI injection.
$typeName = $type->getName();
if (($component = $this->module->get($name, false)) instanceof $typeName) {
$args[] = $component;
$requestedParams[$name] = "Component: " . get_class($component) . " \$$name";
} elseif ($this->module->has($typeName) && ($service = $this->module->get($typeName)) instanceof $typeName) {
$args[] = $service;
$requestedParams[$name] = 'Module ' . get_class($this->module) . " DI: $typeName \$$name";
} elseif (\Yii::$container->has($typeName) && ($service = \Yii::$container->get($typeName)) instanceof $typeName) {
$args[] = $service;
$requestedParams[$name] = "Container DI: $typeName \$$name";
} elseif ($type->allowsNull()) {
$args[] = null;
$requestedParams[$name] = "Unavailable service: $name";
} else {
throw new Exception('Could not load required service: ' . $name);
}
}
}
yii\base\Module
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use yii\di\ServiceLocator;
/**
* Module is the base class for module and application classes.
*
* A module represents a sub-application which contains MVC elements by itself, such as
* models, views, controllers, etc.
*
* A module may consist of [[modules|sub-modules]].
*
* [[components|Components]] may be registered with the module so that they are globally
* accessible within the module.
*
* For more details and usage information on Module, see the [guide article on modules](guide:structure-modules).
*
* @property-write array $aliases List of path aliases to be defined. The array keys are alias names (must
* start with `@`) and the array values are the corresponding paths or aliases. See [[setAliases()]] for an
* example.
* @property string $basePath The root directory of the module.
* @property string $controllerPath The directory that contains the controller classes.
* @property string $layoutPath The root directory of layout files. Defaults to "[[viewPath]]/layouts".
* @property array $modules The modules (indexed by their IDs).
* @property-read string $uniqueId The unique ID of the module.
* @property string $version The version of this module. Note that the type of this property differs in getter
* and setter. See [[getVersion()]] and [[setVersion()]] for details.
* @property string $viewPath The root directory of view files. Defaults to "[[basePath]]/views".
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Module extends ServiceLocator
{
/**
* @event ActionEvent an event raised before executing a controller action.
* You may set [[ActionEvent::isValid]] to be `false` to cancel the action execution.
*/
const EVENT_BEFORE_ACTION = 'beforeAction';
/**
* @event ActionEvent an event raised after executing a controller action.
*/
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var array custom module parameters (name => value).
*/
public $params = [];
/**
* @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
*/
public $id;
/**
* @var Module|null the parent module of this module. `null` if this module does not have a parent.
*/
public $module;
/**
* @var string|bool|null the layout that should be applied for views within this module. This refers to a view name
* relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
* will be taken. If this is `false`, layout will be disabled within this module.
*/
public $layout;
/**
* @var array mapping from controller ID to controller configurations.
* Each name-value pair specifies the configuration of a single controller.
* A controller configuration can be either a string or an array.
* If the former, the string should be the fully qualified class name of the controller.
* If the latter, the array must contain a `class` element which specifies
* the controller's fully qualified class name, and the rest of the name-value pairs
* in the array are used to initialize the corresponding controller properties. For example,
*
* ```php
* [
* 'account' => 'app\controllers\UserController',
* 'article' => [
* 'class' => 'app\controllers\PostController',
* 'pageTitle' => 'something new',
* ],
* ]
* ```
*/
public $controllerMap = [];
/**
* @var string|null the namespace that controller classes are in.
* This namespace will be used to load controller classes by prepending it to the controller
* class name.
*
* If not set, it will use the `controllers` sub-namespace under the namespace of this module.
* For example, if the namespace of this module is `foo\bar`, then the default
* controller namespace would be `foo\bar\controllers`.
*
* See also the [guide section on autoloading](guide:concept-autoloading) to learn more about
* defining namespaces and how classes are loaded.
*/
public $controllerNamespace;
/**
* @var string the default route of this module. Defaults to `default`.
* The route may consist of child module ID, controller ID, and/or action ID.
* For example, `help`, `post/create`, `admin/post/create`.
* If action ID is not given, it will take the default value as specified in
* [[Controller::defaultAction]].
*/
public $defaultRoute = 'default';
/**
* @var string the root directory of the module.
*/
private $_basePath;
/**
* @var string The root directory that contains the controller classes for this module.
*/
private $_controllerPath;
/**
* @var string the root directory that contains view files for this module
*/
private $_viewPath;
/**
* @var string the root directory that contains layout view files for this module.
*/
private $_layoutPath;
/**
* @var array child modules of this module
*/
private $_modules = [];
/**
* @var string|callable the version of this module.
* Version can be specified as a PHP callback, which can accept module instance as an argument and should
* return the actual version. For example:
*
* ```php
* function (Module $module) {
* //return string|int
* }
* ```
*
* If not set, [[defaultVersion()]] will be used to determine actual value.
*
* @since 2.0.11
*/
private $_version;
/**
* Constructor.
* @param string $id the ID of this module.
* @param Module $parent the parent module (if any).
* @param array $config name-value pairs that will be used to initialize the object properties.
*/
public function __construct($id, $parent = null, $config = [])
{
$this->id = $id;
$this->module = $parent;
parent::__construct($config);
}
/**
* Returns the currently requested instance of this module class.
* If the module class is not currently requested, `null` will be returned.
* This method is provided so that you access the module instance from anywhere within the module.
* @return static|null the currently requested instance of this module class, or `null` if the module class is not requested.
*/
public static function getInstance()
{
$class = get_called_class();
return isset(Yii::$app->loadedModules[$class]) ? Yii::$app->loadedModules[$class] : null;
}
/**
* Sets the currently requested instance of this module class.
* @param Module|null $instance the currently requested instance of this module class.
* If it is `null`, the instance of the calling class will be removed, if any.
*/
public static function setInstance($instance)
{
if ($instance === null) {
unset(Yii::$app->loadedModules[get_called_class()]);
} else {
Yii::$app->loadedModules[get_class($instance)] = $instance;
}
}
/**
* Initializes the module.
*
* This method is called after the module is created and initialized with property values
* given in configuration. The default implementation will initialize [[controllerNamespace]]
* if it is not set.
*
* If you override this method, please make sure you call the parent implementation.
*/
public function init()
{
if ($this->controllerNamespace === null) {
$class = get_class($this);
if (($pos = strrpos($class, '\\')) !== false) {
$this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';
}
}
}
/**
* Returns an ID that uniquely identifies this module among all modules within the current application.
* Note that if the module is an application, an empty string will be returned.
* @return string the unique ID of the module.
*/
public function getUniqueId()
{
return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id;
}
/**
* Returns the root directory of the module.
* It defaults to the directory containing the module class file.
* @return string the root directory of the module.
*/
public function getBasePath()
{
if ($this->_basePath === null) {
$class = new \ReflectionClass($this);
$this->_basePath = dirname($class->getFileName());
}
return $this->_basePath;
}
/**
* Sets the root directory of the module.
* This method can only be invoked at the beginning of the constructor.
* @param string $path the root directory of the module. This can be either a directory name or a [path alias](guide:concept-aliases).
* @throws InvalidArgumentException if the directory does not exist.
*/
public function setBasePath($path)
{
$path = Yii::getAlias($path);
$p = strncmp($path, 'phar://', 7) === 0 ? $path : realpath($path);
if (is_string($p) && is_dir($p)) {
$this->_basePath = $p;
} else {
throw new InvalidArgumentException("The directory does not exist: $path");
}
}
/**
* Returns the directory that contains the controller classes according to [[controllerNamespace]].
* Note that in order for this method to return a value, you must define
* an alias for the root namespace of [[controllerNamespace]].
* @return string the directory that contains the controller classes.
* @throws InvalidArgumentException if there is no alias defined for the root namespace of [[controllerNamespace]].
*/
public function getControllerPath()
{
if ($this->_controllerPath === null) {
$this->_controllerPath = Yii::getAlias('@' . str_replace('\\', '/', $this->controllerNamespace));
}
return $this->_controllerPath;
}
/**
* Sets the directory that contains the controller classes.
* @param string $path the root directory that contains the controller classes.
* @throws InvalidArgumentException if the directory is invalid.
* @since 2.0.44
*/
public function setControllerPath($path)
{
$this->_controllerPath = Yii::getAlias($path);
}
/**
* Returns the directory that contains the view files for this module.
* @return string the root directory of view files. Defaults to "[[basePath]]/views".
*/
public function getViewPath()
{
if ($this->_viewPath === null) {
$this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
}
return $this->_viewPath;
}
/**
* Sets the directory that contains the view files.
* @param string $path the root directory of view files.
* @throws InvalidArgumentException if the directory is invalid.
*/
public function setViewPath($path)
{
$this->_viewPath = Yii::getAlias($path);
}
/**
* Returns the directory that contains layout view files for this module.
* @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
*/
public function getLayoutPath()
{
if ($this->_layoutPath === null) {
$this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
}
return $this->_layoutPath;
}
/**
* Sets the directory that contains the layout files.
* @param string $path the root directory or [path alias](guide:concept-aliases) of layout files.
* @throws InvalidArgumentException if the directory is invalid
*/
public function setLayoutPath($path)
{
$this->_layoutPath = Yii::getAlias($path);
}
/**
* Returns current module version.
* If version is not explicitly set, [[defaultVersion()]] method will be used to determine its value.
* @return string the version of this module.
* @since 2.0.11
*/
public function getVersion()
{
if ($this->_version === null) {
$this->_version = $this->defaultVersion();
} else {
if (!is_scalar($this->_version)) {
$this->_version = call_user_func($this->_version, $this);
}
}
return $this->_version;
}
/**
* Sets current module version.
* @param string|callable $version the version of this module.
* Version can be specified as a PHP callback, which can accept module instance as an argument and should
* return the actual version. For example:
*
* ```php
* function (Module $module) {
* //return string
* }
* ```
*
* @since 2.0.11
*/
public function setVersion($version)
{
$this->_version = $version;
}
/**
* Returns default module version.
* Child class may override this method to provide more specific version detection.
* @return string the version of this module.
* @since 2.0.11
*/
protected function defaultVersion()
{
if ($this->module === null) {
return '1.0';
}
return $this->module->getVersion();
}
/**
* Defines path aliases.
* This method calls [[Yii::setAlias()]] to register the path aliases.
* This method is provided so that you can define path aliases when configuring a module.
* @property array list of path aliases to be defined. The array keys are alias names
* (must start with `@`) and the array values are the corresponding paths or aliases.
* See [[setAliases()]] for an example.
* @param array $aliases list of path aliases to be defined. The array keys are alias names
* (must start with `@`) and the array values are the corresponding paths or aliases.
* For example,
*
* ```php
* [
* '@models' => '@app/models', // an existing alias
* '@backend' => __DIR__ . '/../backend', // a directory
* ]
* ```
*/
public function setAliases($aliases)
{
foreach ($aliases as $name => $alias) {
Yii::setAlias($name, $alias);
}
}
/**
* Checks whether the child module of the specified ID exists.
* This method supports checking the existence of both child and grand child modules.
* @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`).
* @return bool whether the named module exists. Both loaded and unloaded modules
* are considered.
*/
public function hasModule($id)
{
if (($pos = strpos($id, '/')) !== false) {
// sub-module
$module = $this->getModule(substr($id, 0, $pos));
return $module === null ? false : $module->hasModule(substr($id, $pos + 1));
}
return isset($this->_modules[$id]);
}
/**
* Retrieves the child module of the specified ID.
* This method supports retrieving both child modules and grand child modules.
* @param string $id module ID (case-sensitive). To retrieve grand child modules,
* use ID path relative to this module (e.g. `admin/content`).
* @param bool $load whether to load the module if it is not yet loaded.
* @return Module|null the module instance, `null` if the module does not exist.
* @see hasModule()
*/
public function getModule($id, $load = true)
{
if (($pos = strpos($id, '/')) !== false) {
// sub-module
$module = $this->getModule(substr($id, 0, $pos));
return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load);
}
if (isset($this->_modules[$id])) {
if ($this->_modules[$id] instanceof self) {
return $this->_modules[$id];
} elseif ($load) {
Yii::debug("Loading module: $id", __METHOD__);
/* @var $module Module */
$module = Yii::createObject($this->_modules[$id], [$id, $this]);
$module::setInstance($module);
return $this->_modules[$id] = $module;
}
}
return null;
}
/**
* Adds a sub-module to this module.
* @param string $id module ID.
* @param Module|array|null $module the sub-module to be added to this module. This can
* be one of the following:
*
* - a [[Module]] object
* - a configuration array: when [[getModule()]] is called initially, the array
* will be used to instantiate the sub-module
* - `null`: the named sub-module will be removed from this module
*/
public function setModule($id, $module)
{
if ($module === null) {
unset($this->_modules[$id]);
} else {
$this->_modules[$id] = $module;
if ($module instanceof self) {
$module->module = $this;
}
}
}
/**
* Returns the sub-modules in this module.
* @param bool $loadedOnly whether to return the loaded sub-modules only. If this is set `false`,
* then all sub-modules registered in this module will be returned, whether they are loaded or not.
* Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
* @return array the modules (indexed by their IDs).
*/
public function getModules($loadedOnly = false)
{
if ($loadedOnly) {
$modules = [];
foreach ($this->_modules as $module) {
if ($module instanceof self) {
$modules[] = $module;
}
}
return $modules;
}
return $this->_modules;
}
/**
* Registers sub-modules in the current module.
*
* Each sub-module should be specified as a name-value pair, where
* name refers to the ID of the module and value the module or a configuration
* array that can be used to create the module. In the latter case, [[Yii::createObject()]]
* will be used to create the module.
*
* If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
*
* The following is an example for registering two sub-modules:
*
* ```php
* [
* 'comment' => [
* 'class' => 'app\modules\comment\CommentModule',
* 'db' => 'db',
* ],
* 'booking' => ['class' => 'app\modules\booking\BookingModule'],
* ]
* ```
*
* @param array $modules modules (id => module configuration or instances).
*/
public function setModules($modules)
{
foreach ($modules as $id => $module) {
$this->_modules[$id] = $module;
if ($module instanceof self) {
$module->module = $this;
}
}
}
/**
* 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]].
* @param string $route the route that specifies the action.
* @param array $params the parameters to be passed to the action
* @return mixed the result of the action.
* @throws InvalidRouteException if the requested route cannot be resolved into an action successfully.
*/
public function runAction($route, $params = [])
{
$parts = $this->createController($route);
if (is_array($parts)) {
/* @var $controller Controller */
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
$result = $controller->runAction($actionID, $params);
if ($oldController !== null) {
Yii::$app->controller = $oldController;
}
return $result;
}
$id = $this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
}
/**
* Creates a controller instance based on the given route.
*
* The route should be relative to this module. The method implements the following algorithm
* to resolve the given route:
*
* 1. If the route is empty, use [[defaultRoute]];
* 2. If the first segment of the route is found in [[controllerMap]], create a controller
* based on the corresponding configuration found in [[controllerMap]];
* 3. If the first segment of the route is a valid module ID as declared in [[modules]],
* call the module's `createController()` with the rest part of the route;
* 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
* or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
*
* If any of the above steps resolves into a controller, it is returned together with the rest
* part of the route which will be treated as the action ID. Otherwise, `false` will be returned.
*
* @param string $route the route consisting of module, controller and action IDs.
* @return array|bool If the controller is created successfully, it will be returned together
* with the requested action ID. Otherwise `false` will be returned.
* @throws InvalidConfigException if the controller class and its file do not match.
*/
public function createController($route)
{
if ($route === '') {
$route = $this->defaultRoute;
}
// double slashes or leading/ending slashes may cause substr problem
$route = trim($route, '/');
if (strpos($route, '//') !== false) {
return false;
}
if (strpos($route, '/') !== false) {
list($id, $route) = explode('/', $route, 2);
} else {
$id = $route;
$route = '';
}
// module and controller map take precedence
if (isset($this->controllerMap[$id])) {
$controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
return [$controller, $route];
}
$module = $this->getModule($id);
if ($module !== null) {
return $module->createController($route);
}
if (($pos = strrpos($route, '/')) !== false) {
$id .= '/' . substr($route, 0, $pos);
$route = substr($route, $pos + 1);
}
$controller = $this->createControllerByID($id);
if ($controller === null && $route !== '') {
$controller = $this->createControllerByID($id . '/' . $route);
$route = '';
}
return $controller === null ? false : [$controller, $route];
}
/**
* Creates a controller based on the given controller ID.
*
* The controller ID is relative to this module. The controller class
* should be namespaced under [[controllerNamespace]].
*
* Note that this method does not check [[modules]] or [[controllerMap]].
*
* @param string $id the controller ID.
* @return Controller|null the newly created controller instance, or `null` if the controller ID is invalid.
* @throws InvalidConfigException if the controller class and its file name do not match.
* This exception is only thrown when in debug mode.
*/
public function createControllerByID($id)
{
$pos = strrpos($id, '/');
if ($pos === false) {
$prefix = '';
$className = $id;
} else {
$prefix = substr($id, 0, $pos + 1);
$className = substr($id, $pos + 1);
}
if ($this->isIncorrectClassNameOrPrefix($className, $prefix)) {
return null;
}
$className = preg_replace_callback('%-([a-z0-9_])%i', function ($matches) {
return ucfirst($matches[1]);
}, ucfirst($className)) . 'Controller';
$className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
if (strpos($className, '-') !== false || !class_exists($className)) {
return null;
}
if (is_subclass_of($className, 'yii\base\Controller')) {
$controller = Yii::createObject($className, [$id, $this]);
return get_class($controller) === $className ? $controller : null;
} elseif (YII_DEBUG) {
throw new InvalidConfigException('Controller class must extend from \\yii\\base\\Controller.');
}
return null;
}
/**
* Checks if class name or prefix is incorrect
*
* @param string $className
* @param string $prefix
* @return bool
*/
private function isIncorrectClassNameOrPrefix($className, $prefix)
{
if (!preg_match('%^[a-z][a-z0-9\\-_]*$%', $className)) {
return true;
}
if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) {
return true;
}
return false;
}
/**
* This method is invoked right before an action within this module is executed.
*
* The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method
* will determine whether the action should continue to run.
*
* In case the action should not run, the request should be handled inside of the `beforeAction` code
* by either providing the necessary output or redirecting the request. Otherwise the response will be empty.
*
* If you override this method, your code should look like the following:
*
* ```php
* public function beforeAction($action)
* {
* if (!parent::beforeAction($action)) {
* return false;
* }
*
* // your custom code here
*
* return true; // or false to not run the action
* }
* ```
*
* @param Action $action the action to be executed.
* @return bool whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$event = new ActionEvent($action);
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
}
/**
* This method is invoked right after an action within this module is executed.
*
* The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method
* will be used as the action return value.
*
* If you override this method, your code should look like the following:
*
* ```php
* public function afterAction($action, $result)
* {
* $result = parent::afterAction($action, $result);
* // your custom code here
* return $result;
* }
* ```
*
* @param Action $action the action just executed.
* @param mixed $result the action return result.
* @return mixed the processed action result.
*/
public function afterAction($action, $result)
{
$event = new ActionEvent($action);
$event->result = $result;
$this->trigger(self::EVENT_AFTER_ACTION, $event);
return $event->result;
}
/**
* {@inheritdoc}
*
* Since version 2.0.13, if a component isn't defined in the module, it will be looked up in the parent module.
* The parent module may be the application.
*/
public function get($id, $throwException = true)
{
if (!isset($this->module)) {
return parent::get($id, $throwException);
}
$component = parent::get($id, false);
if ($component === null) {
$component = $this->module->get($id, $throwException);
}
return $component;
}
/**
* {@inheritdoc}
*
* Since version 2.0.13, if a component isn't defined in the module, it will be looked up in the parent module.
* The parent module may be the application.
*/
public function has($id, $checkInstance = false)
{
return parent::has($id, $checkInstance) || (isset($this->module) && $this->module->has($id, $checkInstance));
}
}
yii\web\Application
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\InvalidRouteException;
use yii\helpers\Url;
/**
* Application is the base class for all web application classes.
*
* For more details and usage information on Application, see the [guide article on applications](guide:structure-applications).
*
* @property-read ErrorHandler $errorHandler The error handler application component.
* @property string $homeUrl The homepage URL.
* @property-read Request $request The request component.
* @property-read Response $response The response component.
* @property-read Session $session The session component.
* @property-read User $user The user component.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Application extends \yii\base\Application
{
/**
* @var string the default route of this application. Defaults to 'site'.
*/
public $defaultRoute = 'site';
/**
* @var array the configuration specifying a controller action which should handle
* all user requests. This is mainly used when the application is in maintenance mode
* and needs to handle all incoming requests via a single action.
* The configuration is an array whose first element specifies the route of the action.
* The rest of the array elements (key-value pairs) specify the parameters to be bound
* to the action. For example,
*
* ```php
* [
* 'offline/notice',
* 'param1' => 'value1',
* 'param2' => 'value2',
* ]
* ```
*
* Defaults to null, meaning catch-all is not used.
*/
public $catchAll;
/**
* @var Controller the currently active controller instance
*/
public $controller;
/**
* {@inheritdoc}
*/
protected function bootstrap()
{
$request = $this->getRequest();
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
parent::bootstrap();
}
/**
* Handles the specified request.
* @param Request $request the request to be handled
* @return Response the resulting response
* @throws NotFoundHttpException if the requested route is invalid
*/
public function handleRequest($request)
{
if (empty($this->catchAll)) {
try {
list($route, $params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
$url = $e->url;
if (is_array($url)) {
if (isset($url[0])) {
// ensure the route is absolute
$url[0] = '/' . ltrim($url[0], '/');
}
$url += $request->getQueryParams();
}
return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} else {
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::debug("Route requested: '$route'", __METHOD__);
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);
if ($result instanceof Response) {
return $result;
}
$response = $this->getResponse();
if ($result !== null) {
$response->data = $result;
}
return $response;
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
}
}
private $_homeUrl;
/**
* @return string the homepage URL
*/
public function getHomeUrl()
{
if ($this->_homeUrl === null) {
if ($this->getUrlManager()->showScriptName) {
return $this->getRequest()->getScriptUrl();
}
return $this->getRequest()->getBaseUrl() . '/';
}
return $this->_homeUrl;
}
/**
* @param string $value the homepage URL
*/
public function setHomeUrl($value)
{
$this->_homeUrl = $value;
}
/**
* 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');
}
/**
* Returns the session component.
* @return Session the session component.
*/
public function getSession()
{
return $this->get('session');
}
/**
* Returns the user component.
* @return User the user component.
*/
public function getUser()
{
return $this->get('user');
}
/**
* {@inheritdoc}
*/
public function coreComponents()
{
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'],
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
]);
}
}
yii\console\Application
类
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
use Yii;
use yii\base\InvalidRouteException;
// 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.
*/
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 the currently active controller instance
*/
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:
*
* ```php
* \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 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'],
]);
}
}
附图
参考资料
深入理解Yii2.0 » 请求与响应(TBD) » 请求(Reqeust)
http://www.digpage.com/request.html
https://www.kancloud.cn/kancloud/yii-in-depth/50782
$_SERVER[‘argv’] https://www.php.net/manual/zh/reserved.variables.server.php
preg_match https://www.php.net/manual/zh/function.preg-match.php