1. 程式人生 > >一次框架效能的比較,引起了我對搭建web框架的興趣

一次框架效能的比較,引起了我對搭建web框架的興趣

背景


一次無意的訪問,點選到了一個專門做PHP效能測試的網站,看這裡PHP Benchmarks。

在裡面發現了框架效能測試的結果,發現Laravel的框架效能盡然是最低的。瞬間受到了一萬點的暴擊,誰讓最近一直用Laravel開發專案的呢。

說到底還是Laravel好用呀,方便不說,各方面支援的也不錯,業務方面做的也是內部系統,哪怕效能慢點,也可以用前後端分離、負載均衡等手段解決掉,大體上也是夠用。

不過,作為一個開發人員,理想還是要有的,這時就在想能不能採取Laravel框架的優點,用到什麼就裝什麼,去掉一些請求到響應之間用不到的元件,精簡框架。

之前也熟讀過Laravel的原始碼,知道它的底層用的是Symfony的元件,畢竟沒必要重複的造輪子。那麼我們的框架之旅也將基於Symfony元件。。。

目錄


一、Composer執行機制

二、框架前期準備

三、HttpFoundation元件封裝Request、Response

四、路由處理

五、控制器處理相應功能(C)

六、分離模板(V)

七、分離模型(M)

八、剝離核心程式碼

九、優化框架

十、依賴注入(Dependency Injection)

正文


一、Composer執行機制

Composer的使用最關鍵的得益於PHP標準規範的出現,特別是其中的psr4,自動載入規範,規範瞭如何指定檔案路徑從而自動載入類定義,以及自動載入檔案的位置。

既然講到php檔案的載入,我們就要聊一聊PHP的載入機制了。

在早前時,載入檔案用的都是include、require,但這種載入有很大的侷限性,相信同學們都知道,無論用到用不到都要載入大量的檔案,相當繁瑣。

於是就出現了autoload載入機制,它可以實現懶載入。

function __autoload($class)
{
    require_once ($class.".php");
}

當程式引用了未載入的類,就會自動呼叫__autoload方法,只要維護了__autoload方法,就可以懶載入檔案。

但這裡有一個很大的問題,就是程式中只能定義一次__autoload,這就需要花大盡力在__autoload中維護檔案和空間的對應關係,特別是在大型專案,多人合作中更是繁瑣。

而解決這個問題就是SPL Autoload

SPL Autoload:__autoload呼叫堆疊。

怎麼理解這個堆疊呢,舉個例子。

現有的框架比如ThinkPHP、Laravel等都有一個vendor目錄,用於存放第三方庫,現在vendor下有兩個庫。

monolog 處理系統日誌
guzzlehttp 處理HTTP

當程式引用這兩個庫的名稱空間,並呼叫monolog、guzzlehttp下面的類時,發現呼叫的類檔案都能被找到。

這主要原理是monolog、guzzlehttp都自定義了類似autoload的方法,然後用spl_autoload_register將方法註冊到了SPL堆疊中。

這樣的話,當程式呼叫類的時候,就會統一到SPL堆疊中尋找註冊到堆疊中的autoload方法,並載入相應的檔案。

以上就是php載入檔案的方式,下面就用實戰談一談composer的執行機制。

建立composer專案

# mkdir phoenix
# cd phoenix
composer init

phoenix是接下來搭建的框架名。

建立成功後,發現當前資料夾下會生成一個composer.json檔案,裡面是剛寫入的內容。

composer dump

tree後,就會發現多了一個vendor的目錄,裡面的autoload.php以及composer資料夾下檔案就是整個框架的載入核心。

接下來看一遍這些檔案。

在整個框架中,第一行必然要引用 vendor/autoload.php 檔案,畢竟這是載入核心,那麼就從autoload.php看起。

# autoload.php
require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit599fa618dd1395bdde5fc3a08ff3e4e6::getLoader(); 

只調用了autoload_real.php裡面的getLoader()方法。

#autoload_real.php 精簡後的程式碼

public static function loadClassLoader($class)
{
    if ('Composer\Autoload\ClassLoader' === $class) {
        require __DIR__ . '/ClassLoader.php';
    }
}

