You are here

class ExtraPackage in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php \Wikimedia\Composer\Merge\ExtraPackage

Processing for a composer.json file that will be merged into a RootPackageInterface

@author Bryan Davis <bd808@bd808.com>

Hierarchy

Expanded class hierarchy of ExtraPackage

1 file declares its use of ExtraPackage
MergePlugin.php in vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php

File

vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php, line 33

Namespace

Wikimedia\Composer\Merge
View source
class ExtraPackage {

  /**
   * @var Composer $composer
   */
  protected $composer;

  /**
   * @var Logger $logger
   */
  protected $logger;

  /**
   * @var string $path
   */
  protected $path;

  /**
   * @var array $json
   */
  protected $json;

  /**
   * @var CompletePackage $package
   */
  protected $package;

  /**
   * @param string $path Path to composer.json file
   * @param Composer $composer
   * @param Logger $logger
   */
  public function __construct($path, Composer $composer, Logger $logger) {
    $this->path = $path;
    $this->composer = $composer;
    $this->logger = $logger;
    $this->json = $this
      ->readPackageJson($path);
    $this->package = $this
      ->loadPackage($this->json);
  }

  /**
   * Get list of additional packages to include if precessing recursively.
   *
   * @return array
   */
  public function getIncludes() {
    return isset($this->json['extra']['merge-plugin']['include']) ? $this->json['extra']['merge-plugin']['include'] : array();
  }

  /**
   * Get list of additional packages to require if precessing recursively.
   *
   * @return array
   */
  public function getRequires() {
    return isset($this->json['extra']['merge-plugin']['require']) ? $this->json['extra']['merge-plugin']['require'] : array();
  }

  /**
   * Read the contents of a composer.json style file into an array.
   *
   * The package contents are fixed up to be usable to create a Package
   * object by providing dummy "name" and "version" values if they have not
   * been provided in the file. This is consistent with the default root
   * package loading behavior of Composer.
   *
   * @param string $path
   * @return array
   */
  protected function readPackageJson($path) {
    $file = new JsonFile($path);
    $json = $file
      ->read();
    if (!isset($json['name'])) {
      $json['name'] = 'merge-plugin/' . strtr($path, DIRECTORY_SEPARATOR, '-');
    }
    if (!isset($json['version'])) {
      $json['version'] = '1.0.0';
    }
    return $json;
  }

  /**
   * @return CompletePackage
   */
  protected function loadPackage($json) {
    $loader = new ArrayLoader();
    $package = $loader
      ->load($json);

    // @codeCoverageIgnoreStart
    if (!$package instanceof CompletePackage) {
      throw new UnexpectedValueException('Expected instance of CompletePackage, got ' . get_class($package));
    }

    // @codeCoverageIgnoreEnd
    return $package;
  }

  /**
   * Merge this package into a RootPackageInterface
   *
   * @param RootPackageInterface $root
   * @param PluginState $state
   */
  public function mergeInto(RootPackageInterface $root, PluginState $state) {
    $this
      ->addRepositories($root);
    $this
      ->mergeRequires('require', $root, $state);
    if ($state
      ->isDevMode()) {
      $this
        ->mergeRequires('require-dev', $root, $state);
    }
    $this
      ->mergePackageLinks('conflict', $root);
    $this
      ->mergePackageLinks('replace', $root);
    $this
      ->mergePackageLinks('provide', $root);
    $this
      ->mergeSuggests($root);
    $this
      ->mergeAutoload('autoload', $root);
    if ($state
      ->isDevMode()) {
      $this
        ->mergeAutoload('devAutoload', $root);
    }
    $this
      ->mergeExtra($root, $state);
  }

  /**
   * Add a collection of repositories described by the given configuration
   * to the given package and the global repository manager.
   *
   * @param RootPackageInterface $root
   */
  protected function addRepositories(RootPackageInterface $root) {
    if (!isset($this->json['repositories'])) {
      return;
    }
    $repoManager = $this->composer
      ->getRepositoryManager();
    $newRepos = array();
    foreach ($this->json['repositories'] as $repoJson) {
      if (!isset($repoJson['type'])) {
        continue;
      }
      $this->logger
        ->info("Adding {$repoJson['type']} repository");
      $repo = $repoManager
        ->createRepository($repoJson['type'], $repoJson);
      $repoManager
        ->addRepository($repo);
      $newRepos[] = $repo;
    }
    $unwrapped = self::unwrapIfNeeded($root, 'setRepositories');
    $unwrapped
      ->setRepositories(array_merge($newRepos, $root
      ->getRepositories()));
  }

