PHP 依赖管理利器 Composer 源码解读

拍片至今,参与的大多项目利用 Composer 管理各种依赖,有效提高了开发效率,也源于社区活跃程度,才出现了形式各样的依赖供我这种低级开发者使用。但从未去真正了解过它的实现,给它的定位就是工具,能用就行。现在看来,似乎行不大通。所以,还是需要抽空去了解具体实现。

下载

这里采用命令操作的,如果 Windows,直接从网页端 Download ZIP 也行,然后解压。至于是否重命名,看个人意愿。

wget https://github.com/composer/composer/archive/refs/heads/master.zip 
tar -zxf master.zip && rm master.zip
mv composr-master composer && rm -rf composr-master

然后使用 Phpstorm 打开对应目录就行了。由于项目源码的原因,这里需要其他的依赖,所以,要在项目根目录执行 composer install 安装依赖。出了异常 Could not scan for classes inside "phpstan/Rules/tests/data" which does not appear to be a file nor a folder,为此,移除了 composer.json 中的 autoload-dev 节点下的 classmap 设置。不对,可以加标记,比如 composer install --no-dev 忽略 require-dev 部分也可以。

分析

目录结构

采用无序列表表示。

  • bin 有关 composer 使用到的命令脚本文件,方便测试;
  • doc 文档,顾名思义;
  • res 存放的模式;
  • src 源码部分;
  • vendor 其他依赖包,经 composer install 产生的。

bin 下的两个命令脚本

compile

将 composer 编译到一个独立文件(默认为 composer.phar)。由于这里会获取版本库最后一次提交时间(git log -n1 --pretty=%ct HEAD)作为后缀拼接,我这边是直接下载压缩包,不是 Git 仓库,所以,下载那里,估计还是得采用 Fork 模式做,当然也可以直接初始化一个仓库,顺便做个提交,似乎也行得通。我采用了第二种方式,但是 php.ini => phar.readonly 需要关闭,注意一下,否则会出错 Failed to compile phar: [UnexpectedValueException] creating archive "composer.phar" disabled by the php.ini setting phar.readonly。速度还行,已经生成了 compser.phar 压缩文件。那先看看这个编译脚本吧。
先使用 require __DIR__.'/../src/bootstrap.php' 引入启动脚本保证 use Composer\Compiler 的可用性。该类实例化后直接调用编译方法 (new Compiler())->compile(),为了表述,代码有一定压缩,当然做了捕获异常处理。接下来,进入这个编译类。
主要有三个私有属性,version 最后提交 Hash 码,branchAliasVersion 分支对应版本,在 composer.json 中配置指向 extra.branch-alias.dev-master 节点,versionDate 最后提交时间,一共有六个方法,其中五个为私有,限制比较严格,也就是说,只有编译那个方法可调用。

  • Composer.getRelativeFilePath 获取文件相对路径;
    对文件绝对路径采用两次 dirname,这里已知的,当前文件和项目根目录的距离,然后定位子串在原串位置,使用 substr_replace 替换,最后 strtr 替换特定字符。
  • Compiler.addFile 向 phar 压缩文件中添加文件;
    获取所要添加文件相对路径后,把文件流载入字符串,然后根据参数 $strip 去除空格,如果是证书文件就不用去,直接拼接。还有个特殊文件 src/Composer/Composer.php,需要替换一些字符串。最后调用 Phar.addFromString 加到压缩文件。
  • Compiler.stripWhitespace 去掉空格;
  • Compile.addComposerBin 添加 composer 文件;
  • Compile.getStub 获取末节内容;
  • Compile.compile 编译方法;
    一来就是压缩文件存在即删除,毕竟要新建。然后就是,通过仓库获取最后提交信息和标签(如果没有标签,就解析 composer.json 中获取)。添加文件的顺序也很讲究啊,依次是:composer 源码(排除了 Compiler.php/ClassLoader.php/InstalledVersions.php)=> ClassLoader.php => InstalledVersions.php => 添加 vendor 下的文件 => 扩展文件 => bin/composer => 末节 => 证书。

所以,大概也了解了生成压缩包的大概流程,但对 composer 的过程,还是不清楚。估计是要看第二个命令脚本。

composer

