Skip to content

配置 ​

三层结构 ​

text
.env                       环境变量(不提交)
  ↓  env() 读取
config/*.php               配置文件(15 个,提交)
  ↓  Config::get() / config() 读取
业务代码
php
// config/database.php
'host' => env('DB_HOST', '127.0.0.1'),
php
// 业务代码
config('database.default');        // 等价于 Config::get()
Config::get('database.default');

.env 不进仓库

.gitignore 已经排除了它。新环境部署时从 .env.example 复制一份:

bash
cp .env.example .env
php bin/sr key:generate

15 个配置文件 ​

文件管什么
api.phpAPI 域、路由模式、中间件别名
app.php应用名、环境、调试、时区、全局中间件
auth.php守卫、JWT 密钥与有效期
cache.php缓存驱动与连接
cors.php跨域策略
database.php数据库连接
http.php请求体大小上限
image.php图片驱动、质量、内存上限
log.php日志通道与格式
mail.php邮件驱动与 SMTP
pagination.php分页默认值与上限
queue.php队列驱动与表名
ratelimit.php限流规则
upload.php上传大小、白名单、分片
validation.php校验相关(当前只有 1 个键)

数组结构的配置在哪详述 ​

上表里有些文件只是「一行说明」,实际内容是一组嵌套数组。这些在对应组件文档里讲:

配置路径详述位置
api.domains路由 —— 域的 prefix / versions / middleware
api.middleware_aliases中间件 —— 别名注册
app.global_middleware中间件 —— 全局中间件顺序
auth.guards认证 —— 守卫与 JWT
cache.stores缓存 —— 四个驱动的连接参数
cors.*见下方「跨域」一节
database.connections多数据库 —— 三库连接
log.channels日志 —— 五个通道
mail.mailers邮件 —— log / smtp
queue.drivers队列 —— database / sync
ratelimit.routes限流 —— 按路由名覆盖规则
upload.disks / upload.chunk上传 —— 存储与分片

这一节不放重复内容——同一个配置说两遍,改的时候必有一处忘改。


环境变量总表 ​

.env 里能配的全部(53 个)。

应用 ​

变量默认说明
APP_NAMESunsetReverie显示名
APP_ENVlocallocal / test / staging / production
APP_DEBUGfalse出错时是否输出堆栈。生产必须 false
APP_KEY空加密密钥,key:generate 生成
APP_TIMEZONEAsia/Shanghai时区

APP_DEBUG=true 在生产会泄露堆栈与配置

env:check 会检查这一项。上线前确认是 false。

API 域 ​

变量默认说明
API_DEFAULT_DOMAINweb未匹配到域时用哪个
API_ROUTING_MODEsinglesingle / directory

认证 ​

变量默认说明
AUTH_GUARDjwt默认守卫
JWT_SECRET空jwt:secret 生成。换它会让所有 token 失效
JWT_TTL7200token 有效期(秒)

数据库 ​

变量默认说明
DB_CONNECTIONmysqlmysql / pgsql / sqlite
DB_HOST127.0.0.1
DB_PORT3306(mysql)/ 5432(pgsql)
DB_DATABASEapp库名
DB_USERNAMEroot / postgres
DB_PASSWORD空
DB_TIMEOUT5连接超时(秒)
DB_SQLITE_DATABASEstorage/database.sqlite仅 sqlite

缓存 ​

变量默认说明
CACHE_DRIVERfilefile / redis / apcu / array
CACHE_PREFIXsr:键前缀,多项目共用一个 Redis 时隔离
CACHE_TTL3600默认存活秒数(0 = 永久)
REDIS_HOST127.0.0.1
REDIS_PORT6379
REDIS_PASSWORD空
REDIS_DB0库编号
REDIS_TIMEOUT1.5连接超时。别设太大,缓存挂了应该快速失败

HTTP ​

变量默认说明
HTTP_MAX_BODY_SIZE8388608(8MB)body.size 中间件的上限

分页 ​

变量默认说明
PAGINATION_DEFAULT20不传 per_page 时的默认值
PAGINATION_MAX50上限,超过会被夹住

上传 ​

变量默认说明
UPLOAD_DISKlocal存储驱动
UPLOAD_MAX_SIZE10485760(10MB)单文件上限
UPLOAD_SIGN_SECRET空签名 URL 的密钥。为空时签名可伪造
UPLOAD_CHUNK_SIZE5MB分片大小

图片 ​

变量默认说明
IMAGE_DRIVERautoauto / gd / imagick
IMAGE_QUALITY82JPEG 质量
IMAGE_MEMORY_MB0解码内存上限,0 = 不限制

IMAGE_MEMORY_MB=0 有风险

图片解码后的内存与文件大小无关:6000×4000 的图约占 96MB。

不设上限时,一个几百 KB 的大图能把 PHP 进程撑爆。

建议设成 memory_limit 的 1/3。

日志 ​

变量默认说明
LOG_CHANNELdailydaily / single / stderr / stdout / null
LOG_LEVELdebug最低级别。生产建议 info 或 warning
LOG_DAYS14按天切分时保留几天(0 = 不清理)
LOG_ACCESStrue是否记录访问日志

邮件 ​

变量默认说明
MAIL_MAILERloglog(不真发)/ smtp
MAIL_HOST127.0.0.1
MAIL_PORT1025
MAIL_USERNAME空
MAIL_PASSWORD空
MAIL_FROMnoreply@xi.blog发件地址
MAIL_FROM_NAME夕梦博客发件人显示名
MAIL_TIMEOUT10SMTP 超时(秒)

默认 MAIL_MAILER=log——不会真发

开发环境方便,生产必须改成 smtp,否则用户收不到任何邮件。

而且不会有报错——邮件「成功」写进日志了。

队列 ​

变量默认说明
QUEUE_DRIVERdatabasedatabase / sync
QUEUE_NAMEdefault默认队列名

sync 驱动会立即同步执行——测试时用,生产别用。

限流 ​

变量默认说明
RATELIMIT_ENABLEDtrue关掉等于没有限流
RATELIMIT_STORE空空 = 用 cache.default

高并发建议单独指到 redis。


跨域(CORS) ​

php
// config/cors.php
'allowed_origins' => [],          // ← 默认空数组 = 不启用 CORS
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
'allowed_headers' => ['Content-Type', 'Authorization', 'X-Request-Id', 'Idempotency-Key'],
'exposed_headers' => ['X-Request-Id', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'Retry-After'],
'max_age' => 86400,
'supports_credentials' => false,

默认不启用 ​

allowed_origins 为空时,cors 中间件不发任何 CORS 头。

浏览器会拦掉跨域请求——这是安全的默认值:要用才显式配。

必须列完整域名 ​

php
// ✅ 精确列出
'allowed_origins' => [
    'https://admin.example.com',
    'https://app.example.com',
    'http://localhost:5173',      // 本地开发
],

// ❌ 不支持通配子域
'allowed_origins' => ['https://*.example.com'],

匹配逻辑是精确比对:

php
return in_array('*', $allowedOrigins, true)
    || in_array($origin, $allowedOrigins, true);

https://*.example.com 会被当成一个普通字符串,永远匹配不上。

子域多的话只能逐个列(或者自己扩展中间件)。

* 与 supports_credentials 互斥 ​

php
'allowed_origins' => ['*'],
'supports_credentials' => true,      // ❌ 浏览器会拒绝

带 * 时响应头是 Access-Control-Allow-Origin: *, 而规范禁止 * 与 Allow-Credentials: true 同时出现。

本框架用 Bearer Token 不用 Cookie,所以 supports_credentials 保持 false 就行。

exposed_headers 决定前端能读到哪些响应头 ​

js
// 只有列在 exposed_headers 里的,JS 才读得到
fetch(url).then(res => {
    res.headers.get('X-RateLimit-Remaining');   // 默认能读
    res.headers.get('X-Custom');                // undefined
});

默认暴露了 4 个:X-Request-Id、X-RateLimit-Limit、X-RateLimit-Remaining、Retry-After。

自己加了自定义响应头要在前端读的话,记得加进 exposed_headers。

max_age 是预检缓存 ​

php
'max_age' => 86400,     // 24 小时

浏览器记住「这个预检通过了」的时长。期间不再发 OPTIONS 请求。

值越大,预检越少,但改了 CORS 配置后生效越慢(用户要等缓存过期或清缓存)。

预检请求会被短路 ​

text
OPTIONS /admin/v1/posts
  → cors 中间件直接返回 204,不进入路由和鉴权

这是必要的:预检请求不带 Authorization,走正常流程必然 401。

常见错误 ​

现象原因
浏览器报 No 'Access-Control-Allow-Origin'allowed_origins 是空的
配了子域通配但不生效不支持通配,要逐个列
前端读不到自定义响应头没加进 exposed_headers
带 Cookie 时 CORS 全挂* 与 supports_credentials 冲突
改了配置要等很久才生效max_age 太长,清浏览器缓存或调小

生产必改的 6 项 ​

ini
APP_ENV=production
APP_DEBUG=false                    # 否则泄露堆栈
LOG_LEVEL=warning                  # debug 会写满磁盘
MAIL_MAILER=smtp                   # 默认是 log,不会真发
RATELIMIT_STORE=redis              # 限流是高频写
UPLOAD_SIGN_SECRET=<随机 64 位>     # 为空时签名可伪造

env:check 会检查其中几项。

生成随机密钥:

bash
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'

常见错误 ​

改了 .env 不生效 ​

bash
php bin/sr config:clear      # 清配置缓存
killall -USR2 php-fpm        # 清 opcache

php bin/sr serve 不需要(每次请求重新读)。

env() 在配置文件外返回 null ​

env() 只在 config/*.php 里可靠——配置缓存之后,.env 不再被读取。

业务代码里用 config():

php
env('APP_NAME');              // ❌ 缓存后是 null
config('app.name');           // ✅

布尔值的转换规则 ​

env() 会把这些字符串转成对应类型:

.env 里写的读出来的类型
true / "true" / 'true'trueboolean
false / "false" / 'false'falseboolean
nullnullnull
36003600integer
空值(KEY=)''string
其他任意文本原样string

实测确认:引号会被剥掉,加不加引号结果一样。

ini
APP_DEBUG=false       # → false(boolean)
APP_DEBUG="false"     # → false(boolean)—— 也可以
APP_DEBUG='false'     # → false(boolean)

所以「必须加引号」或「必须不加引号」都是误解。

但仍建议不加引号

一致性——同一个文件里有些加有些没加,读的时候要多想一次。

而且带空格的值必须加引号:

ini
APP_NAME=My App        # → "My App"(能读,但依赖 trim 行为)
APP_NAME="My App"      # ✅ 明确

配置缓存后 Config::set() 还有用吗 ​

有用——set() 改的是内存里的值,不影响缓存文件。

但只对当前请求有效,下一个请求重新从缓存加载。

多环境怎么管理 ​

三条路:

做法说明
每台机器一个 .env最简单,手动维护
环境变量注入(Docker / K8s)不落盘,.env 可以只留开发用的
config/{env}/ 目录框架不支持——需要自己扩展

下一步 ​

  • 命令行 —— 40 个命令
  • 门面 —— Config::get() 的完整 API
  • 部署 —— 生产环境的完整清单

以 MIT 许可发布 · v1.0 夕照