Composer 工作原理 [原始碼分析]

勺顛顛發表於2020-04-17

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的渣渣

相關文章