public static function getLoader()
{
    #建立ClassLoader類
    spl_autoload_register(array('ComposerAutoloaderInit599fa618dd1395bdde5fc3a08ff3e4e6', 'loadClassLoader'), true, true);

    #初始化ClassLoader物件(主要就是將名稱空間和檔案的對映寫入ClassLoader的屬性中)
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();

    spl_autoload_unregister(array('ComposerAutoloaderInit599fa618dd1395bdde5fc3a08ff3e4e6', 'loadClassLoader')); 
     
    #loadClass方法(類似autoload方法)註冊到 SPL Autoload
    $loader->register(true);   
}

autoload_real.php 的作用就是引入ClassLoader類、初始化ClassLoader類,並註冊到SPL堆疊中。

ClassLoader類中有很多屬性,這些屬性的作用也很簡單:主要就是方便後面程式快速的通過名稱空間找到它所對映的類檔案。

具體用到這些屬性的方法就在ClassLoader類中。

# ClassLoader.php
# 一個快速找到檔案的演算法,很有意思,感興趣的可以研究下
# 主要通過首字元找到名稱空間以及長度,再根據名稱空間以及長度找到檔案

private function findFileWithExtension($class, $ext)
{
    ......
}

那麼ClassLoader類屬性裡面的值是什麼時候寫入的呢?

答案很簡單:當為專案安裝元件時,即composer require xxx時,會更新ClassLoader類的屬性值,也就是將名稱空間和檔案地址做一個關聯。

接下來看看它的register方法。

# ClassLoader.php 
public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

看,其實很簡單,就是將loadClass註冊到SPL堆疊中。

那麼現在就很清楚了,當程式使用了一個還未載入的類時,會呼叫什麼方法?

當然是loadClass方法,再來看看loadClass方法。

# ClassLoader.php 
public function loadClass($class)
{
    if ($file = $this->findFile($class)) {
        includeFile($file);

        return true;
    }
}

根據方法的名稱就能看出它的功能:1、找到檔案 2、載入檔案。

總結一下Composer的執行機制:

1、在composer require安裝時,更新ClassLoader類的屬性 。

2、執行物件時(new \Test()),如果未載入就會執行loadClass(),通過首字元找到名稱空間以及長度,再根據名稱空間以及長度找到檔案,最後include檔案。

以上就是Composer的執行機制,接下來,就進入真正的框架搭建了。

二、框架前期準備

在正式進入搭建框架之前,先看下整體的架構圖以及一些前期準備。

整個架構跟Laravel、ThinkPHP等框架是差不多的,一次請求,一次返回,一個入口,中間根據路由規則交給相應的控制器去執行,在控制器中處理資料以及檢視。

接下來做一些前期準備,進入phoenix專案。

# vi index.php 一個入口
ini_set('display_errors', 1); # 顯示錯誤
error_reporting(-1);

require_once __DIR__.'/vendor/autoload.php'; # 引入核心載入類

$name = $_GET['name'];
dump($name);
# dump()
composer require symfony/var-dumper  # 類似var_dump,輸出的變數體驗更好些。

配置Nginx,訪問域名為:http://dev.phoenix.goods/?name=SexyPhoenix, 可以正常顯示SexyPhoenix。

三、HttpFoundation元件封裝Request、Response

現有的程式只是一個面向過程的程式碼,一個簡單的請求,響應。

對於搭建web框架,這種痛苦寫法當然是要被捨棄的,OOP程式設計才是正路。

既然要面向物件程式設計,首先要做的就是對流程中的Request、Response進行封裝。而Symfony中專門的元件。

composer require symfony/http-foundation

HttpFoundation元件使用說明

改造程式碼

# index.php

ini_set('display_errors', 1);
error_reporting(-1);

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals(); # 建立request物件

$name = $request->get('name', 'World'); # 獲取引數,可移入控制器或從模型得到資料

$response = new Response();

$response->setContent('<b>Hello '.$name.'</b>'); # 設定內容,可用view處理

$response->send(); # 返回

下面來做一個簡單的分析。

$request = Request::createFromGlobals();

