為什麼要自己「寫」框架?
我們都知道在 github
有很多優秀的開源專案,有的專案是專注於某一項事情的庫。比如某某專案是一個 HTTP 客戶端,某某專案是一個處理圖片的庫,某某專案是一個模板引擎等等。
它們在自己所處領域都是非常優秀的,於是我奔著學習使用這些庫的目的,挑選自己喜歡的把他們組成一個簡易的自定義框架。
框架的構成
一個完善的框架有很多特性,現在我準備取一些基礎必須的來一個個拆解,並選擇我喜歡用的擴充套件包。
我這次組裝的框架將會包括如下特性:
- IOC容器
- 資料庫訪問
- 路由
- 日誌
- 模板引擎
- 配置檔案載入
- 除錯
元件組裝
IOC容器php-di/php-di
框架存在的目的是為了讓我們更方便的來開發我們的專案,讓我們更容易的寫出鬆耦合便於維護的程式碼。要起到解耦合作用框架常常是採用的一個依賴注入容器來處理。不管是大名鼎鼎的 spring
還是我們的 laravel
都是這樣。所以我來到 packagist.org 找對應的擴充套件包。分別看了排名前幾個的最終挑選了 php-di
。它吸引我的地方是容易上手,並且可以通過註解的方式來注入物件,非常方便。
- 建立容器
<?php
AnnotationRegistry::registerLoader(array($loader, "loadClass"));
$builder = new ContainerBuilder();
$builder->useAnnotations(true);
$container = $builder->build();
//bootstrap.php
- 通過註解注入物件
<?php
namespace App\Controller;
use App\Repository\SettingRepo;
use DI\Annotation\Inject;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class SettingController
{
/**
* @Inject()
* @var SettingRepo
*/
private $settingRepo;
public function set($params)
{
$this->settingRepo->set($params['key'], $params['value']);
return new Response("Set \"{$params['key']}\" value to \"{$params['value']}\"");
}
public function get($params)
{
return new Response(app(SettingRepo::class)->get($params['key']));
}
public function all()
{
return new JsonResponse($this->settingRepo->all());
}
}
可以看到我並沒有對 SettingController
的 settingRepo
屬性做任何賦值,我只是給他加了 @Inject()
註解,我就可以使用它了,是不是很方便。
資料庫操作doctrine/dbal
要開發專案必然要存取資料,資料庫操作免不了,又來到 packagist.org 找到了強大的 doctrine/dbal
- 先在容器中加入一個資料庫連線
<?php
$builder->addDefinitions([
'db' => function (\DI\Container $c) {
$config = $c->get('config');
$connectionParams = ['url' => $config['db.url']];
$configuration = new Configuration();
$configuration->setSQLLogger($c->get(\App\Doctrine\DBAL\Logging\QueryFileLogger::class));
return DriverManager::getConnection($connectionParams, $configuration);
}
]);
- 運算元據庫
<?php
namespace App\Repository;
use DI\Annotation\Inject;
use Doctrine\DBAL\Connection;
class SettingRepo
{
/**
* @Inject("db")
* @var Connection
*/
private $db;
public function get($key)
{
$result = $this->db->createQueryBuilder()->select('value')
->from('setting')
->where("key = :key")
->setParameter('key', $key)
->setMaxResults(1)
->execute()->fetch();
return $result['value'];
}
public function set($key, $value, $description = '')
{
if ($this->get($key)) {
$this->db->update('setting', ['value' => $value], ['key' => $key]);
} else {
$this->db->insert('setting', ['key' => $key, 'value' => $value, 'description' => $description]);
}
}
public function all()
{
return $this->db->createQueryBuilder()->select('*')
->from('setting')
->execute()->fetchAll();
}
}
路由symfony/routing
和以上操作一樣,最終挑選了 symfony/routing
作為路由元件。
<?php
$routes = new RouteCollection();
$routes->add('home', new Route("/", ['_controller' => [\App\Controller\WelcomeController::class, 'home']]));
$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
$context = new RequestContext();
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->matchRequest($request);
$controller = $parameters['_controller'];
if ($controller instanceof Closure) {
$response = $controller($parameters);
} else {
$response = app($controller[0])->{$controller[1]}($parameters);
}
$response->send();
訪問首頁最終看到 "Welcome!" ,路由成功。
其它元件
其它元件都是按照如上操作一個個加入進來的就不一一介紹詳細介紹了。
-
日誌
開發過程中常常會記錄日誌,最終選用monolog來記錄日誌。 -
模板引擎
模板引擎必不可少,選用twig -
配置檔案載入
使用 hassankhan/config - 除錯
寫程式碼肯定會時不時寫錯,預設的報錯資訊不方便跟蹤,選用symfony/debug
,symfony/var-dumper
除錯。
小結
通過一步步的加入各個擴充套件包可以幫助學習對應的擴充套件包。也可以更深入的理解到框架都由哪些構成。這裡是例子程式碼。此程式碼只是做拋磚引玉的作用,感興趣的朋友可以根據自己的喜好,加入自己喜歡用的擴充套件包來實現想要的功能。