资讯专栏INFORMATION COLUMN

Yii2 完整框架分析(详细)

spademan / 1729人阅读

摘要:行为是如何注册到组件的呢通过注册行为之后,实际上是添加到了的属性中那么行为中的属性,就添加到了,中进行直接调用行为里面的方法的时候,实际上触发了里面的魔术方法继承链图解

Yii2 框架Trace 准备

了解composer的autoload psr0 psr4 加载机制

了解spl_autoload_register

了解依赖注入的实现原理反射

了解常用魔术方法__set,__get,__call

热情与专注

入口分析

加载composer 的自动加载器,支持了PSR-0 PSR-4

require(__DIR__ . "/../vendor/autoload.php");

进行常量的定义,并且声明了最基本的方法例如getVersion

require __DIR__ . "/BaseYii.php";

加载Yii自己的autoload加载器,从classmap中寻找,指定的类,如果没有找到,会解析名称到路径。

spl_autoload_register(["Yii", "autoload"], true, true);
Yii::$classMap = require __DIR__ . "/classes.php";

todo 容器生成

Yii::$container = new yiidiContainer();

开始生成一个应用主体,并且加载了config配置,直接运行

(new yiiwebApplication($config))->run();
主体生成

application is the base class for all web application classes.

Yii::$app 是应用主体的实例,一个请求只会生成一个应用主体
Application类中,定义了初始的defaultRoute,以及coreComponent核心组件的列表,还有一些请求和相应相关的方法

继承链

web/application=>base/application=>base/Model=>id/ServiceLocator=>base/component=>base/object=>base/configurable

初始化主体的配置
public function __construct($config = [])
    {
        Yii::$app = $this; // 这样在任何地方都可以通过静态方法的方式,来调用应用主体
        static::setInstance($this); // 将请求过来的的类实力,进行保存

        $this->state = self::STATE_BEGIN;

        $this->preInit($config); // 进行config的初始化,给路径起别名,设置时区等,并且最后加载了核心组件

        $this->registerErrorHandler($config); // 注册错误句柄,用来捕捉和处理错误的方法

        Component::__construct($config); // 
    }

其中preInit会进行一个注册核心组件,这里web的入口进行了扩展,包含了request等

public function coreComponents()
    {
        return array_merge(parent::coreComponents(), [
            "request" => ["class" => "yiiwebRequest"],
            "response" => ["class" => "yiiwebResponse"],
            "session" => ["class" => "yiiwebSession"],
            "user" => ["class" => "yiiwebUser"],
            "errorHandler" => ["class" => "yiiwebErrorHandler"],
        ]);
    }

讲configure格式为对象,存储到应用主体中

public function __construct($config = [])
    {
        if (!empty($config)) {
            Yii::configure($this, $config);
        }
        $this->init();
    }
组件注册

这里我们来看下组件是如何注册到应用主体中的,这个-> 实际上调用的是__Set魔术方法,
那我们再看这个$this是什么,很明显是指yiiwebapplication

public static function configure($object, $properties)
    {
        foreach ($properties as $name => $value) {
            $object->$name = $value;
        }

        return $object;
    }

我们从webapplication向parent一层一层的找,找到了__set 的定义,在basecomponent

public function __set($name, $value)
    {
        $setter = "set" . $name;
        if (method_exists($this, $setter)) {
            // set property
            $this->$setter($value);

            return;
        } elseif (strncmp($name, "on ", 3) === 0) {
            // on event: attach event handler
            $this->on(trim(substr($name, 3)), $value);

            return;
        } elseif (strncmp($name, "as ", 3) === 0) {
            // as behavior: attach behavior
            $name = trim(substr($name, 3));
            $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

            return;
        }

        // behavior property
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $behavior) {
            if ($behavior->canSetProperty($name)) {
                $behavior->$name = $value;
                return;
            }
        }

        if (method_exists($this, "get" . $name)) {
            throw new InvalidCallException("Setting read-only property: " . get_class($this) . "::" . $name);
        }

        throw new UnknownPropertyException("Setting unknown property: " . get_class($this) . "::" . $name);
    }

