深入解析 CI Detector:用 PHP 统一识别 CI 环境与构建信息(以 Rector 项目为例)
2026/9/15 10:07:29 网站建设 项目流程

深入解析 CI Detector:用 PHP 统一识别 CI 环境与构建信息(以 Rector 项目为例)

【免费下载链接】rectorInstant Upgrades and Automated Refactoring of any PHP 5.3+ code项目地址: https://gitcode.com/GitHub_Trending/re/rector

本篇技术指南围绕 rector 仓库中随附的vendor/ondram/ci-detector/README.md展开,系统讲解 ondram/ci-detector 这一 PHP 库如何在任意 CI 服务器上统一检测持续集成环境、读取当前构建的提交、分支、仓库与 PR 信息。读完本文,你将掌握该库的安装接入、核心 API 用法、17 种 CI 服务器的适配原理与能力矩阵,并能在自己的 CLI 工具(如 Rector 这类自动化重构工具)中复用同一套检测逻辑。

一、CI Detector 是什么:解决什么问题

ondram/ci-detector是一个 PHP 库,用于检测当前脚本是否运行在持续集成(CI)环境中,并读取当前构建的相关信息。它的核心价值在于:不同的 CI 服务器会向构建环境注入命名完全不同的环境变量,而该库通过为每种 CI 提供适配器(Adapter),把这些差异统一收敛为一套稳定的 PHP API,从而让你的脚本和 CLI 工具能够跨多种构建环境可移植。

在 rector 仓库中,该库被安装在 vendor/ondram/ci-detector 目录下(命名空间前缀RectorPrefix202609\OndraM\CiDetector),并被真实用于 Rector 控制台的输出适配——这正是一个"CI 检测改变程序行为"的典型实例,后面会展开说明。

典型使用场景

按照原 README 的说明,该库在以下两类场景中最有价值:

  1. 识别自动化环境,改变程序行为:当 CLI 脚本/工具运行在 CI 服务器上时,它可以隐藏仅对人类有意义的信息,例如进度条(progress bar);
  2. 读取当前构建信息用于记录与发布:获取构建 ID、git commit、分支等,写入日志、发布到 Slack 等外部渠道。

二、工作原理:环境变量驱动的探测机制

原 README 的 "How" 一节指出:检测完全基于各 CI 服务器注入构建环境的环境变量。由于不同 CI 的环境变量命名各异,库内置了对众多 CI 服务器的适配器来消化这些差异。

从源码可以更清晰地印证这一设计。核心类是 CiDetector.php,它维护了一份 CI 适配器类列表:

protected function getCiServers(): array { return [Ci\AppVeyor::class, Ci\AwsCodeBuild::class, Ci\AzurePipelines::class, Ci\Bamboo::class, Ci\BitbucketPipelines::class, Ci\Buddy::class, Ci\Circle::class, Ci\Codeship::class, Ci\Continuousphp::class, Ci\Drone::class, Ci\GitHubActions::class, Ci\GitLab::class, Ci\Jenkins::class, Ci\SourceHut::class, Ci\TeamCity::class, Ci\Travis::class, Ci\Wercker::class]; }

检测流程detectCurrentCiServer()按顺序遍历所有适配器类,依次调用每个类的静态方法isDetected(Env $env),第一个返回true的适配器即为当前 CI:

foreach ($ciServers as $ciClass) { $callback = [$ciClass, 'isDetected']; if (is_callable($callback) && $callback($this->environment)) { return new $ciClass($this->environment); } } return null;

每个适配器的探测逻辑都非常直白——检查各自标志性环境变量是否存在。例如:

  • GitHubActions.php 检查GITHUB_ACTIONS
  • GitLab.php 检查GITLAB_CI
  • AwsCodeBuild.php 检查CODEBUILD_CI

环境变量访问统一由 Env.php 封装(基于 PHP 的getenv()),方便测试时注入模拟环境。

双入口:isCiDetected() 与 detect()

CiDetector对外提供两个互补的入口方法:

  • isCiDetected(): bool—— 返回当前是否处于某个被识别的 CI 环境,不抛异常
  • detect(): CiInterface—— 返回实现了CiInterface的适配器实例;若未检测到任何 CI,则抛出CiNotDetectedException(见 CiNotDetectedException.php)。