  /**
   * Merge require or require-dev into a RootPackageInterface
   *
   * @param string $type 'require' or 'require-dev'
   * @param RootPackageInterface $root
   * @param PluginState $state
   */
  protected function mergeRequires($type, RootPackageInterface $root, PluginState $state) {
    $linkType = BasePackage::$supportedLinkTypes[$type];
    $getter = 'get' . ucfirst($linkType['method']);
    $setter = 'set' . ucfirst($linkType['method']);
    $requires = $this->package
      ->{$getter}();
    if (empty($requires)) {
      return;
    }
    $this
      ->mergeStabilityFlags($root, $requires);
    $requires = $this
      ->replaceSelfVersionDependencies($type, $requires, $root);
    $root
      ->{$setter}($this
      ->mergeOrDefer($type, $root
      ->{$getter}(), $requires, $state));
  }

  /**
   * Merge two collections of package links and collect duplicates for
   * subsequent processing.
   *
   * @param string $type 'require' or 'require-dev'
   * @param array $origin Primary collection
   * @param array $merge Additional collection
   * @param PluginState $state
   * @return array Merged collection
   */
  protected function mergeOrDefer($type, array $origin, array $merge, $state) {
    $dups = array();
    foreach ($merge as $name => $link) {
      if (!isset($origin[$name]) || $state
        ->replaceDuplicateLinks()) {
        $this->logger
          ->info("Merging <comment>{$name}</comment>");
        $origin[$name] = $link;
      }
      else {

        // Defer to solver.
        $this->logger
          ->info("Deferring duplicate <comment>{$name}</comment>");
        $dups[] = $link;
      }
    }
    $state
      ->addDuplicateLinks($type, $dups);
    return $origin;
  }

  /**
   * Merge autoload or autoload-dev into a RootPackageInterface
   *
   * @param string $type 'autoload' or 'devAutoload'
   * @param RootPackageInterface $root
   */
  protected function mergeAutoload($type, RootPackageInterface $root) {
    $getter = 'get' . ucfirst($type);
    $setter = 'set' . ucfirst($type);
    $autoload = $this->package
      ->{$getter}();
    if (empty($autoload)) {
      return;
    }
    $unwrapped = self::unwrapIfNeeded($root, $setter);
    $unwrapped
      ->{$setter}(array_merge_recursive($root
      ->{$getter}(), $this
      ->fixRelativePaths($autoload)));
  }

  /**
   * Fix a collection of paths that are relative to this package to be
   * relative to the base package.
   *
   * @param array $paths
   * @return array
   */
  protected function fixRelativePaths(array $paths) {
    $base = dirname($this->path);
    $base = $base === '.' ? '' : "{$base}/";
    array_walk_recursive($paths, function (&$path) use ($base) {
      $path = "{$base}{$path}";
    });
    return $paths;
  }

  /**
   * Extract and merge stability flags from the given collection of
   * requires and merge them into a RootPackageInterface
   *
   * @param RootPackageInterface $root
   * @param array $requires
   */
  protected function mergeStabilityFlags(RootPackageInterface $root, array $requires) {
    $flags = $root
      ->getStabilityFlags();
    $sf = new StabilityFlags($flags, $root
      ->getMinimumStability());
    $unwrapped = self::unwrapIfNeeded($root, 'setStabilityFlags');
    $unwrapped
      ->setStabilityFlags(array_merge($flags, $sf
      ->extractAll($requires)));
  }

  /**
   * Merge package links of the given type  into a RootPackageInterface
   *
   * @param string $type 'conflict', 'replace' or 'provide'
   * @param RootPackageInterface $root
   */
  protected function mergePackageLinks($type, RootPackageInterface $root) {
    $linkType = BasePackage::$supportedLinkTypes[$type];
    $getter = 'get' . ucfirst($linkType['method']);
    $setter = 'set' . ucfirst($linkType['method']);
    $links = $this->package
      ->{$getter}();
    if (!empty($links)) {
      $unwrapped = self::unwrapIfNeeded($root, $setter);
      if ($root !== $unwrapped) {
        $this->logger
          ->warning('This Composer version does not support ' . "'{$type}' merging for aliased packages.");
      }
      $unwrapped
        ->{$setter}(array_merge($root
        ->{$getter}(), $this
        ->replaceSelfVersionDependencies($type, $links, $root)));
    }
  }

  /**
   * Merge suggested packages into a RootPackageInterface
   *
   * @param RootPackageInterface $root
   */
  protected function mergeSuggests(RootPackageInterface $root) {
    $suggests = $this->package
      ->getSuggests();
    if (!empty($suggests)) {
      $unwrapped = self::unwrapIfNeeded($root, 'setSuggests');
      $unwrapped
        ->setSuggests(array_merge($root
        ->getSuggests(), $suggests));
    }
  }

