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

来源:https://www.cnblogs.com/SexyPhoenix/archive/2019/12/18/12035309.html
-Advertisement-
Play Games

背景 一次無意的訪問,點擊到了一個專門做PHP性能測試的網站,看這裡 "PHP Benchmarks" 。 在裡面發現了框架性能測試的結果,發現Laravel的框架性能盡然是最低的。瞬間受到了一萬點的暴擊,誰讓最近一直用Laravel開發項目的呢。 說到底還是Laravel好用呀,方便不說,各方面支 ...


背景


一次無意的訪問,點擊到了一個專門做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 = [
            '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 = [
        '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官網 - 創建你自己的框架


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [1]調試工具 [2]插件 [3]快捷鍵 [4]小功能 ...
  • 目前國產報表工具大部分都是Java版本,例如潤乾和帆軟,而C#寫的報表工具國內還沒有,介紹一款VS2010(C#)寫的國產報表工具(highreport),採用類Excel設計,零代碼實現複雜報表展示、複雜表單填報、圖表展示、參數面板、系統管理、大屏可視化、導出、列印等。 報表功能:1.報表採用類似 ...
  • JSP是一種運行在伺服器端的腳本語言,是用來開發動態網頁的技術,它是JAVA Web 程式開發的重要技術。本章介紹JSP技術的相關概念以及如何開發JSP程式,主要內容包括JSP技術簡介、JSP的處理過程、JSP語法、JSP的內置對象、每種對象的使用方法和使用技巧,以及簡單web應用程式的開發設計。 ...
  • 實例 this 是什麼? JavaScript this 關鍵詞指的是它所屬的對象。 它擁有不同的值,具體取決於它的使用位置: 在方法中,this 指的是所有者對象。 單獨的情況下,this 指的是全局對象。 在函數中,this 指的是全局對象。 在函數中,嚴格模式下,this 是 undefine ...
  • 封裝getScroll函數 1. 獲取頁面向上或者向左卷曲出去的距離的值 2. 瀏覽器的滾動事件 function getScroll() { return { left: window.pageXOffset || document.documentElement.scrollLeft || do ...
  • 分庫分表之第一篇 1.概述 1.1.分庫分表是什麼 1.2.分庫分表的方式 1.2.1.垂直分表 1.2.2.垂直分庫 1.2.3.水平分庫 1.2.4.水平分表 1.2.5 小結 1.3.分庫分錶帶來的問題 1.3.1.事務一致性問題 1.3.2.跨節點關聯查詢 1.3.3.跨節點分頁、排序函數 ...
  • 1、面向對象的四個優點:可復用、可拓展、可維護、靈活性高。編寫代碼的時候一定要牢記:靈活運用面向對象的三大特征:封裝、繼承、多態,降低代碼之間的耦合,避免做無用功,避免代碼不可維護。ps:不要懶,現在懶只會讓將來不得不勤快 ...
  • 0、前言 任何系統,我們不會傻傻的在每一個地方進行異常捕獲和處理,整個系統一般我們會在一個的地方統一進行異常處理,spring boot全局異常處理很簡單; 介紹前先說點題外話,我們現在開發系統,都是前後端完全分離的,後端只提供RESTfull API,禁止涉及任何界面,什麼thymeleaf、JS ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...