把简单的事情做好

0%

Composer-PHP依赖管理工具

对于 phper 们来说,Composer 再熟悉不过了,几乎每天都会用到。但是经常使用并不代表我们熟悉它的原理,以致于遇到问题就从网上找答案解决,甚至有时候搭一个 php 运行环境或者装一个包要耗费很长时间。本文尝试简单介绍下什么是 composer,它是如何工作的,如何自己写一个 composer package 并发布到 packagist,以及我们如何搭建一个 private repository。同时我们也会探索,对于容器化的场景如何通过 composer proxy 来加速环境安装。

简介

Composer 是 PHP 的一个依赖管理工具,本质是一个 php 的命令行项目,通过执行命令可以解析 composer.json 文件,获取安装 composer.json 中声明的依赖。Composer 的最大作用就是实现了代码的复用。我们最常接触的有下面几个部分

  • composer.json 定义一个包,声明包的依赖。跟 composer.json 同级的文件都属于这个包,注意项目(root包)是一个特殊的没有名称的包。
  • Repository 包仓库。存放包声明(packages.json)和包定义({package}.json)的仓库。
  • composer.lock Composer 请求 Repository 来获取依赖包的定义以及源码的特定版本及存放位置,解析完所有的依赖会把这些信息写入到这个文件
  • vendor Composer 下载下来的源码存放的目录

我们使用依赖包的时候,只需要 require 'vendor/autoload.php';,就可以通过命名空间来调用依赖包的功能。Composer 已经为我们完成了自动加载。

更多介绍请访问官方文档

原理

Composer install 的简单执行流程

composer_instal

调试 composer 源码时,需要将入口文件里 unset($xdebug); 的代码注释掉,否则无法调试。

Composer SAT Solver

Composer 将依赖解析的问题转换了成了一个布尔可满足性问题(Boolean satisfiability problem;SAT),SAT 简单来说就是存在一组输入,使得输出为真。

例如:

  • (A ∩ B) 是可满足的,当 A=TRUE 并且 B=TRUE 使得结果为真
  • (A ∩ B ∩ ¬A) 是不可满足的,因为 A 不能同时为 TURE 和 FALSE

每个包的每个版本都是一个元素
Install A-1.0 (A-1.0)
Remove A-1.0 (¬A-1.0)
A-1.0 requires B-1.0 (¬A-1.0 ∪ B-1.0)
A-1.0 conflicts with B-1.0 (¬A-1.0 ∪ ¬B-1.0)
C-1.0 provides B-1.0, A-1.0 requires B-1.0 (¬A-1.0 ∪ B-1.0 ∪ C-1.0)
C-1.0 replaces B-1.0, A-1.0 requires B-1.0 (¬C-1.0 ∪ ¬B-1.0) ∩ (¬A-1.0 ∨ B-1.0 ∪ C-1.0)

Composer SAT Solver 实现了以 Ο(n²)复杂度的依赖解析。

定义并发布一个包

定一个 Composer Package 很简单,我们通过下面的步骤就可以很方便的搭建好一个 package 的框架,下面以 alitain/demo/EchoService::hello() 为例,当调用时会输出 “hello world!”

  1. 进入一个空目录,执行 composer init
  2. 通过交互式的方式填入包的名称,alitain/demo,包的作者信息、描述、依赖包等。这里不依赖其他包,一路回车就可以生成一个包定义的 composer.json 文件。
  3. 如果依赖了其他包需要执行 composer install 安装依赖
  4. 新建一个 src 目录,用户存放包源码。我们使用 psr4 的规范来完成自动加载
  5. 写好代码后,执行 composer dump-autoload 生成 autoload.php
  6. 将代码上传到 github,登录 packagist,提交 git仓库地址,packagist 会定期获取文件
  7. 未发布版本的代码,只能通过源码的方式进行安装。发布版本,比如: v0.0.1,则即可通过 composer require alitain/demo:v0.0.1 进行安装

最终目录结构为

1
2
3
4
5
6
/
src/
EchoService.php
composer.json
vendor/*
index.php

EchoService.php

1
2
3
4
5
6
7
8
9
10
11
<?php

namespace alitain\demo;

class EchoService
{
public static function hello()
{
echo "hello world!";
}
}

index.php

1
2
3
4
5
6
7
<?php

$loader = require __DIR__ . '/vendor/autoload.php';

use alitain\demo\EchoService;

EchoService::hello();

composer.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name": "alitain/demo",
"description": "a demo package",
"type": "library",
"authors": [
{
"name": "alitain",
"email": "alitain.dev@gmail.com"
}
],
"require": {},
"autoload": {
"psr-4": {
"alitain\\demo\\": "src"
}
}
}

私有包

有时候,我们不希望源码上传到 github 进行公开,因为我们的源码可能是业务相关性比较大,只适合在公司内容进行复用。我们可以定义一个包上传到公司内部的 gitlab 或者其他 vcs。只需要在 composer.json 中 repositories 中指定 type 为 git ,url 为仓库地址即可。

composer proxy

我们在日常使用 composer 的时候,可能会经常遇到安装缓慢或者安装失败的问题,即便是已经使用了国内的镜像代理服务器。可能的原因有:

  • 国内镜像服务不稳定,访问包定义文件时 404
  • 访问 packagist 网络环境不好
  • 达到 github api 访问限制
  • 源配置不合理,比如安装 yii 时,没有配置 asset-repository
  • 容器环境无法利用本地 cache,composer 更新不及时

同时,我们如果我们经常使用私有包,也会遇到一些问题:

  • 开发环境需要各个项目的代码 check 权限
  • 多个包,需要配置多个 git 地址
  • 每次从源码安装比较慢

为了解决上面这些问题,找了一些解决方案,如下:

satis

Composer 组织开发维护的一个轻量级的私有包仓库,用来管理私有包。缺点是没法解决公有包安装慢的问题。

Private.Packagist

官方推荐的功能齐全的收费的仓库服务,几乎可以满足所有的需求。缺点是需要花钱。

toran-proxy

在 Private.Packagist 退出之前使用比较多的一个代理服务器,现在官方已经不维护了,但是原有的功能基本上可以满足我们的需求。所以,最终采用了这个方案,安装使用也很方便,有人做了一个 docker 镜像出来。

1
docker run --name toran-proxy -d -p <your-port>:80 -v /opt/toran-proxy:/data/toran-proxy -e "TORAN_HOST=<your-domain>" -e "PHP_TIMEZONE=Asia/Shanghai" -e "TORAN_CRON_TIMER=half" -e "TORAN_TOKEN_GITHUB=your-token" cedvan/toran-proxy:latest

toran-proxy 可以设置上游服务器地址,例如我们可以指定阿里云镜像。开启缓存功能以后,第一次安装从会上游服务器拉取包定义文件和源码,第二次安装就可以从 toran-proxy 安装,除首次安装比较慢,大大加快了 composer 的安装速度。

同时,可以设置私有包,首先需要让 toran-proxy 获取代码 check 权限,然后添加自定义包的 git 地址即可。我们使用 composer 的时候只需要配置特定的源,其他的设置交由 toran-proxy 去配置。

最终通过 toran-porxy 完成了公有包的安装加速和私有包的统一管理。