  /**
   * Merge extra config into a RootPackageInterface
   *
   * @param RootPackageInterface $root
   * @param PluginState $state
   */
  public function mergeExtra(RootPackageInterface $root, PluginState $state) {
    $extra = $this->package
      ->getExtra();
    unset($extra['merge-plugin']);
    if (!$state
      ->shouldMergeExtra() || empty($extra)) {
      return;
    }
    $rootExtra = $root
      ->getExtra();
    $unwrapped = self::unwrapIfNeeded($root, 'setExtra');
    if ($state
      ->replaceDuplicateLinks()) {
      $unwrapped
        ->setExtra(array_merge($rootExtra, $extra));
    }
    else {
      foreach (array_intersect(array_keys($extra), array_keys($rootExtra)) as $key) {
        $this->logger
          ->info("Ignoring duplicate <comment>{$key}</comment> in " . "<comment>{$this->path}</comment> extra config.");
      }
      $unwrapped
        ->setExtra(array_merge($extra, $rootExtra));
    }
  }

  /**
   * Update Links with a 'self.version' constraint with the root package's
   * version.
   *
   * @param string $type Link type
   * @param array $links
   * @param RootPackageInterface $root
   * @return array
   */
  protected function replaceSelfVersionDependencies($type, array $links, RootPackageInterface $root) {
    $linkType = BasePackage::$supportedLinkTypes[$type];
    $version = $root
      ->getVersion();
    $prettyVersion = $root
      ->getPrettyVersion();
    $vp = new VersionParser();
    return array_map(function ($link) use ($linkType, $version, $prettyVersion, $vp) {
      if ('self.version' === $link
        ->getPrettyConstraint()) {
        return new Link($link
          ->getSource(), $link
          ->getTarget(), $vp
          ->parseConstraints($version), $linkType['description'], $prettyVersion);
      }
      return $link;
    }, $links);
  }

  /**
   * Get a full featured Package from a RootPackageInterface.
   *
   * In Composer versions before 599ad77 the RootPackageInterface only
   * defines a sub-set of operations needed by composer-merge-plugin and
   * RootAliasPackage only implemented those methods defined by the
   * interface. Most of the unimplemented methods in RootAliasPackage can be
   * worked around because the getter methods that are implemented proxy to
   * the aliased package which we can modify by unwrapping. The exception
   * being modifying the 'conflicts', 'provides' and 'replaces' collections.
   * We have no way to actually modify those collections unfortunately in
   * older versions of Composer.
   *
   * @param RootPackageInterface $root
   * @param string $method Method needed
   * @return RootPackageInterface|RootPackage
   */
  public static function unwrapIfNeeded(RootPackageInterface $root, $method = 'setExtra') {
    if ($root instanceof RootAliasPackage && !method_exists($root, $method)) {

      // Unwrap and return the aliased RootPackage.
      $root = $root
        ->getAliasOf();
    }
    return $root;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ExtraPackage::$composer protected property
ExtraPackage::$json protected property
ExtraPackage::$logger protected property
ExtraPackage::$package protected property
ExtraPackage::$path protected property
ExtraPackage::addRepositories protected function Add a collection of repositories described by the given configuration to the given package and the global repository manager.
ExtraPackage::fixRelativePaths protected function Fix a collection of paths that are relative to this package to be relative to the base package.
ExtraPackage::getIncludes public function Get list of additional packages to include if precessing recursively.
ExtraPackage::getRequires public function Get list of additional packages to require if precessing recursively.
ExtraPackage::loadPackage protected function
ExtraPackage::mergeAutoload protected function Merge autoload or autoload-dev into a RootPackageInterface
ExtraPackage::mergeExtra public function Merge extra config into a RootPackageInterface
ExtraPackage::mergeInto public function Merge this package into a RootPackageInterface
ExtraPackage::mergeOrDefer protected function Merge two collections of package links and collect duplicates for subsequent processing.
ExtraPackage::mergePackageLinks protected function Merge package links of the given type into a RootPackageInterface
ExtraPackage::mergeRequires protected function Merge require or require-dev into a RootPackageInterface
ExtraPackage::mergeStabilityFlags protected function Extract and merge stability flags from the given collection of requires and merge them into a RootPackageInterface
ExtraPackage::mergeSuggests protected function Merge suggested packages into a RootPackageInterface
ExtraPackage::readPackageJson protected function Read the contents of a composer.json style file into an array.
ExtraPackage::replaceSelfVersionDependencies protected function Update Links with a 'self.version' constraint with the root package's version.
ExtraPackage::unwrapIfNeeded public static function Get a full featured Package from a RootPackageInterface.
ExtraPackage::__construct public function