V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
parvin
V2EX  ›  PHP

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

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

    如何提高自己编写代码的能力呢?我们首先想到的是阅读学习优秀的开源项目,然后写一个自己的 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   ·   实用小工具   ·   2802 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 02:28 · PVG 10:28 · LAX 18:28 · JFK 21:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.