你们中许多人可能遇到过这样的情况,即您使用的库或框架中存在错误或没有必要的功能。 假设您不太懒惰并形成了请求请求。 但是他们不会立即接受它,并且该产品的下一次发布通常可能会在一年内完成。

如果您迫切需要对产品进行更正,该怎么办? 显而易见的解决方案是使用库或框架的分支。 但是,并不是所有事情都简单。 使用继承来覆盖需要更改的功能并不总是可能的,并且通常需要进行重大更改。 可以修补依赖项的Composer插件可助您一臂之力。
在本文中,我将更多地讨论为什么fork会带来不便,并且还将考虑Composer的两个用于依赖项修补的插件:它们之间的差异,如何使用它们以及它们的优点是什么。 如果您遇到类似的问题或只是想知道,欢迎与我们联系。
通过示例最方便地考虑该问题。 假设我们要更改PHP Code Coverage库中的某些内容,该库在PHPUnit测试框架中用于测量测试的代码覆盖率。 假设我们要在7.0.8版( myFix.patch
文件)中修复类似的问题:
diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 2c92ae2..514171e 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -190,6 +190,7 @@ public function filter(): Filter */ public function getData(bool $raw = false): array { + // for example some changes here if (!$raw && $this->addUncoveredFilesFromWhitelist) { $this->addUncoveredFilesFromWhitelist(); }
让我们创建示例库。 让它成为php-composer-patches-example 。 这里的细节不是很重要,但是如果您决定看一下库是什么,我会将控制台输出置于扰流器下方。
隐藏文字 $ git clone git@github.com:mougrim/php-composer-patches-example.git «php-composer-patches-example»… remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 : 100% (3/3), . $ cd php-composer-patches-example/ $ $ composer.phar init --name=mougrim/php-composer-patches-example --description="It's an example for article with using forks and patches for changing dependencies" --author='Mougrim <rinat@mougrim.ru>' --type=library --require='phpunit/phpunit:^8.4.2' --license=MIT --homepage='https://github.com/mougrim/php-composer-patches-example' Welcome to the Composer config generator This command will guide you through creating your composer.json config. Package name (<vendor>/<name>) [mougrim/php-composer-patches-example]: Description [It's an example for article with using forks and patches for changing dependencies]: Author [Mougrim <rinat@mougrim.ru>, n to skip]: Minimum Stability []: Package Type (eg library, project, metapackage, composer-plugin) [library]: License [MIT]: Define your dependencies. Would you like to define your dev dependencies (require-dev) interactively [yes]? no { "name": "mougrim/php-composer-patches-example", "description": "It's an example for article with using forks and patches for changing dependencies", "type": "library", "homepage": "https://github.com/mougrim/php-composer-patches-example", "require": { "phpunit/phpunit": "^8.4.2" }, "license": "MIT", "authors": [ { "name": "Mougrim", "email": "rinat@mougrim.ru" } ] } Do you confirm generation [yes]? yes Would you like to install dependencies now [yes]? yes Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 29 installs, 0 updates, 0 removals - Installing sebastian/version (2.0.1): Loading from cache - Installing sebastian/type (1.1.3): Loading from cache - Installing sebastian/resource-operations (2.0.1): Loading from cache - Installing sebastian/recursion-context (3.0.0): Loading from cache - Installing sebastian/object-reflector (1.1.1): Loading from cache - Installing sebastian/object-enumerator (3.0.3): Loading from cache - Installing sebastian/global-state (3.0.0): Loading from cache - Installing sebastian/exporter (3.1.2): Loading from cache - Installing sebastian/environment (4.2.2): Loading from cache - Installing sebastian/diff (3.0.2): Loading from cache - Installing sebastian/comparator (3.0.2): Loading from cache - Installing phpunit/php-timer (2.1.2): Loading from cache - Installing phpunit/php-text-template (1.2.1): Loading from cache - Installing phpunit/php-file-iterator (2.0.2): Loading from cache - Installing theseer/tokenizer (1.1.3): Loading from cache - Installing sebastian/code-unit-reverse-lookup (1.0.1): Loading from cache - Installing phpunit/php-token-stream (3.1.1): Loading from cache - Installing phpunit/php-code-coverage (7.0.8): Loading from cache - Installing doctrine/instantiator (1.2.0): Loading from cache - Installing symfony/polyfill-ctype (v1.12.0): Loading from cache - Installing webmozart/assert (1.5.0): Loading from cache - Installing phpdocumentor/reflection-common (2.0.0): Loading from cache - Installing phpdocumentor/type-resolver (1.0.1): Loading from cache - Installing phpdocumentor/reflection-docblock (4.3.2): Loading from cache - Installing phpspec/prophecy (1.9.0): Loading from cache - Installing phar-io/version (2.0.1): Loading from cache - Installing phar-io/manifest (1.0.3): Loading from cache - Installing myclabs/deep-copy (1.9.3): Loading from cache - Installing phpunit/phpunit (8.4.2): Loading from cache sebastian/global-state suggests installing ext-uopz (*) phpunit/phpunit suggests installing phpunit/php-invoker (^2.0.0) phpunit/phpunit suggests installing ext-soap (*) Writing lock file Generating autoload files $ $ echo 'vendor/' > .gitignore $ echo 'composer.lock' >> .gitignore $ git add .gitignore composer.json $ $ git commit --gpg-sign --message='Init composer' [master ce800ae] Init composer 2 files changed, 18 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json $ git push origin master : 4, . Delta compression using up to 4 threads. : 100% (3/3), . : 100% (4/4), 1.21 KiB | 1.21 MiB/s, . Total 4 (delta 0), reused 0 (delta 0) To github.com:mougrim/php-composer-patches-example.git f31c342..ce800ae master -> master
上瘾的叉子怎么了
让我们看看fork依赖是如何发生的。 让我们尝试分叉PHP代码覆盖率。
- 我们转到GitHub上的PHP代码覆盖率页面 。
- 按下前叉按钮
(注意:您将拥有叉子,将mougrim替换为您的用户名)。 - 克隆叉子:
cd ../ git clone git@github.com:mougrim/php-code-coverage.git cd php-code-coverage
- 转到我们要修补的版本:
git checkout 7.0.8
- 创建一个分支进行修复:
git checkout -b 7.0.8-myFix
- 我们进行必要的更改,提交,推送:
git apply ../myFix.patch git add src/CodeCoverage.php git commit --gpg-sign --message='My fix' git push -u origin 7.0.8-myFix
- 在我们的库的composer.json中添加fork作为存储库(这是必需的,因此在连接
phpunit/php-code-coverage
软件包时,不是连接原始软件包,而是连接fork):
cd ../php-composer-patches-example git checkout -b useFork composer.phar config repositories.phpunit/php-code-coverage vcs https://github.com/mougrim/php-code-coverage.git
- 将依赖项的版本更改为早午餐:
composer.phar require phpunit/php-code-coverage 'dev-7.0.8-myFix'
但是实际上它仍然更加复杂:Composer说安装是不可能的,因为phpunit/phpunit
需要phpunit/php-code-coverage
版本^7.0.7
,而我们的项目则需要dev-7.0.8-myFix
:
$ composer.phar require phpunit/php-code-coverage 'dev-7.0.8-myFix' ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - phpunit/phpunit 8.4.2 requires phpunit/php-code-coverage ^7.0.7 -> satisfiable by phpunit/php-code-coverage[7.0.x-dev]. - phpunit/phpunit 8.4.2 requires phpunit/php-code-coverage ^7.0.7 -> satisfiable by phpunit/php-code-coverage[7.0.x-dev]. - phpunit/phpunit 8.4.2 requires phpunit/php-code-coverage ^7.0.7 -> satisfiable by phpunit/php-code-coverage[7.0.x-dev]. - Can only install one of: phpunit/php-code-coverage[7.0.x-dev, dev-7.0.8-myFix]. - Installation request for phpunit/php-code-coverage dev-7.0.8-myFix -> satisfiable by phpunit/php-code-coverage[dev-7.0.8-myFix]. - Installation request for phpunit/phpunit ^8.4.2 -> satisfiable by phpunit/phpunit[8.4.2]. Installation failed, reverting ./composer.json to its original content.
怎么办呢? 有四个选项:
- 除了
phpunit/php-code-coverage
分支外,还分支PHPUnit并为依赖项phpunit/php-code-coverage
编写版本dev-7.0.8-myFix
。 就支持而言,此路径相当复杂,并且越复杂,则依赖phpunit/php-code-coverage
库就越多。 - 连接
phpunit/php-code-coverage
时使用别名 。 但是别名不会从依赖项中提取,这意味着它们将始终需要手动编写。 - 在您的fork中
phpunit/php-code-coverage
,以便7.0.8
标记7.0.8
另一个提交。 至少这不是很明显,但是最大程度地-在Git中,使用标记引用不同远程存储库中具有相同名称的不同提交的标记是不方便的。 - 在您的派生
phpunit/php-code-coverage
使用alpha发行版标签,例如7.0.8-a+myFix
(可能与源库的alpha发行版发生冲突)。
所有选项都有其缺点。 我也尝试使用7.0.8.1
类的标签,但Composer不接受此类标签。
第二种选择和第四种选择似乎邪恶程度较小。 由于它们的动作数量大致相同,因此在本文中,我们将仅考虑其中之一-第四。 创建一个alpha发布标签:
cd ../php-code-coverage git tag 7.0.8-a+myFix git push origin 7.0.8-a+myFix cd ../php-composer-patches-example composer.phar require phpunit/php-code-coverage '7.0.8-a+myFix' git add composer.json git commit --gpg-sign --message='Use fork' git push -u origin useFork
假设我们要在依赖于phpunit/phpunit
的项目中使用我们的库mougrim/php-composer-patches-example
。 在这里,不能没有萨满phpunit/php-code-coverage
,您将不得不再次为phpunit/php-code-coverage
指定存储库https://github.com/mougrim/php-code-coverage.git
,并明确指出对phpunit/php-code-coverage
的依赖性版本7.0.8-a+myFix
(否则安装将不会成功):
cd ../ mkdir php-project cd php-project/ composer.phar require phpunit/phpunit '^8.4.2' composer.phar config repositories.mougrim/php-composer-patches-example vcs https://github.com/mougrim/php-composer-patches-example.git composer.phar config repositories.phpunit/php-code-coverage vcs https://github.com/mougrim/php-code-coverage.git composer.phar require phpunit/php-code-coverage 7.0.8-a+myFix composer.phar require mougrim/php-composer-patches-example dev-useFork
请注意,php-composer-patches-example作为存储库连接,因为该存储库只是一个示例,因此尚未添加到Packagist中。 您的情况下,很可能会跳过此步骤。
总结一下叉子的使用。
这种方法的优点:
这种方法的缺点:
- 如果使用
roave/security-advisories
,则不会看到有关您分叉和修改的依赖项版本包含漏洞的信息; - 当新版本的依赖关系出现时,将不得不重新重复fork的故事;
- 如果要修复依赖项依赖关系(如所考虑的示例中所示),则
dev-*
不适用于该依赖关系,并且您必须使用版本进行萨满化或为冲突的依赖项进行分叉; - 如果有依赖于库的项目,则不必以最明显,最方便的方式在项目中安装库;
- 如果有一些项目依赖于您的库,则对于它们,
phpunit/php-code-coverage
版本将严格固定,这并不总是可以接受的; - 此外,如果上述几点的项目由于其他原因已经分叉了PHP代码覆盖率,那么一切将变得更加复杂。
我认为您已经意识到分叉上瘾不是一个好主意。
使用Cweagans /作曲家补丁
我再次遇到了使用叉子的痛苦和痛苦,我遇到了PHP Digest No. 101中的 cweagans/composer-patches
(顺便说一句, pronskiy有一个有用的博客,我建议订阅)。 这是omposer的插件,它使您可以将补丁应用于依赖项。 阅读说明后,我认为这正是您所需要的。
如何使用cweagans / composer-patches:
- 克隆PHP代码覆盖率:
cd ../ rm -rf php-code-coverage git clone git@github.com:sebastianbergmann/php-code-coverage.git cd php-code-coverage
- 转到我们要修补的版本:
git checkout 7.0.8
- 我们进行必要的更改。
- 创建补丁:
mkdir -p ../php-composer-patches-example/patches/phpunit/php-code-coverage git diff HEAD > ../php-composer-patches-example/patches/phpunit/php-code-coverage/myFix.patch
- 在我们的项目中,我们连接
cweagans/composer-patches
:
cd ../php-composer-patches-example git checkout master composer.phar update git checkout -b cweagansComposerPatches composer.phar require cweagans/composer-patches '^1.6.7'
- 要配置
cweagans/composer-patches
请将以下内容添加到composer.json
(您可以为一个软件包指定多个补丁):
{ "config": { "preferred-install": "source" }, "extra": { "patches": { "phpunit/php-code-coverage": { "My fix description": "patches/phpunit/php-code-coverage/myFix.patch" } }, "enable-patching": true } }
- 更新依赖项:
composer.phar update
- 如果出了点问题,可以在上一条命令的输出中看到,但以防万一,您可以检查一下我们的更改是否已应用:
$ grep example vendor/phpunit/php-code-coverage/src/CodeCoverage.php // for example some changes here
- 提交并推送结果:
git add composer.json patches/phpunit/php-code-coverage/myFix.patch git commit --gpg-sign --message='Use cweagans/composer-patches' git push -u origin cweagansComposerPatches
我们确保在项目中安装库时,补丁也将适用。
创建一个项目:
cd ../ rm -rf php-project mkdir php-project cd php-project composer.phar require phpunit/phpunit '^8.4.2'
composer.json
添加到composer.json
:
{ "extra": { "enable-patching": true } }
安装mougrim/php-composer-patches-example
:
composer.phar config repositories.mougrim/php-composer-patches-example vcs https://github.com/mougrim/php-composer-patches-example.git composer.phar require mougrim/php-composer-patches-example dev-cweagansComposerPatches
似乎在连接软件包时应该尝试应用补丁,但没有。
我们将更新软件包,以便应用补丁,但这不会发生:
$ composer.phar update Removing package phpunit/php-code-coverage so that it can be re-installed and re-patched. - Removing phpunit/php-code-coverage (7.0.8) Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals No patches supplied. Gathering patches for dependencies. This might take a minute. - Installing phpunit/php-code-coverage (7.0.8): Loading from cache - Applying patches for phpunit/php-code-coverage patches/phpunit/php-code-coverage/myFix.patch (My fix description) Could not apply patch! Skipping. The error was: The "patches/phpunit/php-code-coverage/myFix.patch" file could not be downloaded: failed to open stream: No such file or directory Writing lock file Generating autoload files
在错误跟踪器中翻阅后,我发现依赖项错误未解决基于文件的补丁 。 事实证明,您必须在修补程序之前指定URL(这意味着从某处下载URL),或者在安装了需要修补程序的依赖项的每个项目中手动指定修补程序的路径。
总结cweagans/composer-patches
。
这种方法的优点:
- 该插件有一个社区;
roave/security-advisories
将不会停止工作;- 当发布新版本的依赖项时,如果成功应用了补丁程序,就足以确保一切都可以与新版本一起使用(对于次要版本,很有可能完全可以单独工作,对于主要版本,也很可能不需要执行任何操作);
- 如果某些项目依赖于您的库,则对于它们,
phpunit/php-code-coverage
将不会严格固定; - 而且,在以上段落的情况下,这样的项目将能够在PHP Code Coverage中应用其补丁。
缺点:
- 这是Composer的插件,这意味着在更新Composer时可能会损坏;
- 必须指定
enable-patching=true
以便从依赖项应用补丁; - 主要项目维护者没有太多时间来处理它,因此,通常,他接受请求请求,但并没有特别开发项目(例如,他对任务的第二个版本有想法,但三年后几乎没有什么改变);
- 有一个基于文件的补丁不能在依赖项错误中解决 ,这很不方便,并且已经积压了三年了。
- 您不能对不同版本的依赖项使用不同的补丁。
最后一点已成为我的障碍。 首先,我提出了功能请求 。 维护者写道,他不想将此功能添加到主代码中,但是在第二个版本中,可以编写插件(是的,是Composer插件的插件)。 第二版的前景不明确,所以我决定寻找替代方案。 在小清单中,我没有找到将受支持的插件。
我不想进入插件代码,所以我决定分叉-当然,有人已经遇到了问题并已解决。
使用Vaimo Composer修补程序
在大多数货叉中,与原始货叉完全没有区别(为什么它们甚至会货叉?)。 叉的一部分用于请求请求,这些请求已与主库合并。 但是,有一个有趣的候选人正在解决我的问题-Vaimo Composer Patches 。 当时它仍然被构造为fork,但是它的维护者似乎并不会执行拉取请求。 例如,除其他外,他已经将软件包名称更改为vaimo/composer-patches
。 但是有一个问题:问题被禁用,也就是说,根本没有作者的反馈。 另外,该插件也未托管在Packagist上 。
如此好的叉子不应丢在其他无用的叉子中。 因此,我联系了作者,要求启用问题并将程序包添加到Packagist。 经过将近一个月的时间,作者回答并完成了所有这些工作。 :)
使用vaimo/composer-patches
与使用先前的插件没有什么不同,但是您可以为不同的版本指定不同的补丁。
- 我们回滚我们的库(删除
vendor
文件夹是必要的,因为插件cweagans/composer-patches
和vaimo/composer-patches
不太兼容):
cd ../php-composer-patches-example git checkout master rm -rf vendor/ composer.phar update
- 我们执行上一节中的1-4点。
- 在我们的项目中,我们连接
vaimo/composer-patches
:
cd ../php-composer-patches-example git checkout -b vaimoComposerPatches composer.phar require vaimo/composer-patches '^4.20.2'
- 要配置
vaimo/composer-patches
请将以下内容添加到composer.json
(可在此处查看文档):
{ "extra": { "patches": { "phpunit/php-code-coverage": { "My fix description": { "< 7.0.0": "patches/phpunit/php-code-coverage/myFix-leagcy.patch", ">= 7.0.0": "patches/phpunit/php-code-coverage/myFix.patch" } } } } }
- 更新依赖项:
composer.phar update
- 如果出了点问题,可以在上一条命令的输出中看到,但以防万一,您可以确保应用我们的更改:
$ grep example vendor/phpunit/php-code-coverage/src/CodeCoverage.php // for example some changes here
- 提交并推送结果:
git add composer.json patches/phpunit/php-code-coverage/myFix.patch git commit --gpg-sign --message='Use vaimo/composer-patches' git push -u origin vaimoComposerPatches
我们确保在项目中安装库时,补丁也将适用。
创建一个项目并安装mougrim/php-composer-patches-example
:
cd ../ rm -rf php-project mkdir php-project cd php-project composer.phar require phpunit/phpunit '^8.4.2' composer.phar config repositories.mougrim/php-composer-patches-example vcs https://github.com/mougrim/php-composer-patches-example.git composer.phar require mougrim/php-composer-patches-example dev-vaimoComposerPatches
为了以防万一,您可以确保已应用我们的更改:
$ grep example vendor/phpunit/php-code-coverage/src/CodeCoverage.php // for example some changes here
总结vaimo/composer-patches
。
该插件的优点与以前的优点几乎相同,但还包括以下内容:
- 维护者正在积极开发该插件,并已经发布了第四个主要版本;
- 无需为要应用依赖项的补丁规定任何附加规定;
- 您可以对不同版本的依赖项使用不同的补丁。
- 该插件有很多设置,因此,如果本文中描述的功能不足以满足您的要求,请查看文档-可能您所需的功能已经实现。
缺点:
- 像上一个一样,它是Composer的插件,这意味着在更新Composer时它可能会损坏;
- 与以前的插件不同,此社区的插件较少。
结论
总结一般结果:
我还得出了一个间接结论:如果某种依赖关系没有提供必要的功能,那么可能会有实现此功能的分叉甚至更多。
在Badoo,我们在两种情况下使用Vaimo Composer Patches:
- 在SoftMocks中修补PHPUnit和PHP代码覆盖率;
- 在Webmozart的内部存储库中声明为与SoftMocks兼容的临时修补程序(而SoftMocks不支持
array_map(array('static', 'valueToString')
构造array_map(array('static', 'valueToString')
)。
里纳特·阿赫玛德耶夫(Rinat Akhmadeev),高级 PHP开发人员
UPD1 :感谢BoShurik提供的别名 链接 。 在文章中添加了有关别名的要点。