/sally/core/lib/sly/Service/Package.php
PHP | 421 lines | 215 code | 70 blank | 136 comment | 31 complexity | b92f43155d75f2ae8b9c8c2e235d577b MD5 | raw file
- <?php
- /*
- * Copyright (c) 2013, webvariants GbR, http://www.webvariants.de
- *
- * This file is released under the terms of the MIT license. You can find the
- * complete text in the attached LICENSE file or online at:
- *
- * http://www.opensource.org/licenses/mit-license.php
- */
- /**
- * @author christoph@webvariants.de
- * @ingroup service
- */
- class sly_Service_Package {
- protected $sourceDir; ///< string
- protected $cache; ///< BabelCache_Interface
- protected $composers; ///< array
- protected $refreshed; ///< boolean
- protected $namespace; ///< string
- /**
- * @param string $sourceDir
- * @param BabelCache_Interface $cache
- */
- public function __construct($sourceDir, BabelCache_Interface $cache) {
- $this->sourceDir = sly_Util_Directory::normalize($sourceDir).DIRECTORY_SEPARATOR;
- $this->cache = $cache;
- $this->composers = array();
- $this->refreshed = false;
- $this->namespace = substr(md5($this->sourceDir), 0, 10).'_';
- }
- /**
- * Clears the addOn metadata cache
- */
- public function clearCache() {
- $this->cache->flush('sly.package', true);
- $this->composers = array();
- $this->refreshed = false;
- }
- /**
- * @param string $package package name
- * @return string
- */
- public function baseDirectory($package = null) {
- $dir = $this->sourceDir;
- if (!empty($package)) {
- $dir .= str_replace('/', DIRECTORY_SEPARATOR, $package).DIRECTORY_SEPARATOR;
- }
- return $dir;
- }
- protected function getCacheKey($package, $key) {
- $package = str_replace('/', '%', $package);
- return ($package ? $package.'%' : '').$key;
- }
- protected function getCache($package, $key, $default = null) {
- return $this->cache->get('sly.package.'.$this->namespace, $this->getCacheKey($package, $key), $default);
- }
- protected function setCache($package, $key, $value) {
- return $this->cache->set('sly.package.'.$this->namespace, $this->getCacheKey($package, $key), $value);
- }
- /**
- * @param string $package package name
- * @param boolean $forceRefresh true to not use the cache and check if the composer.json is present
- * @return boolean
- */
- public function exists($package, $forceRefresh = false) {
- $exists = $forceRefresh ? null : $this->getCache($package, 'exists');
- if ($exists === null) {
- $base = $this->baseDirectory($package);
- $exists = file_exists($base.'composer.json');
- $this->setCache($package, 'exists', $exists);
- }
- return $exists;
- }
- /**
- * Get package author
- *
- * @param string $package package name
- * @param mixed $default default value if no author was specified in composer.json
- * @return mixed the author as given in static.yml
- */
- public function getAuthor($package, $default = null) {
- $authors = $this->getKey($package, 'authors', null);
- if (!is_array($authors) || empty($authors)) return $default;
- $first = reset($authors);
- return isset($first['name']) ? $first['name'] : $default;
- }
- /**
- * Get support page
- *
- * @param string $package package name
- * @param mixed $default default value if no page was specified in composer.json
- * @return mixed the support page as given in static.yml
- */
- public function getHomepage($package, $default = null) {
- return $this->getKey($package, 'homepage', $default);
- }
- /**
- * Get parent package (only relevant for backend list)
- *
- * @param string $package package name
- * @param mixed $default default value if no page was specified in composer.json
- * @return mixed the parent package or null if not given
- */
- public function getParent($package) {
- return $this->getKey($package, 'parent', null);
- }
- /**
- * Get children packages (only relevant for backend list)
- *
- * @param string $package parent package name
- * @return array
- */
- public function getChildren($parent) {
- $packages = $this->getPackages();
- $children = array();
- foreach ($packages as $package) {
- if ($this->getParent($package) === $parent) {
- $children[] = $package;
- }
- }
- return $children;
- }
- /**
- * Get version
- *
- * @param string $package package name
- * @param mixed $default default value if no version was specified
- * @return string the version
- */
- public function getVersion($package, $default = null) {
- return $this->getKey($package, 'version', $default);
- }
- /**
- * @param string $package package name
- * @return string e.g. 'package/vendor:package'
- */
- public function getVersionKey($package) {
- return 'package/'.str_replace('/', ':', $package);
- }
- /**
- * Get last known version
- *
- * This method reads the last known version from the local config. This can
- * be used to determine whether a package has been updated.
- *
- * @param string $package package name
- * @param mixed $default default value if no version was specified
- * @return string the version
- */
- public function getKnownVersion($package, $default = null) {
- $key = $this->getVersionKey($package);
- $version = sly_Util_Versions::get($key);
- return $version === false ? $default : $version;
- }
- /**
- * Set last known version
- *
- * @param string $package package name
- * @param string $version new version
- * @return string the version
- */
- public function setKnownVersion($package, $version) {
- $key = $this->getVersionKey($package);
- return sly_Util_Versions::set($key, $version);
- }
- /**
- * Read a config value from the composer.json
- *
- * @param string $package package name
- * @param string $key array key
- * @param mixed $default value if key is not set
- * @return mixed value or default
- */
- public function getKey($package, $key, $default = null) {
- if (!isset($this->composers[$package])) {
- $composer = $this->getCache($package, 'composer.json');
- if ($composer === null) {
- $filename = $this->baseDirectory($package).'composer.json';
- $composer = new sly_Util_Composer($filename);
- $composer->setPackage($package);
- $composer->getContent($this->baseDirectory().'composer'); // read file
- $this->setCache($package, 'composer.json', $composer);
- }
- elseif (sly_Core::isDeveloperMode() && $composer->revalidate()) {
- $this->setCache($package, 'composer.json', $composer);
- }
- $this->composers[$package] = $composer;
- }
- $value = $this->composers[$package]->getKey($key);
- return $value === null ? $default : $value;
- }
- /**
- * Return a list of required packages
- *
- * Required packages are packages that $package itself needs to run.
- *
- * @param string $package package name
- * @param boolean $recursive if true, requirements are search recursively
- * @param array $ignore list of packages to ignore (and not recurse into)
- * @return array list of required packages
- */
- public function getRequirements($package, $recursive = true, array $ignore = array()) {
- $cacheKey = 'requirements_'.($recursive ? 1 : 0);
- $result = $this->getCache($package, $cacheKey);
- if ($result !== null) {
- // apply ignore list
- foreach ($ignore as $i) {
- $idx = array_search($i, $result);
- if ($idx !== false) unset($result[$idx]);
- }
- return array_values($result);
- }
- $stack = array($package);
- $stack = array_merge($stack, array_keys($this->getKey($package, 'require', array())));
- $result = array();
- // don't add self
- $ignore[] = $package;
- do {
- // take one out
- $pkg = array_shift($stack);
- // add its requirements
- if ($this->exists($pkg) && $recursive) {
- $stack = array_merge($stack, array_keys($this->getKey($pkg, 'require', array())));
- $stack = array_unique($stack);
- }
- // filter out non-packages
- foreach ($stack as $idx => $req) {
- if (strpos($req, '/') === false) unset($stack[$idx]);
- }
- // respect ignore list
- foreach ($ignore as $i) {
- $idx = array_search($i, $stack);
- if ($idx !== false) unset($stack[$idx]);
- }
- // do not add $package itself or duplicates
- if ($pkg !== $package && !in_array($pkg, $result)) {
- $result[] = $pkg;
- }
- }
- while (!empty($stack));
- natcasesort($result);
- $this->setCache($package, $cacheKey, $result);
- return $result;
- }
- /**
- * Return a list of dependent packages
- *
- * Dependent packages are packages that need $package to run.
- *
- * @param string $package package name
- * @param boolean $recursive if true, dependencies are search recursively
- * @return array list of required packages
- */
- public function getDependencies($package, $recursive = true) {
- $cacheKey = 'dependencies_'.($recursive ? 1 : 0);
- $result = $this->getCache($package, $cacheKey);
- if ($result !== null) {
- return $result;
- }
- $all = $this->getPackages();
- $stack = array($package);
- $result = array();
- do {
- // take one out
- $pkg = array_shift($stack);
- // find packages requiering $pkg
- foreach ($all as $p) {
- $requirements = array_keys($this->getKey($p, 'require', array()));
- if (in_array($pkg, $requirements)) {
- $result[] = $p;
- $stack[] = $p;
- }
- }
- $stack = array_unique($stack);
- }
- while ($recursive && !empty($stack));
- $result = array_unique($result);
- natcasesort($result);
- $this->setCache($package, $cacheKey, $result);
- return $result;
- }
- /**
- * @return array list of packages (cached if possible)
- */
- public function getPackages() {
- $packages = $this->getCache('', 'packages');
- if ($packages === null || ($this->refreshed === false && sly_Core::isDeveloperMode())) {
- $packages = $this->findPackages();
- $this->refreshed = true;
- $this->setCache('', 'packages', $packages);
- }
- return $packages;
- }
- /**
- * @return array list of found packages
- */
- public function findPackages() {
- $root = $this->baseDirectory();
- $packages = array();
- // If we're in a real composer vendor directory, there is a installed.json,
- // that contains a list of all packages. We use this to detect packages
- // have no composer.json themselves (aka leafo/lessphp).
- // On the other hand, we must make sure that we only read those packages
- // that are actually inside $root, as the installed.json will contain data
- // about *all* packages (i.e. for vendors and addons)!
- $installed = $root.'composer/installed.json';
- if (file_exists($installed)) {
- $data = sly_Util_JSON::load($installed);
- foreach ($data as $pkg) {
- if (is_dir($root.$pkg['name'])) {
- $packages[] = $pkg['name'];
- }
- }
- $installed = $root.'composer/installed_dev.json';
- if (file_exists($installed)) {
- $data = sly_Util_JSON::load($installed);
- foreach ($data as $pkg) {
- if (is_dir($root.$pkg['name'])) {
- $packages[] = $pkg['name'];
- }
- }
- }
- }
- // In addition to the installed.json, we should also scan the filesystem
- // for valid packages. This makes it *much* easier to develop addOns
- // and not modify and update your composer files over and over again.
- $dirs = $this->readDir($root);
- foreach ($dirs as $dir) {
- // evil package not conforming to naming convention
- if ($this->exists($dir, true)) {
- $packages[] = $dir;
- }
- else {
- $subdirs = $this->readDir($root.$dir);
- foreach ($subdirs as $subdir) {
- // good package
- if ($this->exists($dir.'/'.$subdir, true)) {
- $packages[] = $dir.'/'.$subdir;
- }
- }
- }
- }
- $packages = array_unique($packages);
- natcasesort($packages);
- return array_values($packages);
- }
- private function readDir($dir) {
- $dir = new sly_Util_Directory($dir);
- return $dir->exists() ? $dir->listPlain(false, true) : array();
- }
- }