這一行程式碼,是相當重要的,它從物件層面上處理了php的全域性變數,例如 GET,POST,SESSION......。

這樣處理就可以輕易的從request物件中獲取所需要的資訊以及對請求頭等資訊的修改。

後期路由這些附加的資訊也是存在request的attributes屬性中,及其好用。

$response = new Response();

通過response物件,可以輕易的控制返回的資訊。比如頭資訊的各種快取策略......

四、路由處理

從架構圖上看,接著就要處理路由了。

phoneix框架用了普遍的做法,統一index.php入口。

那麼下面要做的就是如何將路由的附加引數和要處理的控制器進行對映。

對於路由一般框架都是通過配置來的,這裡也一樣做成可配置,方便。

Yaml格式配置路由

在phoenix專案下,建立routes資料夾,在routes下繼續建立web.yaml檔案。

dashboard:
    path: /dashboard
    defaults: { 
        _controller: 'App\Http\Controllers\DashboardController::index'
    }

下載symfony的Config元件、Yaml元件、Routing元件。

composer require symfony/config
composer require symfony/yaml
composer require symfony/routing

Config元件使用說明

更新程式碼

# index.php
ini_set('display_errors', 1);
error_reporting(-1);

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Loader\YamlFileLoader; # add 
use Symfony\Component\Config\FileLocator; # add

$request = Request::createFromGlobals();

$fileLoader = new YamlFileLoader(new FileLocator(array(__DIR__))); # add
$collection = $fileLoader->load('routes/web.yaml'); # add

$name = $request->get('name', 'World');

$response = new Response();

$response->setContent('<b>Hello '.$name.'</b>');

$response->send();

dump($collection),可以看到返回了路由的Collection物件,裡面有定義的路由。

這個時候,框架只是得到了定義的路由,但還沒有和URL做對映,下面改造繼續。

URL和配置路由對映

ini_set('display_errors', 1);
error_reporting(-1);

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\RequestContext; # add
use Symfony\Component\Routing\Matcher\UrlMatcher; # add

$request = Request::createFromGlobals();

$fileLoader = new YamlFileLoader(new FileLocator(array(__DIR__)));
$collection = $fileLoader->load('routes/web.yaml');

#解析url
$context = new RequestContext(); # add 
$context->fromRequest($request); # add

#初始化UrlMatcher
$matcher    = new UrlMatcher($collection, $context); # add

#url和路由配置對映
$route = $matcher->match($request->getPathInfo()) # add

$name = $request->get('name', 'World');

$response = new Response();

$response->setContent('<b>Hello '.$name.'</b>');

$response->send();

繼續分析。

$context = new RequestContext();
$context->fromRequest($request);

context物件主要就是對url進行解析。現在的域名:http://dev.phoenix.goods/dashboard

既然解析出url的引數,就要用解析出的引數和配置中的路由做精準關聯了,初始化matcher,傳入路由配置和url物件。

得到url和配置中的路由的對映。

$route = $matcher->match($request->getPathInfo());

五、控制器處理相應功能(C)

在路由處理中,框架已經得到了路由和控制器的關聯關係。下面就要執行相應的控制器(上面的_controller值)。

首先,在phoenix專案下,建立app/Http/Controllers/DashboardController.php(仿造Laravel的目錄結構)。

# DashboardController.php
namespace App\Http\Controllers; # 注意這裡App名稱空間,自己定義,並沒有註冊到autoload

class DashboardController{

    public function index()
    {
        echo 'Hello SexyPhoenix';
    }
}

App名稱空間是框架定義的,需要註冊後,才能用,開啟專案的composer.json檔案。

# composer.json

"autoload": {
    "psr-4": {
        "App\\": "app/"
    }
}
composer dump-autoload # 更新名稱空間

到這裡,控制器的準備工作就做完了,接下來的問題就是如果利用得到的路由和控制器的對映關係去執行控制器,也就是下面的程式碼。

App\Http\Controllers\DashboardController::index

其實也很簡單,就是用"::"分隔,得到兩個值,一個是類名,一個是方法名,再用php的call_user_func去執行。

