主頁 > 軟體設計 > 一次框架性能的比較,引起了我對搭建web框架的興趣

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

2020-09-14 01:42:02 軟體設計

背景


一次無意的訪問,點擊到了一個專門做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是接下來搭建的框架名,

1

創建成功后,發現當前檔案夾下會生成一個composer.json檔案,里面是剛寫入的內容,

composer dump

2

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的運行機制,接下來,就進入真正的框架搭建了,

二、框架前期準備

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

3

整個架構跟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......,

4

這樣處理就可以輕易的從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物件,里面有定義的路由,

5

這個時候,框架只是得到了定義的路由,但還沒有和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

6

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

7

得到url和配置中的路由的映射,

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

8

五、控制器處理相應功能(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 = https://www.cnblogs.com/SexyPhoenix/p/['name'   => $name,
            'email'  => '[email protected]',
            'github' => 'https://github.com/SexyPhoenix'
        ];

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

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

9

七、分離模型(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 = https://www.cnblogs.com/SexyPhoenix/p/['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官網 - 創建你自己的框架

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/29192.html

標籤:架構設計

上一篇:《大型網站技術架構:核心原理與案例分析》讀書筆記

下一篇:小白的springboot之路(十)、全域例外處理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more