这里就是正常流程。不过,在非 cli/phpdbg 环境下执行 php bin/composer 会输出警告级异常,不会强行中断。有一行看不懂 setlocale(LC_ALL, 'C'),就挺尴尬,先放在这里,也问过朋友,说是设置地区信息,但是这个字符 C 。紧接着,就引入启动文件,做一些其他检测,内存限制和环境参数设置。最后还是 (new Composer\Console\Application())->run() 就完了。应该就是这个类,入口,我看到了 logo。

流程分析

应用主类 Application,位于 src/Composer/Console/Application.php,继承 Symfony 控制台应用的实现。

  • Application.__construct():应用主类构造方法;
    • 关闭 Xdebug 的输出干扰, xdebug.show_exception_trace 和 xdebug.scream;
    • 注册 php 中止函数 register_shutdown_function;
  • Application.run: 应用运行方法;
    • 重写父级 run 方法,如果没有输出流就调用 Factory::createOutput() 创建,红色高亮警告为黄字黑底的终端样式,然后继续执行父级方法,携带输出流;当然,父级方法也是做了判断,不存在就创建,注意 $input 是终端参数类 ArgvInput 的实例化。调用 configureIO() 方法通过用户参数和选项设置 IO;
    • 切入 doRun() 方法。还是有重写;
      • 一来就是检测插件标记 --no-plugins 设置属性 disablePluginsByDefault,然后检测界面是否可交互;
      • 然后将当前 IO 和 QuestionHelper(用户交互助手) 重新实例化一个终端 IO 助手 ConsoleIO,并注册到 将错误转化异常的助手 ErrorHandle;
      • 检测参数 --no-cache,如果为真,就给出提示,并把环境变量 COMPOSER_CACHE_DIR 定到 nul 或 /dev/null,主要根据系统选择的;
      • 切换工作目录,如果命令参数中有设置 -working-dir。然后,获取当前命令名称,也就是参数的第一个,比如 composer install,那么命令名称就是 install。当然,如果更换了工作目录,是要对当前目录下的 composer.json 文件存在性检测,没有的话,会给出相应提示;
      • 继续检测插件命令,这里先留着
      • 检测是否为代理命令。如果非代理模式,检测 PHP 版本,Xdebug 和超过60天没有更新,以及非 Windows 环境检测当前运行用户是否为根用户;
      • 检测系统临时目录 sys_temp_dir,在 php.ini 配置文件中设置;
      • 将当前 composer.json 文件中 scripts 节点下配置非标准命令加入到自身命令集合;
      • 检测是否显示时间,通过参数 --profile
      • 调用父级 doRun()` 方法;
        • 检测命令是否查看版本(--version / -V),如果是,直接输出;
        • 处理帮助命令;
        • 如果没有命令,就设置为 list;
        • 标记当前运行的命令,doRunCommand()` 执行完后,重置;
        • 检测是否设置调度器,没有的话,直接调用命令的执行,这里就不跟进。看看有调度的情况;
        • 如果存在调度的情况,将当前的 IO 和命令作为参数实例化 ConsoleCommandEvent。异步?
        • 如果终端命令事件可以执行,就执行。如果不能,就将状态码设置禁用。出现了异常,就调用终端异常事件类处理,最后主动调用关闭 ConsoleTerminateEvent;
      • 还原目录(如果设置了工作目录),打印时间(如果设置了 --profile),恢复错误;
    • 异常处理,如果有,渲染并设置退出代码,这里如果自动退出,还要将代码控制在256以内。到这里也就全部结束。

到这里。composer 整体的主干流程也就结束了,接下来,该是对具体命令的分析,粗略看了一下,在 src/Composer/Command 目录中大概有三十多个命令脚本文件。也可以挨着看,真要算起来,也不多。

命令分析

这里仅分析 src/Composer/Command 目录下的命令脚本文件。需要注意,php bin/composer command args 是在 composer 源码项目中调试使用的,如果是在正式环境中,直接 composer command args 即可。这里我采用在 composer 源码项目中调试。

  • about,文件 AboutCommand.php,参考命令 php bin/composer about,查看 Composer 的简要描述;
  • archive,文件 ArchiveCommand.php,参考命令 php bin/composer archive
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值