ThinkPHP 5.1 老项目 + Composer 2:think-installer 装不上
维护一个老 ThinkPHP 5.1 项目,composer install 直接崩: Problem 1 - topthink/think-installer is locked to version v2.0.0 and an update of this package was not requested. - topthink/think-installer v2.0.0 requires composer-plugin-api ^1.0 -> found composer-plugin-api[2.9.0] but it does not match the constraint. Problem 2 - topthink/framework v5.1.39 requires topthink/think-installer 2.* ...顺带还有: The "https://packagist.phpcomposer.com/packages.json" file could not be downloaded (404)两个问题:Composer 版本不兼容 + 镜像源死了。 症状 1:think-installer 只支持 Composer 1 topthink/think-installer v2.0.0 明确要 composer-plugin-api ^1.0,也就是 Composer 1.x 的插件接口。你机器上是 Composer 2.x,插件接口版本变成 2.x,直接不兼容。 TP5.1 上线那会儿(2018)Composer 2 还没出,这问题在当时不存在,遗留下来才成了坑。 解法:降回 Composer 1 推荐、最省事、也最不折腾业务代码。 方法 A:Composer 自己降版本 composer self-update --1Composer 会在原地切到最新的 1.x(一般是 1.10.27)。想指定: composer self-update 1.10.27方法 B:装一个独立的 Composer 1 不想动全局 Composer 的话,单独下一份: curl -sS https://getcomposer.org/installer | php -- --1 mv composer.phar /usr/local/bin/composer1 composer1 install老项目用 composer1,新项目继续用 composer。 症状 2:packagist.phpcomposer.com 已凉 老教程里推荐的 packagist.phpcomposer.com 这个镜像已经关服多年,仍然响应 404。检查 composer.json: cat composer.json | grep -A 3 repositories看到: "repositories": { "packagist": { "type": "composer", "url": "https://packagist.phpcomposer.com" } }改成官方源或者阿里镜像: # 官方 composer config -g repo.packagist composer https://repo.packagist.org# 阿里云 composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/# 清华(有时更快) composer config -g repo.packagist composer https://mirrors.tuna.tsinghua.edu.cn/composer/-g 是全局,改单个项目就去掉。 也可以直接改 composer.json 里的 repositories 字段,同样效果。 完整恢复流程 # 1. Composer 降 1 composer self-update --1# 2. 换镜像 composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/# 3. 清缓存 composer clear-cache# 4. 重装依赖 rm -rf vendor composer.lock composer install90% 的老 TP5.1 项目这一套下来就恢复了。 什么时候该升级项目 如果这不是紧急救火,考虑:升到 ThinkPHP 6 或 8,从 5.1 迁移文档官方有 或者只把 topthink/think-installer 换成 topthink/framework 的现代版本,去掉这个已废弃的插件但生产环境急救时先别动这些——先降 Composer 让站起来,稳定了再排期改造。 一句话总结 think-installer 只兼容 Composer 1,最直接的解法是 composer self-update --1。顺手把死了的 phpcomposer.com 镜像换成阿里或官方源。
写个 Bash 自动 git pull:三种姿势和 cron 里的坑
服务器上想自动拉最新代码,写个脚本挂 cron 跑。看着就两行,实际上有几个必吃的坑。 最基础写法 #!/bin/bash cd /www/wwwroot/yourproject || exit 1 git pull|| exit 1:cd 失败就退出,不然当前目录是家目录,git pull 会执行在错误的仓库 保存为 pull.sh chmod +x pull.sh 执行 ./pull.sh更稳的写法:不依赖 cd git -C 可以直接指定仓库路径,省掉 cd 的失败风险: #!/bin/bash git -C /www/wwwroot/yourproject pull origin main同一个脚本要处理多个仓库时特别顺手。 挂 cron 时会踩的坑 */5 * * * * /www/wwwroot/scripts/pull.sh >> /var/log/pull.log 2>&1看着没问题,实际经常挂。原因: 1. SSH 密钥没加载 用户手动跑时有 ssh-agent。cron 里没有,git@github.com 直接拒绝。解法:走 HTTPS + PAT,别用 SSH 或在脚本里显式指定密钥:export GIT_SSH_COMMAND="ssh -i /root/.ssh/deploy_key -o StrictHostKeyChecking=no" git -C /www/wwwroot/yourproject pull origin main2. PATH 太窄 cron 的 PATH 只有 /usr/bin:/bin,找不到自定义安装的 git。crontab 里手动补: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin */5 * * * * /www/wwwroot/scripts/pull.sh3. 分支不明确 裸 git pull 会走当前分支和默认 remote,可能拉到不该拉的分支。永远显式: git -C "$REPO" pull origin main处理本地修改的完整版 生产脚本上还得防几件事: #!/bin/bash set -eREPO=/www/wwwroot/yourproject BRANCH=maincd "$REPO"# 1. 如果本地有修改,暂存(避免 pull 失败) if [[ -n $(git status --porcelain) ]]; then echo "[$(date)] 本地有修改,先 stash" git stash push -m "auto-stash $(date +%s)" fi# 2. 拉最新 git fetch origin "$BRANCH" git reset --hard "origin/$BRANCH" # 直接对齐远程,别 merge# 3. 清空 stash(不还原,防止和拉下来的冲突) git stash clear注意 git reset --hard 会覆盖本地任何修改。这是刻意的——线上机器上不该有手改,任何修改都视为"意外"直接抹掉。要保守就用 git pull --ff-only。 大小写敏感的坑 服务器 Linux 大小写敏感,Windows / macOS 默认不敏感。仓库里同时存在: RiderAuditController.php RiderauditController.phpLinux 上拉下来会真的多出两个文件,Windows 上只能存一个,pull 立刻挂。 解法:清理仓库中的重名文件,用 git rm 保留一个: git rm --cached RiderauditController.php git commit -m "fix: 大小写重复" git push或者临时关掉大小写敏感(治标): git config core.ignorecase true一句话总结 git -C REPO pull origin BRANCH 是最省事的一行。挂 cron 要额外交代 PATH、SSH 密钥、分支名,生产脚本再套一层 reset --hard 防脏树。
ThinkPHP 5.1 / PhalApi 老项目 Composer 兼容性修复:Composer 2 报错与 composer.lock 镜像污染
部署老 PHP 项目时,composer install 报错往往是 Composer 版本与框架不兼容,或者 lock 文件里记录了失效的镜像地址。 基本部署脚本 #!/bin/bashcd /www/wwwroot/project || exit 1 git pull origin main不依赖 cd 的写法: git -C /www/wwwroot/project pull origin mainComposer 2 与 ThinkPHP 5.1 不兼容 典型错误: topthink/think-installer v2.0.0 requires composer-plugin-api ^1.0 found composer-plugin-api[2.9.0]原因:think-installer v2.0.0 只支持 Composer 1.x,而 Composer 2.x 内置的 composer-plugin-api 是 2.9.0。 确认版本: composer -V降级到 Composer 1(推荐) composer self-update --1 # 或指定版本 composer self-update 1.10.27然后: composer install如果项目目录里有 composer.phar(旧版本): php composer.phar install注意:Packagist 已于 2025-09-01 停止支持 Composer 1 对于无 composer.lock 的老项目,Composer 1 的 composer update 无法重新解析依赖,因为 Packagist 已不提供 Composer 1 格式的包数据。 如果 lock 文件还在,composer install 仍然可以按 lock 文件安装。 dev-master 是什么 "phalapi/task": "dev-master"dev-master 表示直接跟踪 Git 仓库的 master 分支最新提交,不安装正式 release 版本。需要在 composer.json 里声明: "minimum-stability": "dev"才能安装。风险:每次 composer update 拉的内容不固定,接口可能变动。GitHub 默认分支改名后,新项目改用 dev-main。 composer.lock 里的阿里云镜像污染 如果 lock 文件在其他人的阿里云镜像环境下生成,里面会固化 dist URL: "dist": { "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%" }执行 composer install 时 Composer 严格按照 lock 安装,会尝试从这个地址下载,触发: Authentication required (mirrors.aliyun.com): Username:检测 grep -n "mirrors.aliyun.com" composer.lock修复方案 方案 1:--prefer-source(推荐快速处理) php composer.phar install --prefer-source优先 git clone 源码,绕过 dist zip 下载,大多数情况能直接解决。 方案 2:重新生成 lock rm composer.lock rm -rf vendor php composer.phar clear-cache php composer.phar update重新生成后验证: grep "mirrors.aliyun.com" composer.lock应该没有任何结果。 方案 3:从仓库恢复 lock 如果 lock 是仓库管理的: git checkout composer.lock然后再用 --prefer-source 安装。 PhalApi 2.x 依赖安装成功的标志 Generating autoload files这行出现且后面没有 RuntimeException / Installation failed,说明安装成功。然后验证: php -r "require 'vendor/autoload.php'; echo 'OK';"PHP 版本建议 ThinkPHP 5.1 / ThinkCMF 5.1 / PhalApi 2.x 老项目:PHP 7.2 ~ 7.4:最稳定 PHP 8.0+:可能遇到 each()、create_function() 等已删除函数报错
Git 大小写文件名冲突:Windows/macOS 改名后出现重复文件
在 Windows 和默认 macOS 文件系统(大小写不敏感)上改文件名大小写,Git 不会把它识别为 rename,而是把两个文件都保留在仓库里。 症状 执行 git pull 时出现: warning: the following paths have collided (e.g. case-sensitive paths on a case-insensitive filesystem) and only one from the same colliding group is in the working tree: 'app/substation/controller/RiderAuditController.php' 'app/substation/controller/RiderauditController.php'原因 Git 内部是大小写敏感的,仓库里可以同时存在两个文件: RiderAuditController.php RiderauditController.php但 Windows(NTFS)和 macOS(APFS 默认)认为它们是同一个文件,检出时只能保留一个,发生碰撞。 通常是有人在 Windows 上直接改了文件名大小写,Git 把旧文件和新文件都提交进去了。 确认仓库状态 在任意环境执行: git ls-files | grep -i rideraudit如果输出两行就说明仓库里确实存在两个文件: app/substation/controller/RiderAuditController.php app/substation/controller/RiderauditController.php解决步骤(必须在 Linux 或 WSL2 上操作) Windows/macOS 文件系统无法同时持有两个大小写不同的文件,操作只能在大小写敏感环境中完成。 方案一:Linux 服务器(推荐) # SSH 到 Linux 服务器,确认两文件内容 diff app/substation/controller/RiderAuditController.php \ app/substation/controller/RiderauditController.php# 删除要废弃的文件(以删除大写版为例) git rm app/substation/controller/RiderAuditController.php git rm phalapi/src/rider/Model/RiderAudit.phpgit commit -m "fix: remove duplicate case-sensitive files" git push方案二:WSL2(Windows 用户) wsl cd /mnt/c/projects/your-repogit rm app/substation/controller/RiderAuditController.php git commit -m "fix: remove duplicate case-sensitive files" git pushpush 之后,Windows/macOS 成员再 git pull 就不会再有冲突。 如果两个文件内容不同 先比较差异: git diff --no-index \ app/substation/controller/RiderAuditController.php \ app/substation/controller/RiderauditController.php查看哪个是最新版本再决定保留哪个。也可以用 git log 看提交历史: git log --follow -- app/substation/controller/RiderAuditController.php git log --follow -- app/substation/controller/RiderauditController.php不推荐的绕过方式macOS 创建大小写敏感卷(APFS Case-sensitive) Windows 开启目录大小写敏感:fsutil file setCaseSensitiveInfo . enable这两种方式只解决当前机器的问题,团队里其他 Windows/macOS 开发者继续会出问题。根本解决办法是清理远程仓库中的重复文件。 PHP 框架的额外风险 PHP 的 PSR-4 自动加载依赖文件名映射,如果保留了小写文件名但代码里用的是大写类名: new Model_RiderAudit(); // 加载 RiderAudit.php在 Linux 上 autoloader 找不到文件会报 Class not found。 解决后建议全局搜索: grep -R "RiderAudit\|Rideraudit" . --include="*.php"确认所有引用和文件名一致。
git pull --rebase 报 unstaged changes 的四种处理方法
报错场景 git pull --rebase输出: error: cannot pull with rebase: You have unstaged changes. error: please commit or stash them.这是 Git 在执行 rebase 前的保护机制:工作区有未提交修改时,rebase 可能覆盖这些改动,所以拒绝继续。 先确认修改范围: git status方案一:暂存后拉取再恢复(最常用) 保留改动,暂时藏起来: git stash git pull --rebase git stash popstash pop 会把改动重新应用到工作区。如果有冲突,手动解决后 git stash drop 清掉暂存记录。 查看所有 stash: git stash list # stash@{0}: WIP on main: abc1234 last commit message恢复指定 stash(保留记录): git stash apply stash@{0}方案二:提交后再 rebase 如果改动已经完整,直接提交: git add . git commit -m "WIP: 临时提交" git pull --rebaserebase 会把这次提交放在远端最新提交之后,之后可以 git commit --amend 修改提交信息或 git rebase -i HEAD~2 整理提交记录。 方案三:丢弃本地修改 如果这些修改确定不要了: git reset --hard HEAD git pull --rebase如果还有未跟踪的文件也想清掉: git clean -fdreset --hard 和 clean -fd 都是不可逆操作,请确认后执行。方案四:开启自动 stash(推荐长期配置) 新版 Git 支持 --autostash 标志,在 rebase 前自动暂存、完成后自动恢复: git pull --rebase --autostash一次性使用;也可以永久开启: git config --global rebase.autoStash true开启后 git pull --rebase 自动执行以下流程: stash → pull --rebase → stash pop场景速查情况 推荐方案改动要保留,不想提交 stash → pull → stash pop改动完整,可以提交 commit → pull --rebase改动不需要了 reset --hard → pull日常频繁拉取 rebase.autoStash = true常见追问 stash pop 有冲突怎么办? 手动解决冲突后: git add <冲突文件> git stash drop # 清掉 stash 记录为什么不直接 git pull 而是 --rebase? git pull 默认是 fetch + merge,会产生 merge commit;--rebase 把本地提交移到远端之后,保持线性历史,更干净。 git stash 会暂存未跟踪文件吗? 默认不会。加 -u 参数: git stash -u # 包含 untracked files git stash -a # 包含 untracked + ignored files