但自己去寫可能過去粗暴,可用性低,在執行前,要先判斷DashboardController類是否存在,index方法是否存在,index方法的許可權,是否是公共方法,以及各種引數等等,

自己去寫的話,會很麻煩,為了方便,繼續用symfony的元件。

composer require symfony/http-kernel

http-kernel元件,是框架的核心,很重要的元件,它提供了各種鉤子,及其方便框架擴充套件,也提供了控制器及其引數的“解析器”(這裡需要了解下php的反射機制)。

更新index.php程式碼。

# index.php
......
use Symfony\Component\HttpKernel\Controller\ControllerResolver; # add
use Symfony\Component\HttpKernel\Controller\ArgumentResolver; # add

......

$route = $matcher->match($request->getPathInfo());
$request->attributes->add($route); # add 將路由對映關係寫入request物件的附加屬性中。

$controller = (new ControllerResolver())->getController($request); # add 處理控制器
$arguments = (new ArgumentResolver())->getArguments($request, $controller); # add 處理方法的引數

$response = call_user_func_array($controller, $arguments);

$response->send();

更新DashboardController.php程式碼。

namespace App\Http\Controllers;

use Symfony\Component\HttpFoundation\Request; # add
use Symfony\Component\HttpFoundation\Response;# add

class DashboardController{

    public function index(Request $request)
    {   
        $name = $request->get('name', 'world'); # add

        return new Response('Hello '.$name); # add
    }
}

用http-kernel好處就是可以處理各種問題,比如Request作為引數注入。

訪問 http://dev.phoenix.goods/dashboard?name=SexyPhoenix, 得到 Hello SexyPhoenix。

http-kernel元件的使用說明

六、分離模板(V)

現在的框架只是簡單的輸出字串,在正式環境中當然不可能這麼簡單,要能夠返回正常的HTML頁面。

而複雜的HTML也不能放在控制器中處理,需要分離出來,單獨處理。Symfony為框架同樣提供了相關的元件。

composer require symfony/templating

Templating元件使用說明

處理框架的目錄結構。

在phoenix專案下,建立resources/views資料夾,繼續在views下建立dashboard.php檔案。

# dashboard.php
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Phoenix</title>
        <style>
            html, body {
                color: #000;
                font-family: 'Raleway', sans-serif;
                font-weight: 100;
                height: 100vh;
                margin: 0;
            }
        </style>
    </head>
    <body>
        <div>
            <h2>Hello, <b><?php echo $name?></b></h2>
            <h3>your mailbox:<?php echo $email?></h3>
            <h3>your github:<?php echo $github?></h3>
        </div>
    </body>
</html>

在app/Http/Controllers下建立Controller.php檔案。

# Controller.php

namespace App\Http\Controllers;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\Loader\FilesystemLoader;

class Controller {

    /**
     * $templete 模板檔案
     * $data 資料
     */
    public function render($templete, array $data)
    {
        return new Response(
            (new PhpEngine(
                new TemplateNameParser(), 
                new FilesystemLoader(getcwd().'/resources/views/%name%')
            ))
            ->render($templete, $data)
        );
    }
}

改造DashboardController.php 程式碼。

namespace App\Http\Controllers;

use Symfony\Component\HttpFoundation\Request;

class DashboardController extends Controller{ # 繼承Controller

    public function index(Request $request)
    {   
        $name = $request->get('name', 'world');

        $data = [
            'name'   => $name,
            'email'  => '[email protected]',
            'github' => 'https://github.com/SexyPhoenix'
        ];

        return $this->render('dashboard.php', $data);
    }
}

訪問 http://dev.phoenix.goods/dashboard?name=SexyPhoenix, 頁面正常顯示。

七、分離模型(M)

分離完模板後,架構的資料還是在控制器中處理,同樣要做分離。不過這一步,同學們可以根據自己的意願來,比如你可以新增倉庫層、服務層等。

這裡就做簡單點,在app目錄下,建立Models資料夾,繼續建立User.php檔案。

# User.php
namespace App\Models;

class User {
    
    protected $emails = [];

    protected $githubs = [];

    public function getEmailByName(string $name)
    {
        $this->setEmails();

        return array_key_exists($name, $this->emails) ? $this->emails[$name] : ''; 
    }

