從頭開始擼一個 PHP 框架

本文僅僅是用於自我學習記錄,參考了JohnLui的原創文章: https://segmentfault.com/a/11...
隨着PHP標準和Composer包管理工具的面世,普通開發者擼一個框架已經再也不是什麼難事了。

不管是路由管理、ORM管理、仍是視圖渲染都有許許多多優秀的包可使用。咱們就像堆積木同樣把這些包用composer一個個堆積起來。php

接下來咱們即是簡單地實現一個MVC框架,來加深咱們對框架的理解。html

composer

建立一個空的 composer.json 文件。mysql

{
  
}

或者在空目錄下執行:nginx

composer init

則能夠生成一個類型以下的文件:git

{
    "name": "charlie/my_first_framework",
    "description": "My First Framework",
    "type": "project",
    "license": "MIT",
    "authors": [
        {
            "name": "Charlie",
            "email": "demo@qq.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {}
}

安裝第一個包

咱們接下來安裝一個管理路由的包: noahbuscher/macaw。 功能比這個更增強大的路由包有不少,可是爲了簡單起見,咱們選擇安裝這個包。github

composer require noahbuscher/macaw

當前目錄結構爲:web

➜  demo tree
.
├── composer.json
├── composer.lock
└── vendor
    ├── autoload.php
    ├── composer
    │   ├── ClassLoader.php
    │   ├── LICENSE
    │   ├── autoload_classmap.php
    │   ├── autoload_namespaces.php
    │   ├── autoload_psr4.php
    │   ├── autoload_real.php
    │   ├── autoload_static.php
    │   └── installed.json
    └── noahbuscher
        └── macaw
            ├── LICENSE.md
            ├── Macaw.php
            ├── README.md
            ├── composer.json
            ├── composer.lock
            └── config
                ├── Web.config
                └── nginx.conf

public/index.php

咱們在根目錄下新建一個public文件夾,並在該文件夾下新建 index.phpindex.php 文件相似於一個大廈的入口,咱們全部的請求都運行 index.phpsql

下面是 index.php 文件的代碼:shell

// 自動加載vendor目錄下的包
require '../vendor/autoload.php';

routes.php

此時咱們觀察 index.php,除了把vendor下面的包都 require 進來了外,其餘啥都沒幹。那麼如何響應各類各樣的請求呢?數據庫

咱們須要定義路由。路由就有點像快遞分揀站,把寫着不一樣地址的請求分撥給不一樣的控制器處理。
那麼咱們在根目錄下建立一個 routes 文件夾,並在該文件夾下建立 web.php 文件。文件內容:

<?php

use NoahBuscher\Macaw\Macaw;

Macaw::get('hello', function () {
    echo "hello world";
});

Macaw::dispatch();

而後咱們啓動php內置的開發服務器:

> cd public

> php -S localhost:8001

咱們訪問 http://localhost:8001/hello 就能看到咱們預期的 "hello world".

MVC

上面咱們僅僅實現了訪問一個地址,返回一個字符串。下面咱們來真正搭建MVC框架。MVC其實就是Model、View、Controller三個的簡稱。
無論怎麼樣,咱們先新建三個文件夾再說,即 viewsmodelscontrollers

新建 controllersHomeController.php 文件,代碼以下:

<?php

namespace App\Controllers;


use App\Models\Article;

class HomeController extends BaseController
{
    public function home()
    {
      echo "<h1>This is Home Page</h1>";
    }

}

另外咱們在 routes/web.php 中添加一條路由:

Macaw::get('', 'App\\Controllers\\HomeController@home');

總體代碼爲:

<?php

use NoahBuscher\Macaw\Macaw;

Macaw::get('hello', function () {
    echo "hello world";
});

Macaw::get('', 'App\\Controllers\\HomeController@home');

Macaw::dispatch();

此時已經綁定了一個路由至咱們一個控制器的方法,可是咱們去訪問 http://localhost:8001 會出現 Uncaught Error: Class 'App\Controllers\HomeController' not found in 的錯誤。

爲何呢?

由於此時咱們還並無將控制器加載進來,程序並不知道控制器在哪兒。咱們能夠用 composer 的 classmap 方法加載進來。修改 composer.json 中添加:

"autoload": {
    "classmap": [
      "app/controllers",
      "app/models"
    ]
  }

咱們順便把models也加載進來。

composer dump-autoload

刷新自動加載

Model鏈接數據庫

咱們建立一個Article Model,這個 Model 對應數據庫一張表。此時咱們先用mysql 命令行工具新建一個 demo_database 的數據庫,裏面有一張表 articles , 表的結構大體以下:

mysql> desc articles;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | int(11)      | NO   | PRI | NULL    | auto_increment |
| title   | varchar(256) | YES  |     | NULL    |                |
| content | varchar(256) | YES  |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

咱們再在表裏面填入數據:

mysql> select * from articles;
+----+--------+--------------+
| id | title  | content      |
+----+--------+--------------+
|  1 | hhhhh  | heheheheheh  |
|  2 | hhhhh2 | heheheheh2eh |
+----+--------+--------------+
2 rows in set (0.00 sec)

固然了,咱們如今是直接經過 MySQL 的 insert 命令直接填入數據,後續咱們能夠經過咱們的框架新建 model 。

接下來咱們要作的就是怎麼在 Model 中鏈接數據庫取到數據庫裏面的數據啦! 本文使用的 php 7.1,咱們使用 mysqli 來鏈接數據庫查詢數據:

<?php

namespace App\Models;

class Article 
{
    public static function first()
    {
        //mysql_connect is deprecated
        $mysqli = new \mysqli('localhost', 'root', 'w.x.c.123', 'demo_database');
        if ($mysqli->connect_errno) {
            echo "Failed to connect to Mysql: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error();
        }
        $mysqli->set_charset('utf-8');
        $result = $mysqli->query("SELECT * FROM articles limit 0,1");

        $article = $result->fetch_array();
        if ($article) {
            echo "<p>" . $article['title'] . "</p>";
            echo "<p>" . $article['content'] . "</p>";
        }
        $mysqli->close();
    }
}

這麼一來咱們就能夠在控制器裏面使用
`

Article::first();

`
來查詢 articles 表裏面的第一條article數據,而後咱們再經過 echo 返回給瀏覽器。

<?php

namespace App\Controllers;


use App\Models\Article;

class HomeController extends BaseController
{
    public function home()
    {
        Article::first();
    }

}

View層

看上面的代碼,咱們在 Article Model 中連續寫了兩條 echo 語句來格式化輸出。若是後續咱們的頁面十分複雜的時候,咱們把全部的格式化輸出的代碼都寫在 Model 裏面感受是個災難。咱們應該把這些格式化輸出的代碼分離出來,這即是咱們說的 MVC 層的 View 層。

咱們在 views 目錄下新建 home.php:

<?php

echo "<p>" . $article['title'] . "</p>";
echo "<p>" . $article['content'] . "</p>";

咱們再改寫一下 Article.php,刪除echo 那兩行,直接

return article;

而後咱們在 HomeController 中指定要使用的 view:

<?php

namespace App\Controllers;


use App\Models\Article;

class HomeController extends BaseController
{
    public function home()
    {
        $article = Article::first();
        require dirname(__FILE__) . "/../views/home.php";
    }
}

咱們這裏的 view 層僅僅是用 PHP 拼接了 html,後續咱們須要實現更加複雜的視圖的時候,咱們能夠引入模版引擎。

ORM

咱們一路從一個空文件夾開始,打造一個本身的一個框架,感受並無寫多少代碼,惟一寫了好幾行代碼感受比較麻煩的就是鏈接數據庫來查詢數據了。咱們咱們有不少 Model,要實現 增刪改查的話,咱們豈不是要重複 鏈接,查詢、插入、刪除、更新,而後關閉鏈接?咱們應該把這些功能分裝一下。

怎麼封裝?有其餘人寫好的包了,直接拿來用吧~ 固然若是你想本身造輪子的話,也能夠本身實現一下。

咱們這裏使用 illuminate/database:

composer require illuminate/database

而後咱們在 public/index.php 中引入:

use Illuminate\Database\Capsule\Manager as Capsule;

require '../vendor/autoload.php';

// Eloquent ORM
$capsule = new Capsule();
$capsule->addConnection(require '../config/database.php');
$capsule->bootEloquent();

//路由配置
require '../routes/web.php';

咱們在鏈接數據的時候,使用了 config/database.php 的數據庫配置文件。

<?php

return [
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'demo_database',
    'username' => 'root',
    'password' => 'secret',
    'charset' => 'utf8',
    'collation' => 'utf8_general_ci',
    'prefix' => ''
];

接下來咱們就能夠刪掉 models/Article.php 文件中咱們寫的大部分代碼,而僅僅須要繼承IlluminateDatabaseEloquentModel 來使用 Eloquent ORM 的功能:

<?php
/**
 * Created by PhpStorm.
 * User: W
 * Date: 24/03/2018
 * Time: 22:23
 */

namespace App\Models;

use Illuminate\Database\Eloquent\Model;


class Article extends Model
{
    public $timestamps = false;
}

更多Eloquent ORM的功能,您也能夠本身查閱文檔。

總結

好了,咱們一個 MVC 框架基本上就搭建完了,咱們回頭看一下整個框架目錄結構,是否是跟 Laravel 有點像呢?

➜  myFirstFramework git:(master) ✗ tree
.
├── README.md
├── app
│   ├── controllers
│   │   ├── BaseController.php
│   │   └── HomeController.php
│   ├── models
│   │   └── Article.php
│   └── views
│       └── home.php
├── composer.json
├── composer.lock
├── config
│   └── database.php
├── public
│   └── index.php
├── routes
│   └── web.php
└── vendor ...

本文用到的示例代碼已經上傳到github:https://github.com/charliex2/...

相關文章
相關標籤/搜索