mysql level env_探究Laravel使用env函数读取环境变量为null的问题

探讨在Laravel项目中使用php artisan config:cache命令后的环境变量读取问题及解决办法。

发现问题

在 Laravel 项目中,如果执行了 php artisan config:cache 命令把配置文件缓存起来后,在 Tinker 中(Tinker 是 Laravel 自带的一个交互式命令行界面),使用 env函数读取环境变量的值为 null,只有执行 php artisan config:clear清除配置缓存后就可以读取了,这是为什么呢?

一探究竟

打开 .env 文件看,这些都是有值的:

APP_ENV=local

APP_KEY=base64:JHE5bOkRg283uT0n1Zq/GgvGEer8ooYiB42/wIcCyvo=

APP_DEBUG=true

APP_LOG_LEVEL=debug

APP_URL=http://www.tanteng.me

DB_CONNECTION=mysql

DB_HOST=127.0.0.1

DB_PORT=3306

DB_DATABASE=tanteng.me

DB_USERNAME=homestead

DB_PASSWORD=secret

如图所示:

3242febbb2bfe5b93e0e5bd5915eb7b8.png

原因何在?

在 Laravel 中,如果执行 php aritisan config:cache命令,Laravel 将会把 app/config 目录下的所有配置文件“编译”整合成一个缓存配置文件到  bootstrap/cache/config.php,每个配置文件都可以通过 env函数读取环境变量,这里是可以读取的。但是一旦有了这个缓存配置文件,在其他地方使用 env函数是读取不到环境变量的,所以返回 null.

让我们看看这段代码,Illuminate/Foundation/Bootstrap/DetectEnvironment.php line 18:

public function bootstrap(Application $app)