推荐的稳健用法是"先判断、后检测",这也是原 README 示例代码采用的模式(见下文)。

三、支持的 CI 服务器

根据原 README,当前库能够识别以下 17 种 CI 服务器:

CI 服务器CiDetector常量
AppVeyorCI_APPVEYOR
AWS CodeBuildCI_AWS_CODEBUILD
Azure DevOps PipelinesCI_AZURE_PIPELINES
BambooCI_BAMBOO
Bitbucket PipelinesCI_BITBUCKET_PIPELINES
BuddyCI_BUDDY
CircleCICI_CIRCLE
CodeshipCI_CODESHIP
continuousphpCI_CONTINUOUSPHP
droneCI_DRONE
GitHub ActionsCI_GITHUB_ACTIONS
GitLabCI_GITLAB
JenkinsCI_JENKINS
SourceHutCI_SOURCEHUT
TeamCityCI_TEAMCITY
Travis CICI_TRAVIS
WerckerCI_WERCKER(已标记 deprecated,将在下一个大版本移除)

上述常量均定义于 CiDetector.php,每个常量对应 src/Ci 目录下的一个适配器类(如GitHubActionsGitLabAwsCodeBuild等)。

四、安装方式

该库通过 Composer 安装(composer.json 显示其要求 PHP^7.4 || ^8.0):

composer require ondram/ci-detector

作为对比,rector 仓库是在自己的构建流程中通过 Composer 将ondram/ci-detector引入vendor/目录并完成前缀化(命名空间被改写为RectorPrefix202609\OndraM\CiDetector),因此任何 PHP 项目均可采用同样的方式接入。

五、完整示例:如何在你的脚本中检测 CI

原 README 给出了一个可直接运行的完整示例。核心流程是:先调用isCiDetected()确认处于 CI 环境,再调用detect()获得 CI 适配器实例,随后按需读取构建属性:

