源码分析
我们在入口文件里面,已经看到引入了Yii.php,那么在这里我们把源码贴出来:
<?php
require __DIR__ . '/BaseYii.php';
class Yii extends \yii\BaseYii
{
}
//Yii全局辅助类,没啥可说的。
//注册自动加载,为Yii里面的autoload方法。(当然,autoload方法存在于Yii的基类:\yii\BaseYii)
spl_autoload_register(['Yii', 'autoload'], true, true);
// 自动加载的所有类的全局树。
Yii::$classMap = require __DIR__ . '/classes.php';
// 实例化一个容器,作为当前应用的容器。
Yii::$container = new yii\di\Container();
首先,我们引入了辅助类的基类BaseYii.php (因为这个时候,自动加载还没注册,所以只能手动引入),然后注册自动加载。我们接下来讲一下BaseYii.php这个类。这个类主要实现的事情有:
1. 定义一些全局常量。
2. 类的自动加载
3. 路径别名的设置和获取。
4. 创建对象并注入到当前容器。
5. 不同级别日志的记录功能。
6. 语言翻译。
7. 对象属性的初始设置。
当引入yii.php的时候,根据程序执行来看,做的事情有:定义常量和自动加载。我们这里顺便把BaseYii.php的所有支持的功能都列了出来。这里主要讲自动加载、对象的属性设置、以及创建类三个方法。
自动加载:
public static function autoload($className)
{
if (isset(static::$classMap[$className])) {
$classFile = static::$classMap[$className];
if ($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)) {
throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");
}
}
public static function getAlias($alias, $throwException = true)
{
// 如果第一个字符不是@符号,代表不是Yii框架内部的命名空间,直接返回。
if (strncmp($alias, '@', 1)) {
// not an alias
return $alias;
}
// 获取根命名空间
$pos = strpos($alias, '/');
$root = $pos === false ? $alias : substr($alias, 0, $pos);
// 根命名空间必须提前注册到全局注册树上。如果根命名空间不存在就直接返回false
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;
}
首先,autoload方法已经在Yii.php中通过spl_autoload_register()函数注册。autoload()方法存在一个参数$className,就是完整的类名称(包含命名空间),$className是php自动传入的,我们不需要做处理。
$classMap是BaseYii.php的静态属性,它是自动加载完毕的类的全局树。数组键是类名(不带前导反斜杠),数组值是对应的类文件路径。getAlias()用来获取真实类文件路径。
最后引入文件。
创建对象:
public static function createObject($type, array $params = [])
{
// 创建对象,并将其注入当前应用的容器中。
if (is_string($type)) {
return static::$container->get($type, $params);
}
if (is_callable($type, true)) {
return static::$container->invoke($type, $params);
}
if (!is_array($type)) {
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
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.');
}
createObject其实就是new的增强版,他可以根据配置创建一个对象。
第一个参数$type支持字符串、数组、回调函数三种类型。如果type是一个数组,该数组必须包括"__class"或者"class"键。该键对应的值是需要创建的对象对应的类名称。数组里面其他的参数是这个类的属性的初始值的设置。$params则是该类的构造方法所需要的参数。如果type是字符串,则是一个类名称,如果是回调函数,则直接出发回调。$params就是回调函数所需参数。
具体创建对象的实现逻辑,我会在后面的容器章节进行讲解。
设置对象初始属性:
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
这个方法第一个参数是需要设置属性的对象, 第二个参数是需要设置的对象的属性(键值对形式的数组)。代码及其简单,为什么单独拿出来讲呢?因为这里涉及到php魔术方法__set()的一个触发场景。
__set()当设置一个没有访问权限的属性值,或者是设置不存在的属性值的时候会触发。但是yii2源码中,为了满足编辑器的友好展示,使用注释的方式表示类的属性。(这种方式其实并没什么卵用,相当于没写)例如
/**
* @property array $prop;
* Class A
*/
class A
{
}
这样可以触发__set()和__get()方法。因为属性$prop不存在呀。