src/Bowerphp/Bowerphp.php
<?php
/*
* This file is part of Bowerphp.
*
* (c) Massimiliano Arione <massimiliano.arione@bee-lab.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Bowerphp;
use Bowerphp\Config\ConfigInterface;
use Bowerphp\Installer\InstallerInterface;
use Bowerphp\Output\BowerphpConsoleOutput;
use Bowerphp\Package\Package;
use Bowerphp\Package\PackageInterface;
use Bowerphp\Repository\RepositoryInterface;
use Bowerphp\Util\Filesystem;
use Github\Client;
use Guzzle\Http\Exception\RequestException;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Finder\Finder;
use vierbergenlars\SemVer\expression;
use vierbergenlars\SemVer\version;
/**
* Main class
*/
class Bowerphp
{
protected $config;
protected $filesystem;
protected $githubClient;
protected $repository;
protected $output;
/**
* @param ConfigInterface $config
* @param Filesystem $filesystem
* @param Client $githubClient
* @param RepositoryInterface $repository
* @param BowerphpConsoleOutput $output
*/
public function __construct(
ConfigInterface $config,
Filesystem $filesystem,
Client $githubClient,
RepositoryInterface $repository,
BowerphpConsoleOutput $output
) {
$this->config = $config;
$this->filesystem = $filesystem;
$this->githubClient = $githubClient;
$this->repository = $repository;
$this->output = $output;
}
/**
* Init bower.json
*
* @param array $params
*/
public function init(array $params)
{
if ($this->config->bowerFileExists()) {
$bowerJson = $this->config->getBowerFileContent();
$this->config->setSaveToBowerJsonFile();
$this->config->updateBowerJsonFile2($bowerJson, $params);
} else {
$this->config->initBowerJsonFile($params);
}
}
/**
* Install a single package
*
* @param PackageInterface $package
* @param InstallerInterface $installer
* @param bool $isDependency
*/
public function installPackage(PackageInterface $package, InstallerInterface $installer, $isDependency = false)
{
if (false !== strpos($package->getName(), 'github')) {
// install from a github endpoint
$name = basename($package->getName(), '.git');
$repoUrl = $package->getName();
$package = new Package($name, $package->getRequiredVersion());
$this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
$package->setRepository($this->repository);
$packageTag = $this->repository->findPackage($package->getRequiredVersion());
if (is_null($packageTag)) {
throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
}
} else {
$packageTag = $this->getPackageTag($package, true);
$package->setRepository($this->repository);
}
$package->setVersion($packageTag);
$this->updateBowerFile($package, $isDependency);
// if package is already installed, match current version with latest available version
if ($this->isPackageInstalled($package)) {
$this->output->writelnInfoPackage($package, 'validate', sprintf('%s against %s#%s', $packageTag, $package->getName(), $package->getRequiredVersion()));
$packageBower = $this->config->getPackageBowerFileContent($package);
if ($packageTag == $packageBower['version']) {
// if version is fully matching, there's no need to install
return;
}
}
$this->cachePackage($package);
$this->output->writelnInstalledPackage($package);
$installer->install($package);
$overrides = $this->config->getOverrideFor($package->getName());
if (array_key_exists('dependencies', $overrides)) {
$dependencies = $overrides['dependencies'];
} else {
$dependencies = $package->getRequires();
}
if (!empty($dependencies)) {
foreach ($dependencies as $name => $version) {
$depPackage = new Package($name, $version);
if (!$this->isPackageInstalled($depPackage)) {
$this->installPackage($depPackage, $installer, true);
} elseif ($this->isNeedUpdate($depPackage)) {
$this->updatePackage($depPackage, $installer);
}
}
}
}
/**
* Install all dependencies
*
* @param InstallerInterface $installer
*/
public function installDependencies(InstallerInterface $installer)
{
$decode = $this->config->getBowerFileContent();
if (!empty($decode['dependencies'])) {
foreach ($decode['dependencies'] as $name => $requiredVersion) {
if (false !== strpos($requiredVersion, 'github')) {
list($name, $requiredVersion) = explode('#', $requiredVersion);
}
$package = new Package($name, $requiredVersion);
$this->installPackage($package, $installer, true);
}
}
}
/**
* Update a single package
*
* @param PackageInterface $package
* @param InstallerInterface $installer
*/
public function updatePackage(PackageInterface $package, InstallerInterface $installer)
{
if (!$this->isPackageInstalled($package)) {
throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
}
if (is_null($package->getRequiredVersion())) {
$decode = $this->config->getBowerFileContent();
if (empty($decode['dependencies']) || empty($decode['dependencies'][$package->getName()])) {
throw new InvalidArgumentException(sprintf('Package %s not found in bower.json', $package->getName()));
}
$package->setRequiredVersion($decode['dependencies'][$package->getName()]);
}
$bower = $this->config->getPackageBowerFileContent($package);
$package->setInfo($bower);
$package->setVersion($bower['version']);
$package->setRequires(isset($bower['dependencies']) ? $bower['dependencies'] : null);
$packageTag = $this->getPackageTag($package);
$this->output->writelnInfoPackage($package, 'validate', sprintf('%s against %s#%s', $packageTag, $package->getName(), $package->getRequiredVersion()));
$package->setRepository($this->repository);
if ($packageTag == $package->getVersion()) {
// if version is fully matching, there's no need to update
return;
}
$package->setVersion($packageTag);
$this->cachePackage($package);
$this->output->writelnUpdatingPackage($package);
$installer->update($package);
$overrides = $this->config->getOverrideFor($package->getName());
if (array_key_exists('dependencies', $overrides)) {
$dependencies = $overrides['dependencies'];
} else {
$dependencies = $package->getRequires();
}
if (!empty($dependencies)) {
foreach ($dependencies as $name => $requiredVersion) {
$depPackage = new Package($name, $requiredVersion);
if (!$this->isPackageInstalled($depPackage)) {
$this->installPackage($depPackage, $installer, true);
} elseif ($this->isNeedUpdate($depPackage)) {
$this->updatePackage($depPackage, $installer);
}
}
}
}
/**
* Update all dependencies
*
* @param InstallerInterface $installer
*/
public function updatePackages(InstallerInterface $installer)
{
$decode = $this->config->getBowerFileContent();
if (!empty($decode['dependencies'])) {
foreach ($decode['dependencies'] as $packageName => $requiredVersion) {
$this->updatePackage(new Package($packageName, $requiredVersion), $installer);
}
}
}
/**
* @param PackageInterface $package
* @param string $info
* @return mixed
*/
public function getPackageInfo(PackageInterface $package, $info = 'url')
{
$decode = $this->lookupPackage($package->getName());
$this->repository->setHttpClient($this->githubClient);
if ('url' == $info) {
$this->repository->setUrl($decode['url'], false);
return $this->repository->getUrl();
}
if ('versions' == $info) {
$tags = $this->repository->getTags();
usort($tags, function ($a, $b) {
return version_compare($b, $a);
});
return $tags;
}
throw new RuntimeException(sprintf('Unsupported info option "%s".', $info));
}
/**
* @param string $name
* @return array
*/
public function lookupPackage($name)
{
return $this->findPackage($name);
}
/**
* @param PackageInterface $package
* @return string
*/
public function getPackageBowerFile(PackageInterface $package)
{
$this->repository->setHttpClient($this->githubClient);
$lookupPackage = $this->lookupPackage($package->getName());
$this->repository->setUrl($lookupPackage['url'], false);
$tag = $this->repository->findPackage($package->getRequiredVersion());
return $this->repository->getBower($tag, true, $lookupPackage['url']);
}
/**
* Uninstall a single package
*
* @param PackageInterface $package
* @param InstallerInterface $installer
*/
public function uninstallPackage(PackageInterface $package, InstallerInterface $installer)
{
if (!$this->isPackageInstalled($package)) {
throw new RuntimeException(sprintf('Package %s is not installed.', $package->getName()));
}
$installer->uninstall($package);
}
/**
* Search packages by name
*
* @param string $name
* @return array
*/
public function searchPackages($name)
{
try {
$url = $this->config->getBasePackagesUrl() . 'search/' . $name;
$response = $this->githubClient->getHttpClient()->get($url);
return json_decode($response->getBody(true), true);
} catch (RequestException $e) {
throw new RuntimeException(sprintf('Cannot get package list from %s.', str_replace('/packages/', '', $this->config->getBasePackagesUrl())));
}
}
/**
* Get a list of installed packages
*
* @param InstallerInterface $installer
* @param Finder $finder
* @return array
*/
public function getInstalledPackages(InstallerInterface $installer, Finder $finder)
{
return $installer->getInstalled($finder);
}
/**
* Check if package is installed
*
* @param PackageInterface $package
* @return bool
*/
public function isPackageInstalled(PackageInterface $package)
{
return $this->filesystem->exists($this->config->getInstallDir() . '/' . $package->getName() . '/.bower.json');
}
/**
* @param PackageInterface $package
* @param bool $checkInstall
*
* @return bool
*/
public function isPackageExtraneous(PackageInterface $package, $checkInstall = false)
{
if ($checkInstall && !$this->isPackageInstalled($package)) {
return false;
}
try {
$bower = $this->config->getBowerFileContent();
} catch (RuntimeException $e) { // no bower.json file, package is extraneous
return true;
}
if (!isset($bower['dependencies'])) {
return true;
}
// package is a direct dependencies
if (isset($bower['dependencies'][$package->getName()])) {
return false;
}
// look for dependencies of dependencies
foreach ($bower['dependencies'] as $name => $version) {
$dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name . '/.bower.json');
$depBower = json_decode($dotBowerJson, true);
if (isset($depBower['dependencies'][$package->getName()])) {
return false;
}
// look for dependencies of dependencies of dependencies
if (isset($depBower['dependencies'])) {
foreach ($depBower['dependencies'] as $name1 => $version1) {
$dotBowerJson = $this->filesystem->read($this->config->getInstallDir() . '/' . $name1 . '/.bower.json');
$depDepBower = json_decode($dotBowerJson, true);
if (isset($depDepBower['dependencies'][$package->getName()])) {
return false;
}
}
}
}
return true;
}
/**
* @param array $params
* @return array
*/
protected function createAClearBowerFile(array $params)
{
$authors = ['Beelab <info@bee-lab.net>'];
if (!empty($params['author'])) {
$authors[] = $params['author'];
}
$structure = [
'name' => $params['name'],
'authors' => $authors,
'private' => true,
'dependencies' => new \StdClass(),
];
return $structure;
}
/**
* @param PackageInterface $package
* @param bool $setInfo
* @return string
*/
protected function getPackageTag(PackageInterface $package, $setInfo = false)
{
$decode = $this->findPackage($package->getName());
// open package repository
$repoUrl = $decode['url'];
$this->repository->setUrl($repoUrl)->setHttpClient($this->githubClient);
$packageTag = $this->repository->findPackage($package->getRequiredVersion());
if (is_null($packageTag)) {
throw new RuntimeException(sprintf('Cannot find package %s version %s.', $package->getName(), $package->getRequiredVersion()));
}
$bowerJson = $this->repository->getBower($packageTag);
$bower = json_decode($bowerJson, true);
if (!is_array($bower)) {
throw new RuntimeException(sprintf('Invalid bower.json found in package %s: %s.', $package->getName(), $bowerJson));
}
if ($setInfo) {
$package->setInfo($bower);
}
return $packageTag;
}
/**
* @param string $name
* @return array
*/
protected function findPackage($name)
{
try {
$response = $this->githubClient->getHttpClient()->get($this->config->getBasePackagesUrl() . urlencode($name));
} catch (RuntimeException $e) {
throw new RuntimeException(sprintf('Cannot fetch registry info for package %s from search registry (%s).', $name, $e->getMessage()));
}
$packageInfo = json_decode($response->getBody(true), true);
if (!is_array($packageInfo) || empty($packageInfo['url'])) {
throw new RuntimeException(sprintf('Registry info for package %s has malformed json or is missing "url".', $name));
}
return $packageInfo;
}
/**
* @param PackageInterface $package
*/
private function cachePackage(PackageInterface $package)
{
$this->output->writelnInfoPackage($package, 'download');
// get release archive from repository
$file = $this->repository->getRelease();
$tmpFileName = $this->config->getCacheDir() . '/tmp/' . $package->getName();
$this->filesystem->write($tmpFileName, $file);
}
/**
* @param PackageInterface $package
* @param bool $isDependency
*/
private function updateBowerFile(PackageInterface $package, $isDependency = false)
{
if ($this->config->isSaveToBowerJsonFile() && !$isDependency) {
try {
$this->config->updateBowerJsonFile($package);
} catch (RuntimeException $e) {
$this->output->writelnNoBowerJsonFile();
}
}
}
/**
* Update only if needed is greater version
*
* @param PackageInterface $package
* @return bool
*/
public function isNeedUpdate($package)
{
$packageBower = $this->config->getPackageBowerFileContent($package);
$semver = new version($packageBower['version']);
return !$semver->satisfies(new expression($package->getRequiredVersion()));
}
}