<?php $ciDetector = new \OndraM\CiDetector\CiDetector(); if ($ciDetector->isCiDetected()) { // 确保当前处于 CI 环境 echo 'You are running this script on CI server!'; $ci = $ciDetector->detect(); // 返回实现 CiInterface 的实例,否则抛出 CiNotDetectedException // 在 GitHub Actions 中运行时的示例输出: echo $ci->getCiName(); // "GitHub Actions" echo $ci->getBuildNumber(); // "33" echo $ci->getBranch(); // "feature/foo-bar",未检测到则为空字符串 // 针对 Pull Request 的条件逻辑: if ($ci->isPullRequest()->yes()) { echo 'This is pull request. The target branch is: '; echo $ci->getTargetBranch(); // "main" } // 针对特定 CI 服务器的条件逻辑: if ($ci->getCiName() === OndraM\CiDetector\CiDetector::CI_GITHUB_ACTIONS) { echo 'This is being built on GitHub Actions'; } // 以人类可读形式输出全部检测值: print_r($ci->describe()); // Array // ( // [ci-name] => GitHub Actions // [build-number] => 33 // [build-url] => https://github.com/OndraM/ci-detector/commit/abcd/checks // [commit] => fad3f7bdbf3515d1e9285b8aa80feeff74507bde // [branch] => feature/foo-bar // [target-branch] => main // [repository-name] => OndraM/ci-detector // [repository-url] => https://github.com/OndraM/ci-detector // [is-pull-request] => Yes // ) } else { echo 'This script is not run on CI server'; }

注意示例中isPullRequest()返回的是TrinaryLogic实例,需要调用->yes()来获得布尔结果(其意义详见下文第六节)。

六、API 方法参考:CiInterface 全量方法

detect()返回的对象实现了 CiInterface,统一抽象了各 CI 的差异。以下是原 README 提供的完整方法说明表:

方法示例值说明
getCiName()GitHub ActionsCI 服务器名称,取值来自CiDetector::CI_*常量
getBuildNumber()33本次构建的编号。通常是人类可读的递增数字序列(1、2、3…),每次该 job 在 CI 上运行都会增加;部分 CI 不提供这种简单序列,而使用字母数字哈希
getBuildUrl()https://github.com/OndraM/ci-detector/commit/abcd/checks或空字符串本次构建可被查看的 URL,无法确定时返回空字符串
getCommit()b9173d94(...)正在构建的 git(或其他 VCS)提交哈希
getBranch()my-feature或空字符串正在构建的分支名,无法确定时返回空字符串;如需 PR 目标分支请用getTargetBranch()
getTargetBranch()main或空字符串Pull Request 的目标分支(即 PR 合并指向的 base 分支),无法确定时返回空字符串
getRepositoryName()OndraM/ci-detector或空字符串正在构建的仓库名,通常形如user/repository
getRepositoryUrl()https://github.com/OndraM/ci-detector或空字符串仓库地址,可能是 HTTP URL,也可能是ssh://git@bitbucket.org/OndraM/ci-detector这类 SSH URL;无法确定时返回空字符串
isPullRequest()TrinaryLogic实例判断当前构建是否来自 pull/merge request。值为 true/false/maybe(见下方三值逻辑说明),用法如if ($ci->isPullRequest()->yes())
describe()[...](数组)以人类可读的键值对形式返回全部检测属性

describe() 的实现细节

describe()在抽象基类 AbstractCi.php 中统一实现,将九个属性聚合成一个关联数组,其中is-pull-request一项调用TrinaryLogic::describe()输出Yes/No/Maybe字符串——这与 README 示例输出中的[is-pull-request] => Yes完全对应。

三值逻辑 TrinaryLogic 详解

由于并非所有 CI 都能可靠地报告"是否 PR 构建",isPullRequest()没有简单返回布尔值,而是返回 TrinaryLogic.php 实例,支持三种取值(借鉴自 PHPStan 的同类实现):

  • YES(确定是)yes()返回true
  • NO(确定否)no()返回true
  • MAYBE(无法确定)maybe()返回true

例如在 GitHubActions.php 中,通过GITHUB_EVENT_NAME === 'pull_request'精确判断;而 Jenkins、TeamCity 等 CI 则直接返回createMaybe(),表示无法确定。日常使用中建议像 README 示例那样只判断->yes(),把"不确定"当作"否"处理。

七、各 CI 服务器能力矩阵

原 README 明确指出:大多数 CI 都支持(✔)检测全部信息,但部分 CI 不暴露所需环境变量,导致某些字段不可用(❌)。完整能力矩阵如下(列依次为isPullRequestgetBranchgetTargetBranchgetRepositoryNamegetRepositoryUrlgetBuildUrl):

CI 服务器CiDetector常量isPullRequestgetBranchgetTargetBranchgetRepositoryNamegetRepositoryUrlgetBuildUrl
AppVeyorCI_APPVEYOR
AWS CodeBuildCI_AWS_CODEBUILD
Azure PipelinesCI_AZURE_PIPELINES
BambooCI_BAMBOO
Bitbucket PipelinesCI_BITBUCKET_PIPELINES
BuddyCI_BUDDY
CircleCICI_CIRCLE
CodeshipCI_CODESHIP
continuousphpCI_CONTINUOUSPHP
droneCI_DRONE
GitHub ActionsCI_GITHUB_ACTIONS
GitLabCI_GITLAB
JenkinsCI_JENKINS
SourceHutCI_SOURCEHUT
TeamCityCI_TEAMCITY
Travis CICI_TRAVIS
WerckerCI_WERCKER

开发建议:矩阵中的 ❌ 意味着对应方法会返回空字符串或Maybe。如果你的程序依赖某个字段,务必做好空值兜底;从源码看,AWS CodeBuild 的getTargetBranch()getRepositoryName()直接返回''(见 AwsCodeBuild.php),正是"不支持即返回空串"的典型实现。

以 GitHub Actions 为例看属性映射

GitHubActions.php 展示了环境变量与统一 API 的映射关系,可作为理解其他适配器的范本:

统一方法读取的环境变量说明
isDetectedGITHUB_ACTIONS存在即判定为 GitHub Actions
isPullRequestGITHUB_EVENT_NAME等于pull_request时为 Yes
getBuildNumberGITHUB_RUN_NUMBER递增的运行编号
getBuildUrlGITHUB_REPOSITORY+GITHUB_SHA拼接为…/commit/{sha}/checks
getCommitGITHUB_SHA提交哈希
getBranchGITHUB_HEAD_REF/GITHUB_REFPR 构建优先用 head ref,否则去掉refs/heads/前缀
getTargetBranchGITHUB_BASE_REFPR 的目标分支
getRepositoryNameGITHUB_REPOSITORYuser/repository
getRepositoryUrlGITHUB_REPOSITORY拼接为https://github.com/{repo}

八、真实落地案例:Rector 如何用 CI 检测优化进度条输出

原 README 提到的"根据是否处于 CI 隐藏进度条"场景,在 rector 仓库中有真实的工程落地。查看 src/Console/Style/RectorStyle.php 中createProgressBar()的实现:

public function createProgressBar(int $max = 0): ProgressBar { $progressBar = parent::createProgressBar($max); $isCiDetected = $this->isCiDetected(); $progressBar->setOverwrite(!$isCiDetected); if ($isCiDetected) { $progressBar->minSecondsBetweenRedraws(15); $progressBar->maxSecondsBetweenRedraws(30); } elseif (\DIRECTORY_SEPARATOR === '\\') { // windows $progressBar->minSecondsBetweenRedraws(0.5); $progressBar->maxSecondsBetweenRedraws(2); } else { // *nix $progressBar->minSecondsBetweenRedraws(0.1); $progressBar->maxSecondsBetweenRedraws(0.5); } ... }

其中isCiDetected()私有方法正是通过new CiDetector()isCiDetected()完成的(见 RectorStyle.php),并做了惰性缓存避免重复探测:

private function isCiDetected(): bool { if ($this->isCiDetected === null) { $ciDetector = new CiDetector(); $this->isCiDetected = $ciDetector->isCiDetected(); } return $this->isCiDetected; }

效果是:在 CI 上运行时,进度条不覆盖重绘(setOverwrite(false)),并将重绘间隔拉长到 15~30 秒,从而避免在日志系统中产生大量噪声;在本地终端则保持 0.1~0.5 秒的快速刷新。这正是"CI 环境下隐藏/降级面向人类的信息"这一设计理念的权威示例——CLI 工具作者可以直接借鉴该模式。

九、测试与质量保障

原 README 提供两条质量命令(均在库自身目录执行):

# 检查代码风格、静态分析并运行单元测试 composer all # 自动修复代码风格违规 composer fix

这两条命令在库的 composer.json 中有明确定义:all依次执行lint(并行语法检查 + composer 校验)、analyze(ECS 代码风格 + PHPStan 静态分析)、test(PHPUnit);fix则执行composer normalize与 ECS 自动修复。该库的开发依赖覆盖 PHPStan、PHPUnit、ECS 等,且autoload-dev将测试目录映射到OndraM\CiDetector\Ci命名空间,方便以"伪适配器"方式编写单元测试。

十、延伸信息

  • 独立 CLI 版本:若不想在 PHP 项目中写代码,原 README 提到作者维护了独立的ci-detector-standalone仓库,可下载为带命令行界面的 PHAR 文件直接使用(该独立仓库不在本仓库内,需要时请自行查阅)。
  • 变更记录与版本策略:最新变更见库内 CHANGELOG.md,项目遵循 Semantic Versioning。
  • 其他语言的同类库:原 README 列举了 Go(ci-info)、JavaScript/Node.js(ci-info)、Python(ci-info)、Rust(ci_info)等语言的相似实现,如果你在多语言栈中工作,可以对照采用相同的"环境变量探测 + 统一 API"思路。
  • 仓库内进一步探索:全部 17 个适配器实现位于 vendor/ondram/ci-detector/src/Ci 目录;检测入口与常量见 CiDetector.php;接口契约见 CiInterface.php;Rector 的消费实例见 src/Console/Style/RectorStyle.php。

结语

CI Detector 的价值不在于复杂的算法,而在于它把"各家 CI 环境变量命名差异"这一真实的工程痛点,收敛成了 10 个稳定、可测试的 PHP 方法。无论是想让 CLI 工具在自动化环境中自动隐藏进度条(如 Rector 的做法),还是想把构建号、提交哈希、分支名统一采集后上报日志与通知,你都可以通过composer require ondram/ci-detector快速获得一份跨 17 种 CI 的可移植能力。在接入时请牢记两点:先isCiDetected()detect()避免异常,并对能力矩阵中标 ❌ 的字段做好空值兜底。

【免费下载链接】rectorInstant Upgrades and Automated Refactoring of any PHP 5.3+ code项目地址: https://gitcode.com/GitHub_Trending/re/rector

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询