通过实现依赖注入和路由,构建一个自己的现代化 PHP 框架 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
parvin
V2EX    PHP

通过实现依赖注入和路由,构建一个自己的现代化 PHP 框架

  •  1
     
  •   parvin 2019-03-12 15:07:24 +08:00 3997 次点击
    这是一个创建于 2455 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如何提高自己编写代码的能力呢?我们首先想到的是阅读学习优秀的开源项目,然后写一个自己的 web 框架或类库组件。作为 web 开发者,我们通常都是基于面向对象 OOP 来开发的,所以面向对象的设计能力或者说设计模式的运用能力尤为重要,当然还有开发语言本身特性和基础的灵活运用。

    我们可以去阅读一些优秀的开源项目,理解里面的代码设计,去学习和造轮子来提高自己。

    在优秀成熟的 web framework 中路由和 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 规范实现,使用了 PHP 的类反射功能,去实例化类定义的对象依赖。定义类的对象依赖包括 3 种注入实现方式:构造方法注入( Constructor Injection )、setter 方法或属性注入( Setter Injection )、匿名回调函数注入( Closure callable Injection ),代码示例如下:

    1.构造方法注入( 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.方法注入( Setter Injection )

    <?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.匿名回调函数注入 ( Closure callable Injection )

    <?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 组件来处理 HTTP 请求( 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; }); /* title 为可选参数 */ $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; 

    其它的 ORM、cache、filesystem、session、validation 等组件可以使用 composer 来由用户自由扩展。

    项目地址 https://github.com/parvinShi/eagle

    5 条回复    2019-03-17 17:33:22 +08:00
    GM
        1
    GM  
       2019-03-12 15:31:14 +08:00
    自动布线。。。吐槽一下翻译。
    parvin
        2
    parvin  
    OP
       2019-03-12 16:17:20 +08:00
    @GM 自动装配,大概那个意思
    runningman
        3
    runningman  
       2019-03-12 19:19:07 +08:00 via iPhone
    晚上看看 我确实需要
    Varobjs
        4
    Varobjs  
       2019-03-14 13:49:08 +08:00
    借楼,发一个玩具框架 https://github.com/varobjs/xyz
    [\滑稽]
    dvaknheo
        5
    dvaknheo  
       2019-03-17 17:33:22 +08:00
    OK ErrorHandler
    OK Router
    OK Config

    Request Response
    为什么不用 PHP 预定义的 $_GET,$_POST ?
    我写 DNMVCS 的时候,碰到了 Swoole 不支持的问题
    想了一段时间,最后用
    DNMVCS::SG()->_GET 这样解决了 $_GET,$_POST 问题。
    目前是必选,将来会切成可选。

    ORM 这不是框架的必要组成部分。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5676 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 02:58 PVG 10:58 LAX 18:58 JFK 21:58
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86