上面的set方法,会遍历config的属性,来交给不同的set方法处理,果然恰好存在了一个setComponents,用来处理组件

public function setComponents($components)
    {
        foreach ($components as $id => $component) {
            $this->set($id, $component);
        }
    }

最终组件配置就这样被注册到了$this->_definitions里面,后面我们可以通过$this->get($id) 来获取组件配置

public function set($id, $definition)
    { 
        unset($this->_components[$id]);

        if ($definition === null) {
            unset($this->_definitions[$id]);
            return;
        }

        if (is_object($definition) || is_callable($definition, true)) {
            // an object, a class name, or a PHP callable
            $this->_definitions[$id] = $definition;
        } elseif (is_array($definition)) {
            // a configuration array
            if (isset($definition["class"])) {
                $this->_definitions[$id] = $definition;
            } else {
                throw new InvalidConfigException("The configuration for the "$id" component must contain a "class" element.");
            }
        } else {
            throw new InvalidConfigException("Unexpected configuration type for the "$id" component: " . gettype($definition));
        }

    }

注意这个init,并不是当前类下面的init方法,而是被webapplication 覆盖

public function init()
    {
        $this->state = self::STATE_INIT;
        $this->bootstrap();
    }

同样bootstrap方法也被覆盖

protected function bootstrap()
    {
        $request = $this->getRequest();
        Yii::setAlias("@webroot", dirname($request->getScriptFile()));
        Yii::setAlias("@web", $request->getBaseUrl());

        parent::bootstrap();
    }

看下getRequest是怎么回事,跟踪代码,它通过get方法,来从_definitions中获取request对应的配置
然后根据配置中的yiiwebrequest 来进行创建对象,其中$type 就是配置

public static function createObject($type, array $params = [])
    {
        if (is_string($type)) {
            return static::$container->get($type, $params);
        } elseif (is_array($type) && isset($type["class"])) {
            $class = $type["class"];
            unset($type["class"]);
            return static::$container->get($class, $params, $type); // request 走的是这里
        } elseif (is_callable($type, true)) {
            return static::$container->invoke($type, $params);
        } elseif (is_array($type)) {
            throw new InvalidConfigException("Object configuration must be an array containing a "class" element.");
        }

        throw new InvalidConfigException("Unsupported configuration type: " . gettype($type));
    }
    

进行完web层的引导之后,继续进行base层的引导,包括对配置中bootstrap的引导注册,会直接通过容器得到实例。(具体不详)

小结

在上面的new的过程中,完成了组件的注册,配置的初始化,并且学到了get是createObject通过依赖注入的方法,获取组件对象

请求的处理

前面所有的配置和准备都初始化完毕之后,要进行请求处理了。

这一句的run方法,就是处理请求的整个启动入口

$application->run();

使用handleRequest方法来处理请求,并且参数是request实例,其中handleRequest方法要看webapplication层的封装

$response = $this->handleRequest($this->getRequest());
调用resolve进行解析
list($route, $params) = $request->resolve();

我们再看Urlmanager的时候,发现里面定义的routeParam是r也就是默认接受参数的值(重要)
实际上下面这段代码完成的就是解析了$_GET的值,从里面寻找routeParam 定义的值(r)所代表的内容

$result = Yii::$app->getUrlManager()->parseRequest($this);

这里要调用action方法了

$this->requestedRoute = $route;
$result = $this->runAction($route, $params);
创建控制器+调用action

实际上runAction中的主要内容就是createController的实现:

// parts 分为两部分,0 是controller的实例,1 是实例里面的方法名称
$parts = $this->createController($route);
...
...

/* @var $controller Controller */ // 这是一个跟踪优化,不然controller->runaction 就定位不了了
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
$result = $controller->runAction($actionID, $params);

如果遇到了控制器,那么直接返回控制器对象和方法route的名称,这里route类似于action方法名称,代码略微繁琐,但是很清晰,具体实现就是

路由解析规则

例如r=site

寻找controller,找到site控制器,直接实例化

例如r=site/index

构造控制器,并且id为site、route为index

例如r=site/index/test

