laravel 應用層加載過程源碼分析

attachments-2020-08-MTtJj40K5f4cbdb4e1df7.png

laravel 應用層執行過程源碼分析

Laravel 是優雅的 PHP Web 開發框架。具備高效、簡潔、富於表達力等優勢。採用 MVC 設計,是崇尚開發效率的全棧框架。是最受關注的 PHP 框架。php

因此 laravel 框架自己的設計思想我認爲值得咱們每一個 phper 去學習,這裏我經過一個手寫的簡易版框架去認識和了解 laravel 在應用層執行的過程。主要包含的內容有:容器、應用框架、內核、契約、入口文件、服務提供者、路由、請求、門面、控制器及輔助函數。文章結尾附上執行流程的圖片供你們參考。mysql

容器:執行依賴注入和管理依賴的工

主要作了2件事情:laravel

①【綁定】對象、實例(共享實例)、接口(接口到實現)、閉包4個抽象和具體類到容器數組中
②【解析】已綁定的抽象(經過反射機制實現解析具體類中的依賴注入),即建立實例git

整一個框架裏面就圍繞這個容器提供依賴的,因此至關於一個心臟,也是 laravel 的一個心臟。這裏貼出源碼,後面就不貼了。github

<?php
/**
 * [Description]
 *
 * @Author  leeprince:2020-03-11 19:28
 */
class Container
{
    // 綁定到容器的數組,至關於 bindings 與 $aliases
    protected $bind = [];

    // 當前實例,單例建立
    protected static $instance;

    // 綁定到容器的共享實例數組
    protected $instances = [];
    
    // 參數覆蓋堆棧的數組
    protected $with = [];

    /**
     * [綁定到容器數組]
     *
     * @Author  leeprince:2020-03-11 19:36
     * @param $abstract
     * @param $concrete
     */
    public function bind($abstract, $concrete = null)
    {
        // 綁定自身
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        $this->bind[$abstract] = $concrete;
    }

    /**
     * [校驗是否已綁定到容器]
     *
     * @Author  leeprince:2020-03-12 01:15
     */
    public function has($abstract)
    {
        return isset($this->bind[$abstract]);
    }

    /**
     * [綁定到共享實例中]
     *
     * @Author  leeprince:2020-03-12 01:17
     * @param $abstract
     * @param $instance
     */
    public function instance($abstract, $instance)
    {
        if (isset($this->bind[$abstract])) {
            unset($this->bind[$abstract]);
        }

        $this->instances[$abstract] = $instance;
    }

    /**
     * [獲取當前實例]
     *
     * @Author  leeprince:2020-03-12 01:18
     * @return Container
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }
        return static::$instance;
    }

    /**
     * [設置當前實例]
     *
     * @Author  leeprince:2020-03-12 01:19
     * @param $container
     * @return mixed
     */
    public static function setInstance($container)
    {
        return static::$instance = $container;
    }

    /**
     * [建立實例]
     *
     *      laravel 中是resolve 方法
     *
     * @Author  leeprince:2020-03-12 01:20
     * @param $abstract
     * @return mixed
     * @throws Exception
     */
    public function make($abstract, array $parameter = [])
    {
        // 判斷是否在共享實例中,有直接返回
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }
        
        $this->with[] = $parameter;
    
        // 再優化版本:經過反射機制繼續遞歸解析具體類中的依賴注入:參考 IOC 的部分便可!
        if (isset($this->bind[$abstract])) {
            $concrete = $this->bind[$abstract];
            
            // 若是具體實現是閉包那麼直接執行閉包,也沒必要綁定到共享實例中,由於閉包函數自己也不是實例。
            // 下列有3中判斷是不是否一個閉包的方式。顯然第一種更加專業
            if ($concrete instanceof Closure) {
            // if (is_object($concrete)) {
            // if (is_callable($concrete)) {
                return $concrete();
            }
    
            /**
             * 有兩個版本
             */
            
            /** 版本1: 這是不考慮基於類的依賴注入的簡單版本 */
            // return $this->instances[$abstract] = (empty($parameter))? new $concrete() : new $concrete(...$parameter);
    
            /** 版本2:經過反射機制考慮基於類的依賴注入的優化版本 */
            // 經過反射類,反射出該具體的全部信息
            $reflection = new ReflectionClass($concrete);
            if (! $reflection->isInstantiable()) {
                throw new Exception('反射後判斷該類沒法實例化');
            }
    
            // 獲取構造函數
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                $object = $reflection->newInstance();
    
            } else {
                // 獲取構造函數參數
                $depenen = $constructor->getParameters();
                $instances = $this->getDependencies($depenen);
    
                $object = $reflection->newInstanceArgs($instances);
            }
            
            /**
             * 刪除參數覆蓋堆棧的數組的最後一條記錄。
             *      由於這多是遞歸解析基於類的依賴注入,
             *      若是不刪除會致使在遞歸的上一步沒有獲取到當前解析中的參數覆蓋的參數數組
             */
            array_pop($this->with);
            
