/ yii2  

Yii2 源码阅读 05 - BaseYii Yii

BaseYii

BaseYii is the core helper class for the Yii framework.

defined

  • YII_BEGIN_TIME microtime(true)
  • YII2_PATH __DIR__
  • YII_DEBUG false
  • YII_ENV prod(dev test staging)
  • YII_ENV_PROD
  • YII_ENV_DEV
  • YII_ENV_TEST
  • YII_ENABLE_ERROR_HANDLER true

属性

  • public static $classMap = []; [class names => array] @see autoload()
  • public static $app;
  • public static $aliases = ['@yii' => __DIR__];
  • public static $container; @see createObject()

Alias

参见:别名(Aliases)

/**
* Registers a path alias.
*
* 路径别名必须以字符 '@' 开头,这样就可以很容易地与非别名路径区分开来。
*
* 注意,此方法不检查给定路径是否存在。它所做的只是将别名与路径关联起来。
*
* 给定路径中的任何尾随 '/' 和 '\' 字符将被修剪。
*
* @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character.
* It may contain the forward slash '/' which serves as boundary character when performing
* alias translation by [[getAlias()]].
* @param string $path the path corresponding to the alias. If this is null, the alias will
* be removed. Trailing '/' and '\' characters will be trimmed. This can be
*
* - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`)
* - a URL (e.g. `http://www.yiiframework.com`)
* - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the
* actual path first by calling [[getAlias()]].
*
* @throws InvalidArgumentException if $path is an invalid alias.
* @see getAlias()
*/
public static function setAlias($alias, $path)
{
// strncmp: Binary safe string comparison of the first n characters
// 0 if they are equal
if (strncmp($alias, '@', 1)) {
// $alias 不是以 @ 开头,补充 @
$alias = '@' . $alias;
}

// $alias 获取根名
// strpos: Find the position of the first occurrence of a substring in a string
$pos = strpos($alias, '/');
// $alias 没有 /,直接当别名,否则截取第一段
$root = $pos === false ? $alias : substr($alias, 0, $pos);
if ($path !== null) {
// $path 不是以 @ 开头,则清理 \ /;以 @ 开头,直接获取别名
$path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path);
if (!isset(static::$aliases[$root])) {
// $alias 根名不存在于 aliases 时
if ($pos === false) {
// $alias 没有 / 时,设置 $path 为 $alias
static::$aliases[$root] = $path;
} else {
// 如果 $alias 有 / 时,使用数组保存
static::$aliases[$root] = [$alias => $path];
}
} elseif (is_string(static::$aliases[$root])) {
// $alias 根名已存在于 aliases 同时为字符串时
if ($pos === false) {
// $alias 没有 / 时,设置 $path 为 $alias
static::$aliases[$root] = $path;
} else {
// 如果 $alias 有 / 时,使用数组保存
// 之前为字符串 转换为了数组方式保存
static::$aliases[$root] = [
// 新数据
$alias => $path,
// 之前的数据
$root => static::$aliases[$root],
];
}
} else {
// $alias 根名已存在于 aliases 同时为数组时
static::$aliases[$root][$alias] = $path;
// Sort an array by key in reverse order
// 比较巧妙:实现最长的别名优先
krsort(static::$aliases[$root]);
}
} elseif (isset(static::$aliases[$root])) {
// $path is null 如果已设置 则释放
if (is_array(static::$aliases[$root])) {
unset(static::$aliases[$root][$alias]);
} elseif ($pos === false) {
unset(static::$aliases[$root]);
}
}
}
Yii::setAlias('@my', '/a1/b2');
VarDumper::dump(Yii::$aliases);
// '@my' => '/a1/b2'

Yii::setAlias('@my/c', '/a1/b2/c3');
Yii::setAlias('@my/e/f/', '/a1/b2/f6/');
VarDumper::dump(Yii::$aliases);
// '@my' => [ '@my/e/f/' => '/a1/b2/f6' '@my/e' => '/a1/b2/e5' '@my' => '/a1/b2' ]
/**
* 将路径别名转换为实际的路径。
*
* 按照以下处理完成转换:
*
* 1. 如果别名不是以 '@' 开头,将直接返回。
* 2. 否则,寻找最长的注册别名匹配给定的开始部分别名。如果它存在,将匹配给定的别名的一部分替换为相应的注册路径。
* 3. 抛出一个异常或返回false,取决于 `$throwException` 参数。
*
* 例如:
* '@yii' is registered as the alias to the Yii framework directory say '/path/to/yii'.
* The alias '@yii/web' would then be translated into '/path/to/yii/web'.
*
* 如果已注册两个别名 '@foo' 与 '@foo/bar'。当转换 '@foo/bar/config' 时,将替换 '@foo/bar' 而不是 '@foo' 的部分与之相应的注册路径。
* 这是因为最长的别名优先。
*
* 然而,当转换 '@foo/barbar/config' 时,将替换 '@foo' 而不是 '@foo/bar',因为 '/' 作为字符的边界。
*
* 注意,此方法不会检查返回的路径是否存在。
*
* @param string $alias the alias to be translated.
* @param bool $throwException whether to throw an exception if the given alias is invalid.
* If this is false and an invalid alias is given, false will be returned by this method.
* @return string|false the path corresponding to the alias, false if the root alias is not previously registered.
* @throws InvalidArgumentException if the alias is invalid while $throwException is true.
* @see setAlias()
*/
public static function getAlias($alias, $throwException = true)
{
// 如果别名不是以 '@' 开头,将直接返回。
if (strpos($alias, '@') !== 0) {
// not an alias
return $alias;
}

$pos = strpos($alias, '/');
$root = $pos === false ? $alias : substr($alias, 0, $pos);

// 查找 $alias 根名
if (isset(static::$aliases[$root])) {
// 判断值是否为字符串,是字符串也说明只对应一个路径
if (is_string(static::$aliases[$root])) {
return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos);
}

// 寻找最长的注册别名
foreach (static::$aliases[$root] as $name => $path) {
// '/' 作为字符的边界
if (strpos($alias . '/', $name . '/') === 0) {
return $path . substr($alias, strlen($name));
}
}
}

