Composer 工作原理 [源码分析]

PS:篇幅有限详细说明可到composer仓库上下载源码库以及下载本人注解的仓库即可。

composer项目的控制台应用依赖于Symfony控制台组件,控制台组件本人在laravel相关版本已经大体说过,本篇仅是抽核心重点流程来梳理composer框架的运行流程。

composer安装

文档

composer工作原理详说【源码级注解】并非PPT概念扯蛋

  • 首先下载installer文件
  • 运行installer文件
  • installer是啥
    它是一个php脚本文件,执行php installer后运行

composer工作原理详说【源码级注解】并非PPT概念扯蛋

  • 初始化installer

    function setupEnvironment()
    {
      ini_set('display_errors', 1);
    
      $installer = 'Composer Installer';
      //win系统版本号,如果你的系统是win10返回10【本人觉得win系统开发复杂,因为我真的没法调度程序】
      if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
          if ($version = getenv('COMPOSERSETUP')) {
              $installer = sprintf('Composer-Setup.exe %s', $version);
          }
      }
    
      define('COMPOSER_INSTALLER', $installer);
    }

    ` process

    //$argv位置参数,来源于linux运行一个程序时,会把位置参数传递给main入口
    process(is_array($argv) ? $argv : array()); 
    function process($argv)
    {
    
     //安装选项参数https://getcomposer.org/download/说明
     //对于我来说,无用
     //运行时可配置安装位置
    $installDir = getOptValue('--install-dir', $argv, false);
    //可指定版本,不指定就拉取最新的版本
    $version = getOptValue('--version', $argv, false);
    //默认下载后重命名为composer.phar一般默认
    $filename = getOptValue('--filename', $argv, 'composer.phar');
    $cafile = getOptValue('--cafile', $argv, false);
    
    //$installDir $version $cafile 检查你提供的参数是否有效【就是你在安装的时候是否指定了这些选项,指定了就会检查】
    if (!checkParams($installDir, $version, $cafile)) {
    exit(1);
    }
    
    //检测你的PHP环境如扩展有没有安装好
    $ok = checkPlatform($warnings, $quiet, $disableTls, true);
    
    if ($check) {
    
    if ($ok) {
    showWarnings($warnings);
    showSecurityWarning($disableTls);
    }
    exit($ok ? 0 : 1);
    }
    
    if ($ok || $force) {
    //实例化安装器
    $installer = new Installer($quiet, $disableTls, $cafile);
    //开始安装
    //1先从https://getcomposer.org/versions 获取目前官网最新的版本号
    //所以你在安装的时候是可以指定版本号的,不然默认就是拉取最新的
    //2、从https://getcomposer.org/download/1.10.5/composer.phar 下载此项目
    //phar文件是PHP的PHAR扩展打包的php项目【如果你用过PHAR扩展打包过,就知道了】
    //非常的简单
    if ($installer->run($version, $installDir, $filename, $channel)) {
    //装完退出当前进程
    exit(0);
    }
    }
    exit(1);
    }

    安装说明:php composer-setup.php文件时从getcomposer.org网站下载打包好的composer.phar项目到本地

php PHAR扩展使用

可以自行看手册或是搜索,大把资料,我不想重复了【老早就有人撸过了】

composer.phar项目目录结构

Composer 工作原理详说 [源码级注解] 并非 PPT 概念扯蛋

composer.phar项目运行的入口文件

Composer 工作原理详说 [源码级注解] 并非 PPT 概念扯蛋

#!/usr/bin/env php
Composer 工作原理详说 [源码注解] 并非 PPT 概念扯蛋

入口文件源码【精简提炼了,大堆受不了】

#!/usr/bin/env php env可执行文件它最终会找php解释器如上图
<?php

if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
}
//引入自动【自动加载php类文件】加载文件
require __DIR__.'/../src/bootstrap.php';
putenv('COMPOSER_BINARY='.realpath($_SERVER['argv'][0]));
// run the command application
//控制台应用依赖于Symfony框架
//具体如何使用本人在laravel5.5LTS版本注解过
//如果你不清楚可以去看看,或是到symfony官方找到控制台应用组件复制粘贴运行一下就懂
//实在懒的看算了 本人不在重复
$application = new Application();
$application->run();

composer.phar依赖的扩展包

Composer 工作原理详说 [源码注解] 并非 PPT 概念扯蛋

composer控制台应用run流程【抽它的核心流程与我注解laravel 5.5LTS一样的思想不想再重复】

  • 加载命令类文件
    建议自行去撸一下symfony的控制台组件,如果不想撸可以看我这里的大体说明,其它的如加载用户自定义的插件命令,加载composer的配置文件,auth文件,初始化各种如下载管理器,插件管理器等在此不题。

    1$exitCode = $this->doRun($input, $output);
    2$command = $this->find($name);
    //添加所有命令
    3$this->init();
    private function init()
    {
     foreach ($this->getDefaultCommands() as $command) {
         $this->add($command);
     }
    }
    public function add(Command $command)
    {
    
     $command->setApplication($this);
     if (!$command->isEnabled()) {
         $command->setApplication(null);
         return;
     }
     $this->commands[$command->getName()] = $command;
     foreach ($command->getAliases() as $alias) {
         $this->commands[$alias] = $command;
     }
     return $command;
    }
    protected function getDefaultCommands()
    {
    $commands = array_merge(parent::getDefaultCommands(), array(
    new Command\AboutCommand(),
    new Command\ConfigCommand(),
    new Command\DependsCommand(),
    new Command\ProhibitsCommand(),
    new Command\InitCommand(),
    new Command\InstallCommand(),
    new Command\CreateProjectCommand(),
    new Command\UpdateCommand(),
    new Command\SearchCommand(),
    new Command\ValidateCommand(),
    new Command\ShowCommand(),
    new Command\SuggestsCommand(),
    new Command\RequireCommand(),
    new Command\DumpAutoloadCommand(),
    new Command\StatusCommand(),
    new Command\ArchiveCommand(),
    new Command\DiagnoseCommand(),
    new Command\RunScriptCommand(),
    new Command\LicensesCommand(),
    new Command\GlobalCommand(),
    new Command\ClearCacheCommand(),
    new Command\RemoveCommand(),
    new Command\HomeCommand(),
    new Command\ExecCommand(),
    new Command\OutdatedCommand(),
    new Command\CheckPlatformReqsCommand(),
    ));
    
    if ('phar:' === substr(__FILE__, 0, 5)) {
    $commands[] = new Command\SelfUpdateCommand();
    }
    
    return $commands;
    }
    4$exitCode = $this->doRunCommand($command, $input, $output);
    5return $command->run($input, $output);
    //最终运行execute【命令类的方法】
    6$statusCode = $this->execute($input, $output);

Composer对象构建流程

Composer项目的关键配置文件目录结构
Composer 工作原理 [源码分析]
config.json文件内容
Composer 工作原理 [源码分析]
auth.json文件内容

Composer 工作原理 [源码分析]

Composer对象构建源码

1、实例化NUllIO类
Composer\IO\NullIO extends BaseIO$this->io = new NullIO();  
2、工厂Composer\Factory类
Composer\Factory
public static function create(IOInterface $io, $config = null, $disablePlugins = false)
{
  $factory = new static();

  return $factory->createComposer($io, $config, $disablePlugins);
}
$this->composer = Factory::create($this->io, null, $disablePlugins); 

3、createComposer
public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true)
 { 

  $cwd = $cwd ?: getcwd();//当前进程运行的目录
  if (null === $localConfig) {
  //获取当前项目根目录下的composer.json文件
  $localConfig = static::getComposerFile();
  }
  if (is_string($localConfig)) {
      $composerFile = $localConfig;
      $file = new JsonFile($localConfig, null, $io);
      $file->validateSchema(JsonFile::LAX_SCHEMA);
    //读取composer.json的内容
      $localConfig = $file->read();
  }
 //得到配置类Composer/config实例并且合并了.composer目录下的配置文件 config.json auth.json  
  $config = static::createConfig($io, $cwd);
  //合并项目根目录下的composer.json配置文件
  $config->merge($localConfig);
  $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io)));
  //vendor目录
  $vendorDir = $config->get('vendor-dir');
 //composer工厂类
  $composer = new Composer();
  //1、给Composer实例添加【配置实例】
  $composer->setConfig($config);
   //给baseIo实例添加config实例
  $io->loadConfiguration($config);
  //工厂类构建Composer\Util\RemoteFileSystem实例
  $rfs = self::createRemoteFilesystem($io, $config);
  //2、给composer实例添加【事件调度器实例】
  $dispatcher = new EventDispatcher($composer, $io);
  $composer->setEventDispatcher($dispatcher);
 //调用源码仓库工厂构建仓库管理器实例Composer\Repository\RepositoryManager
  $rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs);
  //3、给composer实例添加【仓库管理器实例】
  $composer->setRepositoryManager($rm);

  //给RespositoryManager添加new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io))本地仓库实例对象
  //仓库管理器添加了svn,git,github,vsc,composer等仓库管理类实例
  $this->addLocalRepository($io, $rm, $vendorDir);

  // force-set the version of the global package if not defined as
 // guessing it adds no value and only takes time  if (!$fullLoad && !isset($localConfig['version'])) {
  $localConfig['version'] = '1.0.0';
  }

  // 加载扩展包实例
  $parser = new VersionParser;
  $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser);
  $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
  //4、读取项目根目录下的composer.json配置数据,并保存在Composer\Package\BasePackage 扩展包实例中
//同时根据config.json的配置【镜像类型一般有svn,git,github,composer,vcs等】一般为composer配置了Composer\Repository\ComposerRepository composer仓库实例

  $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
  $composer->setPackage($package);

  // initialize installation manager
 //5、给composer实例添加Installer\InstallationManager() 【安装管理器】
  $im = $this->createInstallationManager();
  $composer->setInstallationManager($im);

  if ($fullLoad) {
  // initialize download manager
 //6、给composer实例添加Downloader\DownloadManager 【下载管理器】
  $dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
  $composer->setDownloadManager($dm);

 //7、给composer实例添加【自动加载生成器实例】
  $generator = new AutoloadGenerator($dispatcher, $io);
  $composer->setAutoloadGenerator($generator);

  // 8、给composer实例添加压缩【ZIP,PHAR打包】归档管理器
  $am = $this->createArchiveManager($config, $dm);
  $composer->setArchiveManager($am);
  }

  //给安装管理器添加一些安装器【如pear,package,plugin,library】
  $this->createDefaultInstallers($im, $composer, $io);

  if ($fullLoad) {
  $globalComposer = null;
  if (realpath($config->get('home')) !== $cwd) {
  $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins);
  }
//return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins);
 //9、给composer实例添加【插件管理器实例】
  $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins);
  $composer->setPluginManager($pm);

//运行用户在composer.json配置的插件类或是composer-installer安装器
  $pm->loadInstalledPlugins();
  }


  if ($fullLoad && isset($composerFile)) {
  $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
 ? substr($composerFile, 0, -4).'lock'
  : $composerFile . '.lock';
  //10、给composer实例添加【Locker实例】
  $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile));
  $composer->setLocker($locker);
  }
  return $composer;
  }

composer/config对象

namespace Composer
class Config
{
    public static $defaultConfig = array(
        'process-timeout' => 300,
        'use-include-path' => false,
        'preferred-install' => 'auto',
        'notify-on-install' => true,
        'github-protocols' => array('https', 'ssh', 'git'),
        'vendor-dir' => 'vendor',
        'bin-dir' => '{$vendor-dir}/bin',
        'cache-dir' => '{$home}/cache',
        'data-dir' => '{$home}',
        'cache-files-dir' => '{$cache-dir}/files',
        'cache-repo-dir' => '{$cache-dir}/repo',
        'cache-vcs-dir' => '{$cache-dir}/vcs',
        'cache-ttl' => 15552000, // 6 months
        'cache-files-ttl' => null, // fallback to cache-ttl
        'cache-files-maxsize' => '300MiB',
        'bin-compat' => 'auto',
        'discard-changes' => false,
        'autoloader-suffix' => null,
        'sort-packages' => false,
        'optimize-autoloader' => false,
        'classmap-authoritative' => false,
        'apcu-autoloader' => false,
        'prepend-autoloader' => true,
        'github-domains' => array('github.com'),
        'bitbucket-expose-hostname' => true,
        'disable-tls' => false,
        'secure-http' => true,
        'cafile' => null,
        'capath' => null,
        'github-expose-hostname' => true,
        'gitlab-domains' => array('gitlab.com'),
        'store-auths' => 'prompt',
        'platform' => array(),
        'archive-format' => 'tar',
        'archive-dir' => '.',
        'htaccess-protect' => true,
        'use-github-api' => true,
        'lock' => true,
        // valid keys without defaults (auth config stuff):
        // bitbucket-oauth
        // github-oauth
        // gitlab-oauth
        // gitlab-token
        // http-basic
    );

    public static $defaultRepositories = array(
        'packagist.org' => array(
            'type' => 'composer',
            'url' => 'https?://repo.packagist.org',//镜像地址,通过composer config便可以修改,比如我上面列出的config.json配置文件
            'allow_ssl_downgrade' => true,
        ),
    );


    public function __construct($useEnvironment = true, $baseDir = null)
    {
        // load defaults
        $this->config = static::$defaultConfig;
        $this->repositories = static::$defaultRepositories;
        $this->useEnvironment = (bool) $useEnvironment;
        $this->baseDir = $baseDir;
    }

Composer类

namespace Composer;

use Composer\Package\RootPackageInterface;
use Composer\Package\Locker;
use Composer\Repository\RepositoryManager;
use Composer\Installer\InstallationManager;
use Composer\Plugin\PluginManager;
use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Archiver\ArchiveManager;

class Composer
{
    const VERSION = '@package_version@';
    const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
    const RELEASE_DATE = '@release_date@';
    const SOURCE_VERSION = '1.10-dev+source';

    public static function getVersion()
    {

        return self::VERSION;
    }

    /**
     * @var Package\RootPackageInterface
     */
    private $package;

    /**
     * @var Locker
     */
    private $locker;

    /**
     * @var Repository\RepositoryManager
     */
    private $repositoryManager;

    /**
     * @var Downloader\DownloadManager
     */
    private $downloadManager;

    /**
     * @var Installer\InstallationManager
     */
    private $installationManager;

    /**
     * @var Plugin\PluginManager
     */
    private $pluginManager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    /**
     * @var Autoload\AutoloadGenerator
     */
    private $autoloadGenerator;

    /**
     * @var ArchiveManager
     */
    private $archiveManager;

    /**
     * @param  Package\RootPackageInterface $package
     * @return void
     */
    public function setPackage(RootPackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * @return Package\RootPackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**Composer/Config 实例
     * @param Config $config
     */
    public function setConfig(Config $config)
    {
        $this->config = $config;
    }

    /**Composer/Config 实例
     * @return Config
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * @param Package\Locker $locker
     */
    public function setLocker(Locker $locker)
    {
        $this->locker = $locker;
    }

    /**
     * @return Package\Locker
     */
    public function getLocker()
    {
        return $this->locker;
    }

    /**
     * @param Repository\RepositoryManager $manager
     */
    public function setRepositoryManager(RepositoryManager $manager)
    {
        $this->repositoryManager = $manager;
    }

    /**
     * @return Repository\RepositoryManager
     */
    public function getRepositoryManager()
    {
        return $this->repositoryManager;
    }

    /**
     * @param Downloader\DownloadManager $manager
     */
    public function setDownloadManager(DownloadManager $manager)
    {
        $this->downloadManager = $manager;
    }

    /**
     * @return Downloader\DownloadManager
     */
    public function getDownloadManager()
    {
        return $this->downloadManager;
    }

    /**
     * @param ArchiveManager $manager
     */
    public function setArchiveManager(ArchiveManager $manager)
    {
        $this->archiveManager = $manager;
    }

    /**
     * @return ArchiveManager
     */
    public function getArchiveManager()
    {
        return $this->archiveManager;
    }

    /**
     * @param Installer\InstallationManager $manager
     */
    public function setInstallationManager(InstallationManager $manager)
    {
        $this->installationManager = $manager;
    }

    /**
     * @return Installer\InstallationManager
     */
    public function getInstallationManager()
    {
        return $this->installationManager;
    }

    /**
     * @param Plugin\PluginManager $manager
     */
    public function setPluginManager(PluginManager $manager)
    {
        $this->pluginManager = $manager;
    }

    /**
     * @return Plugin\PluginManager
     */
    public function getPluginManager()
    {
        return $this->pluginManager;
    }

    /**Composer\EventDispatcher 实例
     * @param EventDispatcher $eventDispatcher
     */
    public function setEventDispatcher(EventDispatcher $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @return EventDispatcher
     */
    public function getEventDispatcher()
    {
        return $this->eventDispatcher;
    }

    /**
     * @param Autoload\AutoloadGenerator $autoloadGenerator
     */
    public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator)
    {
        $this->autoloadGenerator = $autoloadGenerator;
    }

    /**
     * @return Autoload\AutoloadGenerator
     */
    public function getAutoloadGenerator()
    {
        return $this->autoloadGenerator;
    }
}

composer 扩展包类结构

Composer 工作原理 [源码分析]

composer 重要命令运行流程说明

  • composer require 命令
    require 命令类结构【继承】图
    Composer 工作原理 [源码分析]
    //测试composer require nicmart/tree
    //$input封装了运行composer脚本时传递的位置参数
    //$output对象
    RequireCommand->execute(InputInterface $input, OutputInterface $output)
    RequireCommand->doUpdate($input, $output, $io, $requirements);
    //安装器
    Composer\Installer->run();
    Composer\Installer->doInstall($localRepo, $installedRepo, $platformRepo, $aliases);
    Composer\Installer\InstallationManager->installationManager->execute($localRepo, $operation);  
    //包安装器
    Composer\Installer\LibraryInstaller->install(InstalledRepositoryInterface $repo, PackageInterface $package);
    Composer\Installer\LibraryInstaller->installCode(PackageInterface $package)
    //下载管理器
    Composer\Downloader->download(PackageInterface $package, $targetDir, $preferSource = null);
    //git下载管理器
    Composer\Downloader\GitDownloader extends VcsDownloader->doDownload(PackageInterface $package, $path, $url);
    //git 命令
    $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer && git remote set-url origin %sanitizedUrl% && git remote set-url composer %sanitizedUrl%';  
    Composer\Util\Git->runCommand($commandCallable, $url, $cwd, $initialClone = false);  
    //低层源码执行情况【如何跟踪linux源码运行情况可以参考本人在larave社区写过的nginx低层数据交互原理】:
    1、execve(“/usr/local/bin/php”, [“php”, “/bin/composer”, “require”, “nicmart/tree”], [/* 26 vars */]) = 0
    连接自己配置的镜像
    Composer 工作原理 [源码分析]
    Composer 工作原理 [源码分析]
    连接国外的镜像网站
    Composer 工作原理 [源码分析]

Composer 工作原理 [源码分析]

2、execve(“/bin/git”, [“git”, “clone”, “–no-checkout”, “https://github.com/nicmart/Tree."…, “/home/worker/vendor/nicmart/tree”], [/* 29 vars */]) = 0
这破命令执行后,干嘛大家都懂

总结

composer【官方使用PHAR打包的Composer.phar项目】 的运行【下载扩展包时】会通过网络与镜像仓库网站和github仓库网站【大部分,其它gitlab,svn等同样的道理】进行通信,当然了低层自然是大家熟悉的tcp/ip【socket api】,而控制台的命令运行依赖于Symfony控制台组件。
剩下的如框架封装了源码扩展包类,下载管理器,仓库管理器,安装管理器,插件管理器,命令类,工具类等,比如它下载源码库时会根据扩展包的类型使用git clone或是直接下载zip包,比如composer create-project,就会直接下载源码zip包,然后解压。篇幅原因不在对其它命令进行分析,意义不大了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
只会php crud的渣渣
利涉大川
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 11
Dennis_Ritchie

卧槽 :+1:

7个月前 评论
利涉大川
7个月前 评论
利涉大川

@深蓝wahle :blush:

7个月前 评论

很棒的分享,clone了composer的源码,发现无法直接通过源码得到composer.phar,那么官方的composer.phar是怎么打包出来的?我的猜测是手动把依赖的源码包放到vendor目录下,然后通过phar扩展得来的composer.phar

5个月前 评论

@mmmm emmm 我是在猜测官方的这个phar包怎么打出来的,具体步骤

5个月前 评论
MinsonLee

:+1:学习了

3个月前 评论
╰ゝSakura

学习了,老哥稳

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!