发现没有找到控制器,那么从模块中获取,这里是重新构造id为site/index route为test,然后调用createControllerById 方法来获取控制器(具体不详,不过应该是通过namespace定位了)

而且createController直接返回了controller的实例

然后我们在createAction中找到解析action方法名称的代码
例如r=site/index-test 那么下面对应的methodName就是actionIndexTest

$methodName = "action" . str_replace(" ", "", ucwords(implode(" ", explode("-", $id))));
内容输出

将调用action方法的值,进行返回,然后直接交给yiiwebResponse作为data属性的一部分。最后调用send方法,进行输出。

public function send()
    {
        if ($this->isSent) {
            return;
        }
        $this->trigger(self::EVENT_BEFORE_SEND);
        $this->prepare();
        $this->trigger(self::EVENT_AFTER_PREPARE);
        $this->sendHeaders();
        $this->sendContent();
        $this->trigger(self::EVENT_AFTER_SEND);
        $this->isSent = true;
    }
行为是如何注册到组件的呢?

通过attacheBehavior注册行为之后,实际上是添加到了$this_behaviors属性中

那么行为中的属性,就添加到了,_behaviors

进行直接调用行为里面的方法的时候,实际上触发了yiiaseComponent里面的__call魔术方法

继承链图解

END

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26127.html

相关文章

  • YII2快速学习笔记

    摘要:高性能始终是的首要目标之一。版是上代的老版本,现在处于维护状态。版是一个完全重写的版本,采用了最新的技术和协议,包括依赖包管理器代码规范命名空间特质等等。所以,我们学习版本。启用本镜像服务系统全局配置即将配置信息添加到的全局配置文件中。 工作中需要用到YII框架,于是乎,系统的学习下这套框架,详细教程请看考该站完整系列:YII2教程 一、YII简介 1、什么是YII Yii 是一个高性...

    kbyyd24 评论0 收藏0
  • Yii2性能优化之:类的延迟加载技术介绍

    摘要:据官方介绍,框架广泛的使用了一种叫做延迟加载的技术,从而达到这样的效果。比如我们在判断中,需要实例化类的时候,再去加载相应的文件。代码如下等于不等于优化过后的文件效率肯定得到了提升,这个也就是类的延迟加载雏形。这就是的延迟加载了。 Yii框架号称最高效的PHP框架,执行效率高出其他框架很多。据官方介绍,Yii框架广泛的使用了一种叫做延迟加载的技术,从而达到这样的效果。 下面我们就通过实...

    tuniutech 评论0 收藏0
  • 列表——表头自定义显示字段

    摘要:今天我就来讲讲插件的使用,它是如何实现列表表头自定义显示字段的,我把我的经验分享出来,满足一下不懂英语的人,给你们搭个快车。需求分析实现列表表头自定义显示字段,自定义表头排序。 序言 Yii2框架的扩展性能真的很不错,很多效果都可以通过插件去实现,你想不到的老外都帮你想好了,于是,人群中就流传了这么一句话:效果不会写不要紧,会用插件也不错。GitHub是一个庞大而且开放的资源库,平时有...

    Yangyang 评论0 收藏0
  • Yii2框架源码分析之如何实现注册和登录

    摘要:在用户注册的时候是为空的,当用户忘记密码在登录页面点击后生成的,用来给用法发送邮件后重置密码时进行验证。如有错误,不吝赐教。 注册 在advanced模板中,进入frontend/index.php?r=site%2Fsignup页面,可以看到框架的注册页面showImg(https://segmentfault.com/img/bVDEaZ?w=300&h=235); 填写完User...

    chemzqm 评论0 收藏0
  • Yii修行之路 - Extension 扩展

    摘要:运行来安装指定的扩展。这更便于用户辨别是否是的扩展。当用户运行安装一个扩展时,文件会被自动更新使之包含新扩展的信息。上述代码表明该扩展依赖于包。例如,上述的条目声明将对应于别名。为达到这个目的,你应当在公开发布前做测试。 简述 扩展是专门设计的在 Yii 应用中随时可拿来使用的, 并可重发布的软件包。 基础 例如, yiisoft/yii2-debug 扩展在你的应用的每个页面底部添加...

    bovenson 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<