如何提高自己編寫代碼的能力呢?作爲web開發者,我們通常都是基於面向對象OOP來開發的,所以面向對象的設計能力或者說設計模式的運用能力尤爲重要,當然還有開發語言本身特性和基礎的靈活運用。
我們可以去閱讀一些優秀的開源項目,理解裏面的代碼設計,去學習和造輪子來提高自己。
我比較關注web framework中的路由、HTTP、依賴注入容器這幾部分,路由和http處理是web框架必不可少的,整個框架的服務對象依賴解析也是很重要的,有了依賴注入容器可以實現類很好的解耦。
Dependency Injection Container
先來說下什麼是依賴注入,自維基百科 Wikipedia:依賴注入是一種允許我們從硬編碼的依賴中解耦出來,從而在運行時或者編譯時能夠修改的軟件設計模式。
依賴注入通過構造注入,函數調用或者屬性的設置來提供組件的依賴關係。
下面的代碼中有一個 Database 的類,它需要一個適配器來與數據庫交互。我們在構造函數裏實例化了適配器,從而產生了耦合。這會使測試變得很困難,而且 Database 類和適配器耦合的很緊密。
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct()
{
$this->adapter = new MySqlAdapter;
}
}
class MysqlAdapter {}
這段代碼可以用依賴注入重構,從而解耦
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct(MySqlAdapter $adapter)
{
$this->adapter = $adapter;
}
}
class MysqlAdapter {}
現在我們通過外界給予 Database 類的依賴,而不是讓它自己產生依賴的對象。我們甚至能用可以接受依賴對象參數的成員函數來設置,或者如果 $adapter 屬性本身是 public的,我們可以直接給它賦值。
根據依賴注入的概念,我們的框架實現了這些特性,代碼如下。
Dependency injection Container基於PSR-11規範實現,包括3種注入實現方式:構造方法注入(Constructor Injection)、setter方法或屬性注入(Setter Injection)、匿名回調函數注入。
構造方法注入(Constructor Injection)
<?php
declare(strict_types=1);
namespace Examples;
use Eagle\DI\Container;
class Foo
{
/**
* @var \Examples\Bar
*/
public $bar;
/**
* Foo constructor.
* @param \Examples\Bar $bar
*/
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
}
/*class Bar {
}*/
class Bar {
public $baz;
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
}
class Baz {
}
$container = new Container;
$container->set(Foo::class)->addArguments(Bar::class);
$container->set(Bar::class)->addArguments(Baz::class);
$foo = $container->get(Foo::class);
var_dump($foo, $foo->bar);
var_dump($foo instanceof Foo); // true
var_dump($foo->bar instanceof Bar); // true
var_dump($foo->bar->baz instanceof Baz); // true
2.方法注入
<?php
declare(strict_types=1);
namespace Examples;
require 'vendor/autoload.php';
use Eagle\DI\Container;
class Controller
{
public $model;
public function __construct(Model $model)
{
$this->model = $model;
}
}
class Model
{
public $pdo;
public function setPdo(\PDO $pdo)
{
$this->pdo = $pdo;
}
}
$container = new Container;
$container->set(Controller::class)->addArguments(Model::class);
$container->set(Model::class)->addInvokeMethod('setPdo', [\PDO::class]);
$container->set(\PDO::class)
->addArguments(['mysql:dbname=test;host=localhost', 'root', '111111']);
$controller = $container->get(Controller::class);
var_dump($controller instanceof Controller); // true
var_dump($controller->model instanceof Model); // true
var_dump($controller->model->pdo instanceof \PDO); // true
3.匿名回調函數注入
<?php
declare(strict_types=1);
namespace Examples;
require 'vendor/autoload.php';
use Eagle\DI\Container;
class Controller
{
public $model;
public function __construct(Model $model)
{
$this->model = $model;
}
}
class Model
{
public $pdo;
public function setPdo(\PDO $pdo)
{
$this->pdo = $pdo;
}
}
$container = new Container;
$container->set(Controller::class, function () {
$pdo = new \PDO('mysql:dbname=test;host=localhost', 'root', '111111');
$model = new Model;
$model->setPdo($pdo);
return new Controller($model);
});
$controller = $container->get(Controller::class);
var_dump($controller instanceof Controller); // true
var_dump($controller->model instanceof Model); // true
var_dump($controller->model->pdo instanceof \PDO); // true
自動佈線 (auto wiring)
<?php
declare(strict_types=1);
namespace AutoWiring;
require 'vendor/autoload.php';
use Eagle\DI\ContainerBuilder;
class Foo
{
/**
* @var \AutoWiring\Bar
*/
public $bar;
/**
* @var \AutoWiring\Baz
*/
public $baz;
/**
* Construct.
*
* @param \AutoWiring\Bar $bar
* @param \AutoWiring\Baz $baz
*/
public function __construct(Bar $bar, Baz $baz)
{
$this->bar = $bar;
$this->baz = $baz;
}
}
class Bar
{
/**
* @var \AutoWiring\Bam
*/
public $bam;
/**
* Construct.
*
* @param \AutoWiring\Bam $bam
*/
public function __construct(Bam $bam)
{
$this->bam = $bam;
}
}
class Baz
{
// ..
}
class Bam
{
// ..
}
$container = new ContainerBuilder;
$container = $container->build();
$foo = $container->get(Foo::class);
var_dump($foo instanceof Foo); // true
var_dump($foo->bar instanceof Bar); // true
var_dump($foo->baz instanceof Baz); // true
var_dump($foo->bar->bam instanceof Bam); // true
Route
再介紹下路由使用的例子,route可以使用symfony的http foundation組件來處理HTPP消息請求(http messages)。
<?php
require 'vendor/autoload.php';
use Eagle\Route\Router;
use Symfony\Component\HttpFoundation\Request;
$router = new Router();
$router->get('/articles', function () {
return 'This is articles list';
});
$router->get('/articles/{id:\d+}', function ($id) {
return 'Article id: ' . $id;
});
$router->get('/articles/{id:\d+}[/{title}]', function ($id, $title) {
return 'Article id: ' . $id . ', title: ' . $title;
});
/*匹配處理路由組*/
$router->group('/articles', function () use ($router) {
$router->get('/list', function() {
return 'This is articles list';
});
$router->get('/detail', function ($id, $title) {
return 'Article detail id: ' . $id . ', title: ' . $title;
});
});
$request = new Request();
$routeHandler = $router->getRouteHandler();
$response = $routeHandler->handle($request);
echo $response;