            return $this->instances[$abstract] = $object;
            
        }
        
        throw new Exception('沒有找到實例'.$abstract);
    }
    
    /**
     * [根據構造函數中的類型提示參數(依賴注入:基於類的依賴注入或者非基於類的原始依賴注入)中繼續實例化對象]
     *
     * @Author  leeprince:2020-03-15 13:12
     * @param array $depenen
     * @return array
     * @throws Exception
     */
    public function getDependencies(array $dependencies)
    {
        $results = [];
        foreach ($dependencies as $key => $dependency) {
            // 肯定給定的依賴項是否具備參數替代
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);
                continue;
            }
    
            // 考慮構造函數的參數是否是基於類的依賴注入
            $results[] = is_null($dependency->getClass())
                ? $this->resolvePrimitive($dependency, $key)
                : $this->resolveClass($dependency);
        }
        
        return $results;
    }
    
    /**
     * [解析非基於類的原始依賴]
     *
     * @Author  leeprince:2020-03-15 15:47
     * @param ReflectionParameter $parameter
     * @param $key
     * @return mixed
     * @throws ReflectionException
     */
    public function resolvePrimitive(ReflectionParameter $parameter, $key)
    {
        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }
        
        return null;
    }
    
    /**
     * [解析容器中基於類的依賴]
     *
     * @Author  leeprince:2020-03-15 15:29
     * @param ReflectionParameter $parameter
     * @return mixed
     * @throws Exception
     */
    public function resolveClass(ReflectionParameter $parameter)
    {
        return $this->make($parameter->getClass()->name, []);
    }
    
    
    /**
     * [肯定給定的依賴項是否具備參數替代。]
     *
     * @Author  leeprince:2020-03-15 16:17
     * @param $dependency
     * @return bool
     */
    protected function hasParameterOverride(ReflectionParameter $dependency)
    {
        return array_key_exists(
            $dependency->name, $this->getLastParameterOverride()
        );
    }
    
    /**
     * [獲取依賴項的參數覆蓋。]
     *
     * @Author  leeprince:2020-03-15 16:18
     * @param ReflectionParameter $dependency
     * @return mixed
     */
    protected function getParameterOverride(ReflectionParameter $dependency)
    {
        return $this->getLastParameterOverride()[$dependency->name];
    }
    
    /**
     * [獲取最後一個參數覆蓋]
     *
     * @Author  leeprince:2020-03-15 16:23
     * @return array|mixed
     */
    protected function getLastParameterOverride()
    {
        return count($this->with) ? end($this->with) : [];
    }
}

應用程序

主要也是作2件事:在構造函數中註冊基本綁定和註冊基本服務sql

  1. 構造函數中【註冊基本綁定】到容器共享實例數組中:綁定自身到app 抽象;綁定自身到從容器類抽象
  2. 構造函數中【註冊基本服務】到容器綁定數組中:包含但不限於:路由對象、請求對象、日誌契約接口到文件日誌實現類、文件日誌服務對象、mysql日誌服務對象...

內核

  1. 內核類經過構造函數將依賴(應用框架(容器))注入
  2. 並在構造函數中解析路由對象到內核的屬性中
  3. 將請求對象傳入到路由對象(在構造函數中解析路由對象到內核的屬性中)的請求分發方法中

契約:契約是一組接口,每個契約都有框架提供的相應的實現

容器契約
內核契約
服務提供者契約
- 日誌契約數據庫

入口文件

主要作了5件事,執行順序也是按照下面執行,固然能夠調整,只不過爲了更好理解我就這麼排了數組

  1. 實例化應用框架
  2. 經過路由門面類的靜態方法執行路由配置。具體執行流程查看門面相應的內容便可
  3. 綁定內核契約的接口到實現
  4. 解析內核契約對象接口(即獲取實現契約接口的具體實現類的實例化對象)
  5. 調用請求對象設置請求地址到請求對象的屬性中,並將請求對象返回的方法
  6. 將請求對象傳入到內核的處理請求方法中

服務提供者

這裏主要介紹的是服務的概念。設計思路與 laravel 關於服務提供者實現的不同,對這塊感興趣的朋友能夠看下源碼或者留言一塊兒討論。閉包

文件日誌服務類:實現契約接口的約束方法
mysql 日誌服務類:實現契約接口的約束方法架構

路由對象

主要作了3件事

  1. 將路由配置項添加到路由對象的路由表中
  2. 請求分發方法執行查找路由表的方法
  3. 執行匹配到請求的路由,即執行控制器下的方法或者當即執行閉包。示例中介紹執行控制器的方法流程,這樣才更能掌握服務提供者和輔助函數的概念

請求對象

設置請求地址到請求對象的屬性中,並將請求對象返回

門面:經過定義門面的抽象類,「靜態代理」應用的服務容器中可用的底層類

門面的抽象類

  1. 調用具體對象(路由、請求...)的門面類, 執行__callStatic() 魔術方法動態的調用靜態對象
  2. 獲取繼承該抽象門面類的門面對象名稱
  3. 經過獲取門面名稱從容器已綁定的類庫中解析出門面要靜態代理的基類,並返回
  4. 傳入可變參數,並動態執行該具體對象的方法

控制器

  1. 執行控制器的方法的相應業務。
  2. 這裏示例提供有數據庫鏈接的服務,配合應用服務容器輔助函數並調用服務方法便可

輔助函數

應用服務容器輔助函數:根據參數判斷直接返回服務容器或者解析出相應抽象的實例

執行流程

執行的順序以 「阿拉伯數字.」 作爲順序閱讀

attachments-2020-08-ELhFpVaO5f4cbea81e530.png

源碼地址

https://github.com/leeprince/my_pattern/tree/master/my_minilaravel

這框架只是爲讓你們一塊兒瞭解和熟悉 laravel 在應用層的加載過程。固然 laravel 框架自己還有不少核心的架構,好比服務提供者、管道、中間件有時間我再補上

歡迎你們留言一塊兒討論~

attachments-2020-09-quO1XqEw5f4db77ed6424.jpg

相關文章
相關標籤/搜索