---
title: 用 PHP 8.5 从零构建零依赖 Web 框架：路由与容器
url: https://vabora.dev/posts/php85-zero-dependency-framework
author: Vabora
published: 2026-09-17 12:05:53
updated: 2026-09-26 20:02:28
category: 技术分享
tags: 架构设计, PHP 8, Vabora Framework
canonical: https://vabora.dev/posts/php85-zero-dependency-framework
license: 转载与引用请注明出处并保留原文链接
---

# 用 PHP 8.5 从零构建零依赖 Web 框架：路由与容器

> 零依赖不是目的，可控才是。当框架的每一行代码你都能读懂，线上排障就不再是“玄学”。 为什么选择零依赖 Composer 生态很繁荣，但也带来三个现实问题： 问题 表现 零依赖的收益 --- --- --- 依赖膨胀 一个博客装进 40+ 个包 部署包只有几百 KB 版本地狱 升级…
零依赖不是目的，**可控**才是。当框架的每一行代码你都能读懂，线上排障就不再是“玄学”。

## 为什么选择零依赖

Composer 生态很繁荣，但也带来三个现实问题：

| 问题 | 表现 | 零依赖的收益 |
| --- | --- | --- |
| 依赖膨胀 | 一个博客装进 40+ 个包 | 部署包只有几百 KB |
| 版本地狱 | 升级一个包牵连一片 | 只有自己的代码要维护 |
| 排障困难 | 堆栈 90% 在 vendor | 断点直接落在业务代码 |

> 依赖越少，不可控的部分就越少。

## 路由：从正则到编译缓存

最朴素的路由就是一个 `path => handler` 映射表，但动态参数必须编译成正则：

```php
public static function get(string $path, mixed $action): RouteItem
{
    $pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $path);
    return self::collection()->add(['GET'], $path, $action, $pattern);
}
```

几个必须处理的细节：

- 参数默认约束为 `[^/]+`，需要数字时用 `->where('id', '\d+')`
- 静态路由优先匹配，避免动态路由“吃掉” `/posts/new`
- 生产环境把编译结果缓存成 PHP 数组文件

## 容器：只做两件事

一个够用的容器只需要支持**绑定**与**自动解析**：

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

    $reflection = new ReflectionClass($class);
    $constructor = $reflection->getConstructor();

    if ($constructor === null) {
        return new $class();
    }

    $arguments = [];
    foreach ($constructor->getParameters() as $parameter) {
        $type = $parameter->getType();
        $arguments[] = $type instanceof ReflectionNamedType && !$type->isBuiltin()
            ? $this->make($type->getName())
            : null;
    }

    return $reflection->newInstanceArgs($arguments);
}
```

## 中间件洋葱模型

中间件用闭包层层包裹，注意**反向遍历**：

```php
foreach (array_reverse($middleware) as $entry) {
    $handler = fn (Request $request): Response => $instance->handle($request, $handler);
}
```

这样第一个中间件在最外层，既能前置处理，也能后置处理响应。

## 小结

- 路由编译结果要可缓存
- 容器保持最小实现，够用就好
- 中间件顺序决定请求生命周期
