用 Composer 从零搭建 PHP 框架:Router、IoC 容器、Middleware 到 ServiceProvider

自己搭一个最小 PHP 框架是理解 Laravel 架构的最好方式,比直接读 Laravel 源码有效得多。

项目结构

my-framework/
├── app/
│   └── Controller/
│       └── HomeController.php
├── public/
│   └── index.php
├── src/
│   ├── Router.php
│   ├── Container.php
│   └── Middleware.php
├── vendor/
└── composer.json

第一步:Composer 初始化

mkdir my-framework && cd my-framework
composer init

composer.json

{
    "name": "me/framework",
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Framework\\": "src/"
        }
    }
}

生成自动加载文件:

composer dump-autoload

第二步:最小 Router

src/Router.php

<?php
namespace Framework;

class Router
{
    private array $routes = [];

    public function get(string $uri, $action): void
    {
        $this->routes['GET'][$uri] = $action;
    }

    public function post(string $uri, $action): void
    {
        $this->routes['POST'][$uri] = $action;
    }

    public function dispatch(): void
    {
        $method = $_SERVER['REQUEST_METHOD'];
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

        if (isset($this->routes[$method][$uri])) {
            call_user_func($this->routes[$method][$uri]);
            return;
        }

        http_response_code(404);
        echo "404 Not Found";
    }
}

入口文件 public/index.php

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

use Framework\Router;
use App\Controller\HomeController;

$router = new Router();
$router->get('/', [HomeController::class, 'index']);
$router->dispatch();

启动:

php -S localhost:8000 -t public

第三步:IoC 容器(依赖注入)

Laravel 的核心是 Container,不是 Router。

src/Container.php

<?php
namespace Framework;

use ReflectionClass;

class Container
{
    private array $bindings = [];

    public function bind(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
    }

    public function make(string $class): object
    {
        if (isset($this->bindings[$class])) {
            return ($this->bindings[$class])($this);
        }

        // 用反射自动解析构造函数依赖
        $ref = new ReflectionClass($class);
        $constructor = $ref->getConstructor();

        if (!$constructor) {
            return new $class();
        }

        $params = array_map(
            fn($p) => $this->make($p->getType()->getName()),
            $constructor->getParameters()
        );

        return $ref->newInstanceArgs($params);
    }
}

这样 $container->make(HomeController::class) 会自动实例化其所有依赖:

class HomeController
{
    public function __construct(
        private UserService $userService
    ) {}
}

第四步:Middleware

请求流程:Request → Middleware → Controller → Response

<?php
namespace Framework;

class Middleware
{
    private array $stack = [];

    public function add(callable $middleware): void
    {
        $this->stack[] = $middleware;
    }

    public function run(callable $handler): void
    {
        $runner = array_reduce(
            array_reverse($this->stack),
            fn($next, $mw) => fn() => $mw($next),
            $handler
        );
        $runner();
    }
}

示例认证中间件:

$middleware->add(function (callable $next) {
    if (!isset($_SESSION['user'])) {
        http_response_code(401);
        echo '请先登录';
        return;
    }
    $next();
});

第五步:ServiceProvider

<?php
namespace Framework;

abstract class ServiceProvider
{
    public function __construct(
        protected Container $app
    ) {}

    abstract public function register(): void;
}

示例:

class DatabaseServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind('db', fn($app) => new Database(
            config('database.host'),
            config('database.name')
        ));
    }
}

注册:

(new DatabaseServiceProvider($container))->register();

学习路线

  1. Router + 自动加载 → 理解 HTTP 分发
  2. Container + ReflectionClass → 理解依赖注入
  3. Middleware → 理解 Laravel Pipeline
  4. ServiceProvider → 理解 Laravel 服务注册
  5. 自己实现后再看 Laravel 源码,对照理解

完成前 3 步后基本就能看懂 Laravel 入口流程了。