{

if (! $app->configurationIsCached()) {

$this->checkForSpecificEnvironmentFile($app);

try {

(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();

} catch (InvalidPathException $e) {

//

}

}

}

这个方法在框架启动后就会运行,这段代码说明了如果存在缓存配置文件,就不会去设置环境变量了,配置都读缓存配置文件,而不会再读环境变量了。

因此,在配置文件即 app/config 目录下的其他地方,读取配置不要使用 env函数去读环境变量,这样你一旦执行php artisan config:cache之后,env函数就不起作用了。所有要用到的环境变量,在 app/config 目录的配置文件中通过 env 读取,其他地方要用到环境变量的都统一读配置文件而不是使用 env函数读取。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

<think> 我们之前的分区表中已经有一个env_backup分区作为备份分区。现在需要修改代码,使得在保存环境变量时同时写入主环境分区和备份环境分区(env_backup)。同时,在启动时能够自动检查主分区是否有效,如果无效则使用备份分区。 修改思路: 1. 在配置文件中定义两个分区的偏移和大小。 2. 修改环境变量驱动(通常是nand_env.c或env_nand.c),实现双分区的读取和写入。 3. 在读取环境变量时,先尝试读取主分区,如果主分区无效(CRC错误或读取失败)则尝试读取备份分区。 4. 在保存环境变量时,同时写入两个分区(先写备份分区,再写主分区,这样如果写主分区时掉电,备份分区还是旧的但完整的数据,可以保证下次启动有环境变量可用)。 具体修改如下: 步骤1:修改板级配置文件(include/configs/your_board.h) 步骤2:修改环境变量驱动(这里假设使用NAND,驱动文件为drivers/env/nand.c) 步骤3:修改设备树(dts)文件,确保分区表包含envenv_backup分区,并且大小一致。 注意:由于分区表已经存在env_backup分区,我们不需要删除它,只需要在代码中使用它。 下面给出具体代码修改: </think> ### 保留env_backup分区的双备份方案实现 以下是修改后的完整方案,保留了设备树中的env_backup分区定义,同时实现可靠的环境变量双备份功能: #### 1. 修改板级配置文件 (`include/configs/your_board.h`) ```c /* 环境变量配置 - 保持原有分区定义 */ #define CONFIG_ENV_OFFSET 0x1200000 // env分区偏移 #define CONFIG_ENV_SIZE 0x100000 // 环境分区大小 #define CONFIG_ENV_OFFSET_REDUND 0x1300000 // env_backup分区偏移 #define CONFIG_ENV_SIZE_REDUND 0x100000 // 备份分区大小 /* 启用双备份支持 */ #define CONFIG_SYS_REDUNDAND_ENVIRONMENT #define CONFIG_ENV_OVERWRITE /* 定义分区名称用于诊断 */ #define CONFIG_ENV_PRIMARY_NAME "env" #define CONFIG_ENV_BACKUP_NAME "env_backup" ``` #### 2. 修改环境驱动文件 (`drivers/env/nand.c`) ```c // 添加分区状态结构体 struct env_partition { uint32_t offset; uint32_t size; const char *name; int valid; }; // 修改env_relocate_spec函数 void env_relocate_spec(void) { struct env_partition partitions[2] = { {CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, CONFIG_ENV_PRIMARY_NAME, 0}, {CONFIG_ENV_OFFSET_REDUND, CONFIG_ENV_SIZE_REDUND, CONFIG_ENV_BACKUP_NAME, 0} }; env_t *env_ptr = NULL; char *buf = malloc(max(CONFIG_ENV_SIZE, CONFIG_ENV_SIZE_REDUND)); // 检查两个分区 for (int i = 0; i < 2; i++) { struct env_partition *p = &partitions[i]; if (readenv(p->offset, (u_char *)buf, p->size) == 0) { p->valid = env_crc_ok(buf); if (p->valid && !env_ptr) { env_ptr = (env_t *)buf; // 首个有效分区 printf("Using %s partition at 0x%08x\n", p->name, p->offset); } } } // 处理分区状态 if (env_ptr) { env_import((char *)env_ptr, 1); } else { printf("## Both %s and %s corrupted! Using default env\n", partitions[0].name, partitions[1].name); set_default_env(NULL); } free(buf); } // 修改saveenv函数 int saveenv(void) { env_t env_new; char *buf = (char *)&env_new; // 准备环境数据 memcpy(env_new.data, env_ptr->data, ENV_SIZE); env_new.crc = env_crc; // 先写入env_backup分区 int ret = writeenv(CONFIG_ENV_OFFSET_REDUND, (u_char *)buf, CONFIG_ENV_SIZE_REDUND); if (ret) { printf("Error writing %s partition!\n", CONFIG_ENV_BACKUP_NAME); return 1; } // 再写入主env分区 ret = writeenv(CONFIG_ENV_OFFSET, (u_char *)buf, CONFIG_ENV_SIZE); if (ret) { printf("Error writing %s partition! Backup is intact\n", CONFIG_ENV_PRIMARY_NAME); // 标记主分区损坏 env_mark_bad(CONFIG_ENV_OFFSET); return 1; } return 0; } // 添加坏块标记函数 static void env_mark_bad(uint32_t offset) { struct mtd_info *mtd = get_nand_dev_by_index(0); if (mtd) { printf("Marking block @0x%08x as bad\n", offset); int block = offset / mtd->erasesize; nand_block_markbad(mtd, block); } } ``` #### 3. 修改设备树文件 (`arch/arm/dts/your_board.dts`) ```dts partitions { compatible = "fixed-partitions"; #address-cells = <1>; #size-cells = <1>; partition@0 { label = "boot"; reg = <0 0x1200000>; }; // 保留env分区定义 partition@1 { label = "env"; reg = <0x1200000 0x100000>; }; // 保留env_backup分区定义 partition@2 { label = "env_backup"; reg = <0x1300000 0x100000>; }; // 其他分区 partition@3 { label = "dgs"; reg = <0x1400000 0x80000>; }; // ...无需调整后续分区 }; ``` #### 4. 添加分区健康检查命令 (`common/cmd_nvedit.c`) ```c // 添加env_check命令 static int do_env_check(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) { struct env_partition partitions[] = { {CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, CONFIG_ENV_PRIMARY_NAME, 0}, {CONFIG_ENV_OFFSET_REDUND, CONFIG_ENV_SIZE_REDUND, CONFIG_ENV_BACKUP_NAME, 0} }; puts("Environment partitions health check:\n"); for (int i = 0; i < 2; i++) { char *buf = malloc(partitions[i].size); if (!buf) continue; // 读取分区数据 int ret = readenv(partitions[i].offset, (u_char *)buf, partitions[i].size); if (ret) { printf(" %s @0x%08x: READ ERROR\n", partitions[i].name, partitions[i].offset); } else { int valid = env_crc_ok(buf); printf(" %s @0x%08x: %s\n", partitions[i].name, partitions[i].offset, valid ? "VALID" : "CRC ERROR"); } free(buf); } return 0; } // 注册命令 U_BOOT_CMD( env_check, 1, 1, do_env_check, "Check environment partitions health", "" ); ``` #### 5. 修改NAND读写辅助函数 (`env/nand.c`) ```c // 扩展readenv函数支持不同分区大小 int readenv(unsigned long offset, u_char *buf, size_t size) { struct mtd_info *mtd = get_nand_dev_by_index(0); size_t retlen; int ret; // 坏块检查 if (nand_block_isbad(mtd, offset)) { printf("Bad block at 0x%08lx\n", offset); return -EIO; } ret = nand_read_skip_bad(mtd, offset, &retlen, buf, size); if (ret || retlen != size) { return -EIO; } return 0; } // 添加分区同步函数 int env_sync(void) { char *buf = malloc(CONFIG_ENV_SIZE); int ret; // 读取主分区 ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf, CONFIG_ENV_SIZE); if (ret) { free(buf); return ret; } // 写入备份分区 ret = writeenv(CONFIG_ENV_OFFSET_REDUND, (u_char *)buf, CONFIG_ENV_SIZE_REDUND); free(buf); return ret; } ``` ### 分区健康监控方案 ```c /* 添加启动时自动检查 */ __attribute__((constructor)) static void env_init_check(void) { struct env_partition primary = { CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, CONFIG_ENV_PRIMARY_NAME, 0 }; // 检查主分区 char *buf = malloc(primary.size); if (readenv(primary.offset, (u_char *)buf, primary.size) == 0) { primary.valid = env_crc_ok(buf); } if (!primary.valid) { printf("WARNING: Primary env partition invalid! "); printf("Switching to backup on next save\n"); env_set("env_active", "backup"); } free(buf); } /* 定期后台检查 */ void env_bg_check(void) { static ulong next_check = 0; if (get_timer(0) < next_check) return; next_check = get_timer(0) + 60000; // 每60秒检查一次 // 执行同步检查 if (env_get("env_active")) { if (strcmp(env_get("env_active"), "backup") == 0) { env_sync(); // 确保备份分区最新 } } } ``` ### 验证与测试流程 1. **分区状态检查**: ```bash # U-Boot命令行 env_check # 预期输出示例 Environment partitions health check: env @0x01200000: VALID env_backup @0x01300000: VALID ``` 2. **模拟分区故障**: ```bash # 在U-Boot中标记主分区为坏块 nand markbad 0x1200000 # 重启后验证自动切换 reset env_check # 应显示主分区READ ERROR,使用备份分区 ``` 3. **分区同步测试**: ```bash # 修改环境变量 setenv test_value sync_test # 手动同步分区 env_sync # 比较分区内容 nand read 0x82000000 env 0x100000 nand read 0x83000000 env_backup 0x100000 cmp.b 0x82000000 0x83000000 0x100000 ``` ### 关键优势与特点 1. **分区独立性**: - 保持envenv_backup两个独立分区 - 分区大小可独立配置 - 分区名称显示在诊断信息中 2. **增强的可靠性**: - 写入顺序:先备份分区再主分区 - 自动坏块检测和标记 - 后台定期同步检查 3. **诊断工具**: - 新增env_check命令 - 详细的启动日志 - 自动健康监控 4. **无缝集成**: - 兼容现有分区表定义 - 保持原有分区偏移 - 最小化对其他分区的影响
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值