    public function getGithubByName($name)
    {
        $this->setGithubs();

        return array_key_exists($name, $this->githubs) ? $this->githubs[$name] : ''; 
    }

    public function setEmails()
    {
        $this->emails = [
            'SexyPhoenix' => '[email protected]'
        ];
    }

    public function setGithubs()
    {
        $this->githubs = [
            'SexyPhoenix' => 'https://github.com/SexyPhoenix'
        ];
    }
}

更新DashboardController.php。

# DashboardController.php
......
use App\Models\User #add 
......

public function index(Request $request)
{
    $name = $request->get('name', 'world');

    $user = new User(); # add
    $data = [
        'name'   => $name,
        'email'  => $user->getEmailByName($name),  # update
        'github' => $user->getGithubByName($name),# update
    ];

    return $this->render('dashboard.php', $data);
}

訪問頁面,正常顯示。

八、剝離核心程式碼

框架的基本架構已經搭建完成,但此時的核心程式碼都寫在了index.php裡面,另寫專案的話,無法複用此架構,接下來剝離出核心程式碼。

在phoenix專案下建立Core資料夾,繼續建立Phoenix.php檔案,移入核心程式碼並優化。

# Phoenix.php

namespace Core; #注意此名稱空間需要註冊

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;

class Phoenix {

    public $request;

    public $routeMap;

    public function handle(Request $request)
    {
        $this->request = $request;

        try {

            //url map
            $this->getRouteMap();

            $this->setRequestRoute();

            $controller = (new ControllerResolver())->getController($request);
            $arguments = (new ArgumentResolver())->getArguments($request, $controller);

            return call_user_func_array($controller, $arguments);   

        } catch(\Exception $e) {

            return new Response('File Not Found', 404);
        }
    }

    public function setRequestRoute()
    {
        $this->request->attributes->add($this->routeMap->match($this->request->getPathInfo()));
    }

    public function getRouteMap()
    {    
        $this->routeMap = new UrlMatcher(
            $this->getCollection(), 
            (new RequestContext())->fromRequest($this->request)
        );
    }

    public function getCollection()
    {
        return (
            new YamlFileLoader(
               new FileLocator(array(getcwd()))
            )
        )->load('routes/web.yaml');
    }
}

更新index.php程式碼。

ini_set('display_errors', 1);
error_reporting(-1);

require_once __DIR__.'/vendor/autoload.php';

$kernel = new Core\Phoenix();

$response = $kernel->handle(
    Symfony\Component\HttpFoundation\Request::createFromGlobals()
);

$response->send();

註冊Core名稱空間,開啟composer.json檔案。

# composer.json
"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Core\\": "core/"
    }
}
composer dump-autoload # 更新名稱空間

重新整理頁面,顯示正常。

九、優化框架

在前面用到HttpKernel元件時,為什麼介紹它是框架的核心呢?

因為HttpKernel裡面有個很重要的概念,派遣事件,給註冊過的不同監聽器監聽。

是用Mediator模式設計的,這種模式帶來的好處,就是使框架的擴充套件性得到極大的提高。

在請求到響應之前設計了八種鉤子,方便後期擴充套件,詳情看下面的連結。

KernelEvents鉤子介紹

同時,也可以用另一種監聽事件的方式,通過一個event subscriber(事件訂閱器),向派遣器精確通報它要訂閱哪些事件。下面對路由優化時,會用到這。

EventDispatcher元件使用說明

HttpKernel元件的功能僅止於此嗎? 當然不,它裡面有一個很重要的類“HttpKernel類”,將框架的核心Core/Phoenix.php的程式都實現了。

只要phoenix框架核心類Phoenix繼承HttpKernel,並呼叫它的構造方法就行了。

下面來改造Core/Phoenix.php程式碼。

# Phoenix.php
namespace Core;

use Symfony\Component\HttpFoundation\RequestStack; # add
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\EventDispatcher\EventDispatcher; # add
use Symfony\Component\HttpKernel\HttpKernel; # add 
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;

class Phoenix extends HttpKernel{ # 繼承HttpKernel