if ($throwException) {
throw new InvalidArgumentException("Invalid path alias: $alias");
}

return false;
}
/**
* 返回根别名已定义的别名。
* 根别名是前面通过 setAlias() 注册的别名。如果给定的别名匹配多个根别名,则返回最长的一个。
* @param string $alias the alias
* @return string|false the root alias, or false if no root alias is found
*/
public static function getRootAlias($alias)
{
$pos = strpos($alias, '/');
$root = $pos === false ? $alias : substr($alias, 0, $pos);

if (isset(static::$aliases[$root])) {
if (is_string(static::$aliases[$root])) {
return $root;
}

foreach (static::$aliases[$root] as $name => $path) {
if (strpos($alias . '/', $name . '/') === 0) {
// 返回定义的最长 key
return $name;
}
}
}

return false;
}

Autoload

参见:类自动加载(Autoloading)

/**
* Class autoload loader.
*
* This method is invoked automatically when PHP sees an unknown class.
* The method will attempt to include the class file according to the following procedure:
*
* 1. Search in [[classMap]];
* 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt
* to include the file associated with the corresponding path alias
* (e.g. `@yii/base/Component.php`);
*
* This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/)
* and have its top-level namespace or sub-namespaces defined as path aliases.
*
* Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace
* will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension
* files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory.
*
*
* @param string $className the fully qualified class name without a leading backslash "\"
* @throws UnknownClassException if the class does not exist in the class file
*/
public static function autoload($className)
{
// classes.php:
// return [
// 'yii\base\Action' => YII2_PATH . '/base/Action.php',
// 'yii\base\ActionEvent' => YII2_PATH . '/base/ActionEvent.php',
// ...
if (isset(static::$classMap[$className])) {
$classFile = static::$classMap[$className];
// 使用别名
if (strpos($classFile, '@') === 0) {
$classFile = static::getAlias($classFile);
}
} elseif (strpos($className, '\\') !== false) {
// 包含 \
$classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);
if ($classFile === false || !is_file($classFile)) {
return;
}
} else {
return;
}

include $classFile;

if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {
// YII_DEBUG && class|interface|trait 不存在
throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");
}
}

Object

/**
* Creates a new object using the given configuration.
*
* 你可能会认为这种方法是一个增强版的 `new` 操作符。
*
* 该方法支持基于类名、配置数组或匿名函数创建对象。
*
* Using [[\yii\di\Container|dependency injection container]], this method can also identify
* dependent objects, instantiate them and inject them into the newly created object.
*
* @param string|array|callable $type the object type. This can be specified in one of the following forms:
*
* - a string: 代表要创建的对象的类名
* - a configuration array: the array must contain a `class` element which is treated as the object class,
* and the rest of the name-value pairs will be used to initialize the corresponding object properties
* - a PHP callable: 匿名函数或表示类方法的数组 (`[$class or $object, $method]`)。可调用对象应该返回正在创建的对象的新实例。
*
* @param array $params 构造函数的参数
* @return object the created object
* @throws InvalidConfigException if the configuration is invalid.
* @see \yii\di\Container
*/
public static function createObject($type, array $params = [])
{
// string,从容器中获取
if (is_string($type)) {
return static::$container->get($type, $params);
}

// callable
if (is_callable($type, true)) {
return static::$container->invoke($type, $params);
}

// 不是数组
if (!is_array($type)) {
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}

// __class 作用用 class,优先于 class
if (isset($type['__class'])) {
$class = $type['__class'];
unset($type['__class'], $type['class']);
return static::$container->get($class, $params, $type);
}

if (isset($type['class'])) {
$class = $type['class'];
unset($type['class']);
return static::$container->get($class, $params, $type);
}

throw new InvalidConfigException('Object configuration must be an array containing a "class" or "__class" element.');
}

例子:

// create an object using a class name
$object = Yii::createObject('yii\db\Connection');

// create an object using a configuration array
$object = Yii::createObject([
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);

// create an object with two constructor parameters
$object = \Yii::createObject('MyClass', [$param1, $param2]);

Log

后文分解。

I18N

后文分解。

Yii

// https://php.net/manual/en/function.spl-autoload-register.php
// callback(string $class_name): void
spl_autoload_register(['Yii', 'autoload'], true, true);
//
// classes.php:
// return [
// 'yii\base\Action' => YII2_PATH . '/base/Action.php',
// 'yii\base\ActionEvent' => YII2_PATH . '/base/ActionEvent.php',
// ...
//
Yii::$classMap = require __DIR__ . '/classes.php';

见上文的 autoload()

Yii::$container = new yii\di\Container();

下节见。

– EOF –