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);
    5、return $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 包,然后解压。篇幅原因不在对其它命令进行分析,意义不大了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值