    public function __construct()
    {
        $matcher      = new UrlMatcher($this->getCollection(), new RequestContext());
        $requestStack = new RequestStack();

        $dispatcher   = new EventDispatcher();
        $dispatcher->addSubscriber(new RouterListener($matcher,  $requestStack)); # 訂閱路由
        
        # HttpKernel的建構函式,可以點下面的連結進去看看
        parent::__construct(

            $dispatcher,
            new ControllerResolver(),
            $requestStack,
            new ArgumentResolver()
        );
    }

    public function getCollection()
    {
        return (
            new YamlFileLoader(
               new FileLocator(array(getcwd()))
            )
        )->load('routes/web.yaml');
    }
}

HttpKernel類

index.php的程式碼不用變,HttpKernel類裡面也有handle方法。建議同學們看看HttpKernel類的原始碼。

十、依賴注入(Dependency Injection)

Phoenix類繼承了HttpKernel,是整個架構的核心,在框架裡面定義了“路由監聽”,但如果框架不僅僅要對路由進行監聽,還要對response階段進行監聽呢?是不是繼續修改Phoenix類呢?

這樣的設計對於框架來說,是絕對不友好的。那有沒有方法解決呢?

當然有,可以通過在外面注入物件,框架通過type檢測,自動引入相關物件。

首先下載Symfony的DependencyInjection元件。

composer require symfony/dependency-injection

在core資料夾下建立container.php檔案

# container.php
namespace Core;

use Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;

$app = new ContainerBuilder();
$app->register('context', 'Symfony\Component\Routing\RequestContext');
$app->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher')
    ->setArguments(array(getCollection(), new Reference('context')));

$app->register('request_stack', 'Symfony\Component\HttpFoundation\RequestStack');
$app->register('controller_resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');
$app->register('argument_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentResolver');
    
$app->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener') # 路由監聽
    ->setArguments(array(new Reference('matcher'), new Reference('request_stack')));

$app->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
    ->addMethodCall('addSubscriber', array(new Reference('listener.router')));            

$app->register('phoenix', 'Core\Phoenix')
->setArguments(array(
    new Reference('dispatcher'),
    new Reference('controller_resolver'),
    new Reference('request_stack'),
    new Reference('argument_resolver'),
));


return $app;

function getCollection()
{
    return (
        new YamlFileLoader(
           new FileLocator(array(getcwd()))
        )
    )->load('routes/web.yaml');
}

別名和物件一一對應,後面可以通過別名獲取物件。

去掉core/phoenix.php裡面的程式碼。

namespace Core;

use Symfony\Component\HttpKernel\HttpKernel;

class Phoenix extends HttpKernel{

    // public function __construct()
    // {
    //     $matcher      = new UrlMatcher($this->getCollection(), new RequestContext());
    //     $requestStack = new RequestStack();

    //     $dispatcher   = new EventDispatcher();
    //     $dispatcher->addSubscriber(new RouterListener($matcher,  $requestStack));

    //     parent::__construct(

    //         $dispatcher,
    //         new ControllerResolver(),
    //         $requestStack,
    //         new ArgumentResolver()
    //     );
    // }

    // public function getCollection()
    // {
    //     return (
    //         new YamlFileLoader(
    //            new FileLocator(array(getcwd()))
    //         )
    //     )->load('routes/web.yaml');
    // }
}

更新index.php程式碼。

ini_set('display_errors', 1);
error_reporting(-1);

require_once __DIR__.'/vendor/autoload.php';

$app = require_once __DIR__.'/core/container.php'; # add 

$response = $app->get('phoenix') # 通過別名獲取
    ->handle(
        Symfony\Component\HttpFoundation\Request::createFromGlobals()
    );

$response->send();

訪問 http://dev.phoenix.goods/dashboard?name=SexyPhoenix, 顯示正常。

到這裡,框架的整個基本設計就結束了,之後需要什麼功能,就可以自己用composer安裝元件了,composer還是很好用的。

同學們如果有什麼疑問的,歡迎在評論區一起交流,ヾ(●´∀`●) 。

最後,附一份最終程式碼 phoenix web 架構。

參考Symfony官網 - 建立你自己的框架