From 7d994b5de4db36c0e17c5c2e9b2f7478de82d320 Mon Sep 17 00:00:00 2001 From: Artem Lopata Date: Fri, 27 Jan 2012 10:29:07 +0200 Subject: [PATCH 01/25] Added ErrorHandler for handling E_NOTICE, E_WARNING, E_ERROR --- src/Composer/Console/Application.php | 2 + src/Composer/Util/ErrorHandler.php | 52 +++++++++++++++++++ tests/Composer/Test/Util/ErrorHandlerTest.php | 49 +++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 src/Composer/Util/ErrorHandler.php create mode 100644 tests/Composer/Test/Util/ErrorHandlerTest.php diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 745d5510d..aeddf82ce 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -24,6 +24,7 @@ use Composer\Composer; use Composer\Factory; use Composer\IO\IOInterface; use Composer\IO\ConsoleIO; +use Composer\Util\ErrorHandler; /** * The console application that handles the commands @@ -39,6 +40,7 @@ class Application extends BaseApplication public function __construct() { + ErrorHandler::set(); parent::__construct('Composer', Composer::VERSION); } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php new file mode 100644 index 000000000..cb620e52f --- /dev/null +++ b/src/Composer/Util/ErrorHandler.php @@ -0,0 +1,52 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * Convert PHP E_NOTICE, E_WARNING into exceptions + * + * @author Artem Lopata + */ +class ErrorHandler +{ + /** + * Error handler + * + * @param int $errorNo Level of the error raised + * @param string $errorString Error message + * @param string $errorFile Filename that the error was raised in + * @param int $errorLine Line number the error was raised at + * + * @static + * @throws \ErrorException + */ + public static function handle($errorNo, $errorString, $errorFile, $errorLine) + { + //this allows error suppression in 3rd party code to work + if (!error_reporting()) { + return; + } + + throw new \ErrorException(sprintf('%s in %s:%d', $errorString, $errorFile, $errorLine), $errorNo); + } + + /** + * Set error handler + * + * @static + */ + public static function set() + { + set_error_handler(array(__CLASS__, 'handle')); + } +} diff --git a/tests/Composer/Test/Util/ErrorHandlerTest.php b/tests/Composer/Test/Util/ErrorHandlerTest.php new file mode 100644 index 000000000..6aebc5c15 --- /dev/null +++ b/tests/Composer/Test/Util/ErrorHandlerTest.php @@ -0,0 +1,49 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\ErrorHandler; +use Composer\Test\TestCase; + +/** + * ErrorHandler test case + * + * @author Artem Lopata + */ +class ErrorHandlerTest extends TestCase +{ + /** + * Test ErrorHandler handles notices + */ + public function testErrorHandlerCaptureNotice() + { + $this->setExpectedException('\ErrorException', 'Undefined index: baz in ' . __FILE__); + + ErrorHandler::set(); + + $array = array('foo' => 'bar'); + $array['baz']; + } + + /** + * Test ErrorHandler handles warnings + */ + public function testErrorHandlerCaptureWarning() + { + $this->setExpectedException('\ErrorException', 'array_merge(): Argument #2 is not an array in ' . __FILE__); + + ErrorHandler::set(); + + array_merge(array(), 'string'); + } +} From 2c87fe5a22f148c9743f07a19fbbec15a9c5ee7c Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 18 Mar 2012 20:41:10 +0100 Subject: [PATCH 02/25] Collect solver problems in Problem objects with human readable output. --- src/Composer/DependencyResolver/Problem.php | 149 ++++++++++++++++++ src/Composer/DependencyResolver/Request.php | 1 + src/Composer/DependencyResolver/Solver.php | 87 +++++----- .../SolverProblemsException.php | 47 ++---- .../Test/DependencyResolver/RequestTest.php | 10 +- .../Test/DependencyResolver/SolverTest.php | 16 +- 6 files changed, 227 insertions(+), 83 deletions(-) create mode 100644 src/Composer/DependencyResolver/Problem.php diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php new file mode 100644 index 000000000..385baeef3 --- /dev/null +++ b/src/Composer/DependencyResolver/Problem.php @@ -0,0 +1,149 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * Represents a problem detected while solving dependencies + * + * @author Nils Adermann + */ +class Problem +{ + /** + * A set of reasons for the problem, each is a rule or a job and a rule + * @var array + */ + protected $reasons; + + /** + * Add a job as a reason + * + * @param array $job A job descriptor which is a reason for this problem + * @param Rule $rule An optional rule associated with the job + */ + public function addJobRule($job, Rule $rule = null) + { + $this->addReason(serialize($job), array( + 'rule' => $rule, + 'job' => $job, + )); + } + + /** + * Add a rule as a reason + * + * @param Rule $rule A rule which is a reason for this problem + */ + public function addRule(Rule $rule) + { + $this->addReason($rule->getId(), array( + 'rule' => $rule, + 'job' => null, + )); + } + + /** + * Retrieve all reasons for this problem + * + * @return array The problem's reasons + */ + public function getReasons() + { + return $this->reasons; + } + + /** + * A human readable textual representation of the problem's reasons + */ + public function __toString() + { + if (count($this->reasons) === 1) { + reset($this->reasons); + $reason = current($this->reasons); + + $rule = $reason['rule']; + $job = $reason['job']; + + if ($job && $job['cmd'] === 'install' && empty($job['packages'])) { + return 'The requested package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'could not be found.'; + } + } + + $messages = array("Problem caused by:"); + + foreach ($this->reasons as $reason) { + + $rule = $reason['rule']; + $job = $reason['job']; + + if ($job) { + $messages[] = $this->jobToText($job); + } elseif ($rule) { + if ($rule instanceof Rule) { + $message = ''; + if ($rule->getType() == RuleSet::TYPE_LEARNED) { + $message .= 'learned:'; + } + + $messages[] = $message.$rule; + } + } + } + + return implode("\n\t\t\t- ", $messages); + } + + /** + * Store a reason descriptor but ignore duplicates + * + * @param string $id A canonical identifier for the reason + * @param string $reason The reason descriptor + */ + protected function addReason($id, $reason) + { + if (!isset($this->reasons[$id])) { + $this->reasons[$id] = $reason; + } + } + + /** + * Turns a job into a human readable description + * + * @param array $job + * @return string + */ + protected function jobToText($job) + { + switch ($job['cmd']) { + case 'install': + return 'Installation of package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'was requested. Satisfiable by packages ['.implode(', ', $job['packages']).'].'; + case 'update': + return 'Update of package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'was requested.'; + case 'remove': + return 'Removal of package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'was requested.'; + } + + return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])'; + } + + /** + * Turns a constraint into text usable in a sentence describing a job + * + * @param LinkConstraint $constraint + * @return string + */ + protected function constraintToText($constraint) + { + return ($constraint) ? 'with constraint '.$constraint.' ' : ''; + } +} diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index 3d1b28448..92c8aa175 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -52,6 +52,7 @@ class Request 'packages' => $packages, 'cmd' => $cmd, 'packageName' => $packageName, + 'constraint' => $constraint, ); } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index aa832b113..2b9dc082b 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -466,24 +466,29 @@ class Solver $conflict = $this->findDecisionRule($literal->getPackage()); /** TODO: handle conflict with systemsolvable? */ - $this->learnedPool[] = array($rule, $conflict); - if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) { + $problem = new Problem; + if ($rule->getType() == RuleSet::TYPE_JOB) { - $why = $this->ruleToJob[$rule->getId()]; + $job = $this->ruleToJob[$rule->getId()]; + + $problem->addJobRule($job, $rule); + $problem->addRule($conflict); + $this->disableProblem($job); } else { - $why = $rule; + $problem->addRule($rule); + $problem->addRule($conflict); + $this->disableProblem($rule); } - $this->problems[] = array($why); - - $this->disableProblem($why); + $this->problems[] = $problem; continue; } // conflict with another job or update/feature rule - - $this->problems[] = array(); + $problem = new Problem; + $problem->addRule($rule); + $problem->addRule($conflict); // push all of our rules (can only be feature or job rules) // asserting this literal on the problem stack @@ -500,14 +505,16 @@ class Solver } if ($assertRule->getType() === RuleSet::TYPE_JOB) { - $why = $this->ruleToJob[$assertRule->getId()]; + $job = $this->ruleToJob[$assertRule->getId()]; + + $problem->addJobRule($job, $assertRule); + $this->disableProblem($job); } else { - $why = $assertRule; + $problem->addRule($assertRule); + $this->disableProblem($assertRule); } - $this->problems[count($this->problems) - 1][] = $why; - - $this->disableProblem($why); } + $this->problems[] = $problem; // start over while (count($this->decisionQueue) > $decisionStart) { @@ -977,7 +984,9 @@ class Solver switch ($job['cmd']) { case 'install': if (empty($job['packages'])) { - $this->problems[] = array($job); + $problem = new Problem(); + $problem->addJobRule($job); + $this->problems[] = $problem; } else { $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']); $this->addRule(RuleSet::TYPE_JOB, $rule); @@ -1028,7 +1037,7 @@ class Solver //solver_prepare_solutions(solv); if ($this->problems) { - throw new SolverProblemsException($this->problems, $this->learnedPool); + throw new SolverProblemsException($this->problems); } return $this->createTransaction(); @@ -1492,17 +1501,16 @@ class Solver return array($ruleLevel, $newRule, $why); } - private function analyzeUnsolvableRule($conflictRule, &$lastWeakWhy) + private function analyzeUnsolvableRule($problem, $conflictRule, &$lastWeakWhy) { $why = $conflictRule->getId(); if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) { - $learnedWhy = $this->learnedWhy[$why]; - $problem = $this->learnedPool[$learnedWhy]; + $problemRules = $this->learnedPool[$learnedWhy]; - foreach ($problem as $problemRule) { - $this->analyzeUnsolvableRule($problemRule, $lastWeakWhy); + foreach ($problemRules as $problemRule) { + $this->analyzeUnsolvableRule($problem, $problemRule, $lastWeakWhy); } return; } @@ -1520,24 +1528,22 @@ class Solver } if ($conflictRule->getType() == RuleSet::TYPE_JOB) { - $why = $this->ruleToJob[$conflictRule->getId()]; - } - - // if this problem was already found skip it - if (in_array($why, $this->problems[count($this->problems) - 1], true)) { - return; + $job = $this->ruleToJob[$conflictRule->getId()]; + $problem->addJobRule($job, $conflictRule); + } else { + $problem->addRule($conflictRule); } - - $this->problems[count($this->problems) - 1][] = $why; } private function analyzeUnsolvable($conflictRule, $disableRules) { $lastWeakWhy = null; - $this->problems[] = array(); - $this->learnedPool[] = array($conflictRule); + $problem = new Problem; + $problem->addRule($conflictRule); + + $this->analyzeUnsolvableRule($problem, $conflictRule, $lastWeakWhy); - $this->analyzeUnsolvableRule($conflictRule, $lastWeakWhy); + $this->problems[] = $problem; $seen = array(); $literals = $conflictRule->getLiterals(); @@ -1569,9 +1575,9 @@ class Solver } $why = $this->decisionQueueWhy[$decisionId]; - $this->learnedPool[count($this->learnedPool) - 1][] = $why; + $problem->addRule($why); - $this->analyzeUnsolvableRule($why, $lastWeakWhy); + $this->analyzeUnsolvableRule($problem, $why, $lastWeakWhy); $literals = $why->getLiterals(); /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed @@ -1591,7 +1597,6 @@ class Solver if ($lastWeakWhy) { array_pop($this->problems); - array_pop($this->learnedPool); if ($lastWeakWhy->getType() === RuleSet::TYPE_JOB) { $why = $this->ruleToJob[$lastWeakWhy]; @@ -1616,8 +1621,12 @@ class Solver } if ($disableRules) { - foreach ($this->problems[count($this->problems) - 1] as $why) { - $this->disableProblem($why); + foreach ($this->problems[count($this->problems) - 1] as $reason) { + if ($reason['job']) { + $this->disableProblem($reason['job']); + } else { + $this->disableProblem($reason['rule']); + } } $this->resetSolver(); @@ -1670,10 +1679,10 @@ class Solver { foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) { $why = $this->learnedWhy[$rule->getId()]; - $problem = $this->learnedPool[$why]; + $problemRules = $this->learnedPool[$why]; $foundDisabled = false; - foreach ($problem as $problemRule) { + foreach ($problemRules as $problemRule) { if ($problemRule->disabled()) { $foundDisabled = true; break; diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index cbc4fd571..d558aa122 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -19,47 +19,26 @@ class SolverProblemsException extends \RuntimeException { protected $problems; - public function __construct(array $problems, array $learnedPool) + public function __construct(array $problems) { - $message = ''; - foreach ($problems as $i => $problem) { - $message .= '['; - foreach ($problem as $why) { + $this->problems = $problems; - if (is_int($why) && isset($learnedPool[$why])) { - $rules = $learnedPool[$why]; - } else { - $rules = $why; - } + parent::__construct($this->createMessage()); + } + + protected function createMessage() + { + $messages = array(); - if (isset($rules['packages'])) { - $message .= $this->jobToText($rules); - } else { - $message .= '('; - foreach ($rules as $rule) { - if ($rule instanceof Rule) { - if ($rule->getType() == RuleSet::TYPE_LEARNED) { - $message .= 'learned: '; - } - $message .= $rule . ', '; - } else { - $message .= 'String(' . $rule . '), '; - } - } - $message .= ')'; - } - $message .= ', '; - } - $message .= "]\n"; + foreach ($this->problems as $problem) { + $messages[] = (string) $problem; } - parent::__construct($message); + return "\n\tProblems:\n\t\t- ".implode("\n\t\t- ", $messages); } - public function jobToText($job) + public function getProblems() { - //$output = serialize($job); - $output = 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])'; - return $output; + return $this->problems; } } diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index d11c3c427..969240860 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -40,9 +40,9 @@ class RequestTest extends TestCase $this->assertEquals( array( - array('packages' => array($foo), 'cmd' => 'install', 'packageName' => 'foo'), - array('packages' => array($bar), 'cmd' => 'install', 'packageName' => 'bar'), - array('packages' => array($foobar), 'cmd' => 'remove', 'packageName' => 'foobar'), + array('packages' => array($foo), 'cmd' => 'install', 'packageName' => 'foo', 'constraint' => null), + array('packages' => array($bar), 'cmd' => 'install', 'packageName' => 'bar', 'constraint' => null), + array('packages' => array($foobar), 'cmd' => 'remove', 'packageName' => 'foobar', 'constraint' => null), ), $request->getJobs()); } @@ -63,11 +63,11 @@ class RequestTest extends TestCase $pool->addRepository($repo2); $request = new Request($pool); - $request->install('foo', $this->getVersionConstraint('=', '1')); + $request->install('foo', $constraint = $this->getVersionConstraint('=', '1')); $this->assertEquals( array( - array('packages' => array($foo1, $foo2), 'cmd' => 'install', 'packageName' => 'foo'), + array('packages' => array($foo1, $foo2), 'cmd' => 'install', 'packageName' => 'foo', 'constraint' => $constraint), ), $request->getJobs() ); diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index fc7dce7a1..9132fe4e5 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -59,13 +59,15 @@ class SolverTest extends TestCase $this->repo->addPackage($this->getPackage('A', '1.0')); $this->reposComplete(); - $this->request->install('B'); + $this->request->install('B', $this->getVersionConstraint('=', '1')); try { $transaction = $this->solver->solve($this->request); - $this->fail('Unsolvable conflict did not resolve in exception.'); + $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { - // TODO assert problem properties + $problems = $e->getProblems(); + $this->assertEquals(1, count($problems)); + $this->assertEquals('The requested package "b" with constraint == 1.0.0.0 could not be found.', (string) $problems[0]); } } @@ -589,8 +591,10 @@ class SolverTest extends TestCase try { $transaction = $this->solver->solve($this->request); - $this->fail('Unsolvable conflict did not resolve in exception.'); + $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { + $problems = $e->getProblems(); + $this->assertEquals(1, count($problems)); // TODO assert problem properties } } @@ -610,8 +614,10 @@ class SolverTest extends TestCase try { $transaction = $this->solver->solve($this->request); - $this->fail('Unsolvable conflict did not resolve in exception.'); + $this->fail('Unsolvable conflict did not result in exception.'); } catch (SolverProblemsException $e) { + $problems = $e->getProblems(); + $this->assertEquals(1, count($problems)); // TODO assert problem properties } } From 83123b2d402cb2618528f84bdb7ed855670f804b Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 18 Mar 2012 21:09:26 +0100 Subject: [PATCH 03/25] Generate human readable explanations of rules which cause problems --- src/Composer/DependencyResolver/Problem.php | 7 +- src/Composer/DependencyResolver/Rule.php | 75 +++++++++++++++++++++ src/Composer/DependencyResolver/Solver.php | 37 +++------- 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 385baeef3..c40367d4c 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -90,12 +90,7 @@ class Problem $messages[] = $this->jobToText($job); } elseif ($rule) { if ($rule instanceof Rule) { - $message = ''; - if ($rule->getType() == RuleSet::TYPE_LEARNED) { - $message .= 'learned:'; - } - - $messages[] = $message.$rule; + $messages[] = $rule->toHumanReadableString(); } } } diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 8af24094e..cd674ef0f 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -17,6 +17,19 @@ namespace Composer\DependencyResolver; */ class Rule { + const RULE_INTERNAL_ALLOW_UPDATE = 1; + const RULE_JOB_INSTALL = 2; + const RULE_JOB_REMOVE = 3; + const RULE_JOB_LOCK = 4; + const RULE_NOT_INSTALLABLE = 5; + const RULE_PACKAGE_CONFLICT = 6; + const RULE_PACKAGE_REQUIRES = 7; + const RULE_PACKAGE_OBSOLETES = 8; + const RULE_INSTALLED_PACKAGE_OBSOLETES = 9; + const RULE_PACKAGE_SAME_NAME = 10; + const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11; + const RULE_LEARNED = 12; + protected $disabled; protected $literals; protected $type; @@ -163,6 +176,68 @@ class Rule } } + public function toHumanReadableString() + { + $ruleText = ''; + foreach ($this->literals as $i => $literal) { + if ($i != 0) { + $ruleText .= '|'; + } + $ruleText .= $literal; + } + + switch ($this->reason) { + case self::RULE_INTERNAL_ALLOW_UPDATE: + return $ruleText; + + case self::RULE_JOB_INSTALL: + return "Install command rule ($ruleText)"; + + case self::RULE_JOB_REMOVE: + return "Remove command rule ($ruleText)"; + + case self::RULE_JOB_LOCK: + return "Lock command rule ($ruleText)"; + + case self::RULE_NOT_INSTALLABLE: + return $ruleText; + + case self::RULE_PACKAGE_CONFLICT: + $package1 = $this->literals[0]->getPackage(); + $package2 = $this->literals[1]->getPackage(); + return 'Package "'.$package1.'" conflicts with "'.$package2.'"'; + + case self::RULE_PACKAGE_REQUIRES: + $literals = $this->literals; + $sourceLiteral = array_shift($literals); + $sourcePackage = $sourceLiteral->getPackage(); + + $requires = array(); + foreach ($literals as $literal) { + $requires[] = $literal->getPackage(); + } + + $text = 'Package "'.$sourcePackage.'" contains the rule '.$this->reasonData.'. '; + if ($requires) { + $text .= 'Any of these packages satisfy the dependency: '.implode(', ', $requires).'.'; + } else { + $text .= 'No package satisfies this dependency.'; + } + return $text; + + case self::RULE_PACKAGE_OBSOLETES: + return $ruleText; + case self::RULE_INSTALLED_PACKAGE_OBSOLETES: + return $ruleText; + case self::RULE_PACKAGE_SAME_NAME: + return $ruleText; + case self::RULE_PACKAGE_IMPLICIT_OBSOLETES: + return $ruleText; + case self::RULE_LEARNED: + return 'learned: '.$ruleText; + } + } + /** * Formats a rule as a string of the format (Literal1|Literal2|...) * diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 2b9dc082b..ef9c786c0 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -21,21 +21,6 @@ use Composer\DependencyResolver\Operation; */ class Solver { - const RULE_INTERNAL_ALLOW_UPDATE = 1; - const RULE_JOB_INSTALL = 2; - const RULE_JOB_REMOVE = 3; - const RULE_JOB_LOCK = 4; - const RULE_NOT_INSTALLABLE = 5; - const RULE_NOTHING_PROVIDES_DEP = 6; - const RULE_PACKAGE_CONFLICT = 7; - const RULE_PACKAGE_NOT_EXIST = 8; - const RULE_PACKAGE_REQUIRES = 9; - const RULE_PACKAGE_OBSOLETES = 10; - const RULE_INSTALLED_PACKAGE_OBSOLETES = 11; - const RULE_PACKAGE_SAME_NAME = 12; - const RULE_PACKAGE_IMPLICIT_OBSOLETES = 13; - const RULE_LEARNED = 14; - protected $policy; protected $pool; protected $installed; @@ -235,7 +220,7 @@ class Solver } if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installedMap, $package)) { - $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, self::RULE_NOT_INSTALLABLE, (string) $package)); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, Rule::RULE_NOT_INSTALLABLE, (string) $package)); continue; } @@ -261,7 +246,7 @@ class Solver } } - $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, self::RULE_PACKAGE_REQUIRES, (string) $link)); + $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link)); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); @@ -276,7 +261,7 @@ class Solver continue; } - $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, self::RULE_PACKAGE_CONFLICT, (string) $link)); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link)); } } @@ -301,7 +286,7 @@ class Solver continue; // don't repair installed/installed problems } - $reason = ($isInstalled) ? self::RULE_INSTALLED_PACKAGE_OBSOLETES : self::RULE_PACKAGE_OBSOLETES; + $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link)); } } @@ -327,7 +312,7 @@ class Solver continue; } - $reason = ($package->getName() == $provider->getName()) ? self::RULE_PACKAGE_SAME_NAME : self::RULE_PACKAGE_IMPLICIT_OBSOLETES; + $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES; $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package)); } } @@ -973,7 +958,7 @@ class Solver foreach ($installedPackages as $package) { $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); - $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); + $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); $rule->setWeak(true); $this->addRule(RuleSet::TYPE_FEATURE, $rule); @@ -988,7 +973,7 @@ class Solver $problem->addJobRule($job); $this->problems[] = $problem; } else { - $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']); + $rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job['packageName']); $this->addRule(RuleSet::TYPE_JOB, $rule); $this->ruleToJob[$rule->getId()] = $job; } @@ -999,7 +984,7 @@ class Solver // todo: cleandeps foreach ($job['packages'] as $package) { - $rule = $this->createRemoveRule($package, self::RULE_JOB_REMOVE); + $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE); $this->addRule(RuleSet::TYPE_JOB, $rule); $this->ruleToJob[$rule->getId()] = $job; } @@ -1007,9 +992,9 @@ class Solver case 'lock': foreach ($job['packages'] as $package) { if (isset($this->installedMap[$package->getId()])) { - $rule = $this->createInstallRule($package, self::RULE_JOB_LOCK); + $rule = $this->createInstallRule($package, Rule::RULE_JOB_LOCK); } else { - $rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK); + $rule = $this->createRemoveRule($package, Rule::RULE_JOB_LOCK); } $this->addRule(RuleSet::TYPE_JOB, $rule); $this->ruleToJob[$rule->getId()] = $job; @@ -1496,7 +1481,7 @@ class Solver $why = count($this->learnedPool) - 1; assert($learnedLiterals[0] !== null); - $newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why); + $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why); return array($ruleLevel, $newRule, $why); } From 51447074c2e1fb16e5d7bd60900850fcf4db825d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 21:16:36 +0100 Subject: [PATCH 04/25] Fix wording & co --- src/Composer/Console/Application.php | 2 +- src/Composer/Util/ErrorHandler.php | 20 +++++++++---------- tests/Composer/Test/Util/ErrorHandlerTest.php | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 57c659519..06a8fb5d0 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -41,7 +41,7 @@ class Application extends BaseApplication public function __construct() { - ErrorHandler::set(); + ErrorHandler::register(); parent::__construct('Composer', Composer::VERSION); } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index cb620e52f..708d2976f 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -13,7 +13,7 @@ namespace Composer\Util; /** - * Convert PHP E_NOTICE, E_WARNING into exceptions + * Convert PHP errors into exceptions * * @author Artem Lopata */ @@ -22,30 +22,30 @@ class ErrorHandler /** * Error handler * - * @param int $errorNo Level of the error raised - * @param string $errorString Error message - * @param string $errorFile Filename that the error was raised in - * @param int $errorLine Line number the error was raised at + * @param int $level Level of the error raised + * @param string $message Error message + * @param string $file Filename that the error was raised in + * @param int $line Line number the error was raised at * * @static * @throws \ErrorException */ - public static function handle($errorNo, $errorString, $errorFile, $errorLine) + public static function handle($level, $message, $file, $line) { - //this allows error suppression in 3rd party code to work + // respect error_reporting being disabled if (!error_reporting()) { return; } - throw new \ErrorException(sprintf('%s in %s:%d', $errorString, $errorFile, $errorLine), $errorNo); + throw new \ErrorException($message, 0, $level, $file, $line); } /** - * Set error handler + * Register error handler * * @static */ - public static function set() + public static function register() { set_error_handler(array(__CLASS__, 'handle')); } diff --git a/tests/Composer/Test/Util/ErrorHandlerTest.php b/tests/Composer/Test/Util/ErrorHandlerTest.php index 6aebc5c15..015997191 100644 --- a/tests/Composer/Test/Util/ErrorHandlerTest.php +++ b/tests/Composer/Test/Util/ErrorHandlerTest.php @@ -27,9 +27,9 @@ class ErrorHandlerTest extends TestCase */ public function testErrorHandlerCaptureNotice() { - $this->setExpectedException('\ErrorException', 'Undefined index: baz in ' . __FILE__); + $this->setExpectedException('\ErrorException', 'Undefined index: baz'); - ErrorHandler::set(); + ErrorHandler::register(); $array = array('foo' => 'bar'); $array['baz']; @@ -40,9 +40,9 @@ class ErrorHandlerTest extends TestCase */ public function testErrorHandlerCaptureWarning() { - $this->setExpectedException('\ErrorException', 'array_merge(): Argument #2 is not an array in ' . __FILE__); + $this->setExpectedException('\ErrorException', 'array_merge(): Argument #2 is not an array'); - ErrorHandler::set(); + ErrorHandler::register(); array_merge(array(), 'string'); } From d291d65faf7bbc7bd0cb5c50388c15cd11cba4c0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 21:01:59 +0100 Subject: [PATCH 05/25] Add getIO proxy to base Command class --- src/Composer/Command/Command.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Composer/Command/Command.php b/src/Composer/Command/Command.php index 8744a0da1..98298d139 100644 --- a/src/Composer/Command/Command.php +++ b/src/Composer/Command/Command.php @@ -29,4 +29,12 @@ abstract class Command extends BaseCommand { return $this->getApplication()->getComposer($required); } + + /** + * @return \Composer\IO\ConsoleIO + */ + protected function getIO() + { + return $this->getApplication()->getIO(); + } } From 7f65dd74091459b188c50f53227c1cc857b1180c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 21:04:15 +0100 Subject: [PATCH 06/25] Use RemoteFilesystem everywhere we do http requests --- src/Composer/Command/CreateProjectCommand.php | 3 ++- src/Composer/Command/SelfUpdateCommand.php | 9 +++---- src/Composer/Command/ValidateCommand.php | 3 ++- src/Composer/Factory.php | 5 ++-- src/Composer/Json/JsonFile.php | 27 ++++++++++++------- .../Repository/ComposerRepository.php | 8 ++++-- src/Composer/Repository/PearRepository.php | 13 +++++---- 7 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 4ad2e81d2..88da8adc8 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -23,6 +23,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Json\JsonFile; +use Composer\Util\RemoteFilesystem; /** * Install a package as new project into new directory. @@ -86,7 +87,7 @@ EOT if (null === $repositoryUrl) { $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org')); } elseif (".json" === substr($repositoryUrl, -5)) { - $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl)); + $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io))); } elseif (0 === strpos($repositoryUrl, 'http')) { $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl)); } else { diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 426fc6882..721e44461 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -13,7 +13,7 @@ namespace Composer\Command; use Composer\Composer; -use Composer\Util\StreamContextFactory; +use Composer\Util\RemoteFilesystem; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -40,9 +40,8 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $ctx = StreamContextFactory::getContext(); - - $latest = trim(file_get_contents('http://getcomposer.org/version', false, $ctx)); + $rfs = new RemoteFilesystem($this->getIO()); + $latest = trim($rfs->getContents('getcomposer.org', 'http://getcomposer.org/version', false)); if (Composer::VERSION !== $latest) { $output->writeln(sprintf("Updating to version %s.", $latest)); @@ -50,7 +49,7 @@ EOT $remoteFilename = 'http://getcomposer.org/composer.phar'; $localFilename = $_SERVER['argv'][0]; - copy($remoteFilename, $localFilename, $ctx); + $rfs->copy('getcomposer.org', $remoteFilename, $localFilename); } else { $output->writeln("You are using the latest composer version."); } diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 878a058aa..b65841d71 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; use Composer\Json\JsonFile; use Composer\Json\JsonValidationException; +use Composer\Util\RemoteFilesystem; /** * @author Robert Schönthal @@ -55,7 +56,7 @@ EOT $laxValid = false; try { - $json = new JsonFile($file); + $json = new JsonFile($file, new RemoteFilesystem($this->getIO())); $json->read(); $json->validateSchema(JsonFile::LAX_SCHEMA); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 958844930..2815f22b7 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -16,6 +16,7 @@ use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Repository\RepositoryManager; use Composer\Util\ProcessExecutor; +use Composer\Util\RemoteFilesystem; /** * Creates an configured instance of composer. @@ -38,7 +39,7 @@ class Factory $composerFile = getenv('COMPOSER') ?: 'composer.json'; } - $file = new JsonFile($composerFile); + $file = new JsonFile($composerFile, new RemoteFilesystem($io)); if (!$file->exists()) { if ($composerFile === 'composer.json') { $message = 'Composer could not find a composer.json file in '.getcwd(); @@ -98,7 +99,7 @@ class Factory // init locker $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; - $locker = new Package\Locker(new JsonFile($lockFile), $rm, md5_file($composerFile)); + $locker = new Package\Locker(new JsonFile($lockFile, new RemoteFilesystem($io)), $rm, md5_file($composerFile)); // initialize composer $composer = new Composer(); diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 458265e3e..5c891814e 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -17,6 +17,7 @@ use Composer\Composer; use JsonSchema\Validator; use Seld\JsonLint\JsonParser; use Composer\Util\StreamContextFactory; +use Composer\Util\RemoteFilesystem; /** * Reads/writes json files. @@ -34,15 +35,22 @@ class JsonFile const JSON_UNESCAPED_UNICODE = 256; private $path; + private $rfs; /** * Initializes json file reader/parser. * * @param string $lockFile path to a lockfile + * @param RemoteFilesystem $rfs required for loading http/https json files */ - public function __construct($path) + public function __construct($path, RemoteFilesystem $rfs = null) { $this->path = $path; + + if (null === $rfs && preg_match('{^https?://}i', $path)) { + throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed'); + } + $this->rfs = $rfs; } public function getPath() @@ -67,15 +75,14 @@ class JsonFile */ public function read() { - $ctx = StreamContextFactory::getContext(array( - 'http' => array( - 'header' => 'User-Agent: Composer/'.Composer::VERSION."\r\n" - ) - )); - - $json = file_get_contents($this->path, false, $ctx); - if (!$json) { - throw new \RuntimeException('Could not read '.$this->path.', you are probably offline'); + try { + if ($this->rfs) { + $json = $this->rfs->getContents($this->path, $this->path, false); + } else { + $json = file_get_contents($this->path); + } + } catch (\Exception $e) { + throw new \RuntimeException('Could not read '.$this->path.', you are probably offline ('.$e->getMessage().')'); } return static::parseJson($json); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 03a13b8e2..c05af141d 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -15,6 +15,8 @@ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Json\JsonFile; +use Composer\IO\IOInterface; +use Composer\Util\RemoteFilesystem; /** * @author Jordi Boggiano @@ -22,9 +24,10 @@ use Composer\Json\JsonFile; class ComposerRepository extends ArrayRepository { protected $url; + protected $io; protected $packages; - public function __construct(array $config) + public function __construct(array $config, IOInterface $io) { if (!preg_match('{^\w+://}', $config['url'])) { // assume http as the default protocol @@ -36,12 +39,13 @@ class ComposerRepository extends ArrayRepository } $this->url = $config['url']; + $this->io = $io; } protected function initialize() { parent::initialize(); - $json = new JsonFile($this->url.'/packages.json'); + $json = new JsonFile($this->url.'/packages.json', new RemoteFilesystem($this->io)); $packages = $json->read(); if (!$packages) { throw new \UnexpectedValueException('Could not parse package list from the '.$this->url.' repository'); diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index a1a2f4272..44a454128 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -13,7 +13,7 @@ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; -use Composer\Util\StreamContextFactory; +use Composer\Util\RemoteFilesystem; /** * @author Benjamin Eberlei @@ -23,9 +23,9 @@ class PearRepository extends ArrayRepository { private $url; private $channel; - private $streamContext; + private $rfs; - public function __construct(array $config) + public function __construct(array $config, IOInterface $io, RemoteFilesystem $rfs = null) { if (!preg_match('{^https?://}', $config['url'])) { $config['url'] = 'http://'.$config['url']; @@ -36,8 +36,8 @@ class PearRepository extends ArrayRepository } $this->url = rtrim($config['url'], '/'); - $this->channel = !empty($config['channel']) ? $config['channel'] : null; + $this->rfs = $rfs ?: new RemoteFilesystem($io); } protected function initialize() @@ -47,7 +47,6 @@ class PearRepository extends ArrayRepository set_error_handler(function($severity, $message, $file, $line) { throw new \ErrorException($message, $severity, $severity, $file, $line); }); - $this->streamContext = StreamContextFactory::getContext(); $this->fetchFromServer(); restore_error_handler(); } @@ -120,7 +119,7 @@ class PearRepository extends ArrayRepository ); try { - $deps = file_get_contents($releaseLink . "/deps.".$pearVersion.".txt", false, $this->streamContext); + $deps = $this->rfs->getContents($this->url, $releaseLink . "/deps.".$pearVersion.".txt", false); } catch (\ErrorException $e) { if (strpos($e->getMessage(), '404')) { continue; @@ -289,7 +288,7 @@ class PearRepository extends ArrayRepository */ private function requestXml($url) { - $content = file_get_contents($url, false, $this->streamContext); + $content = $this->rfs->getContents($this->url, $url, false); if (!$content) { throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); } From f98bd971f23d9c723e4470039ba2888fcaae7821 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 21:05:10 +0100 Subject: [PATCH 07/25] Add Gzip handling to RemoteFilesystem --- src/Composer/Downloader/FileDownloader.php | 1 - src/Composer/Util/RemoteFilesystem.php | 44 ++++++++++++++----- .../Test/Util/RemoteFilesystemTest.php | 3 +- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 1b440048f..d2bfc845b 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -74,7 +74,6 @@ class FileDownloader implements DownloaderInterface $url = $this->processUrl($url); $this->rfs->copy($package->getSourceUrl(), $url, $fileName); - $this->io->write(''); if (!file_exists($fileName)) { throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index e14201a13..6c70a2f14 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -12,6 +12,7 @@ namespace Composer\Util; +use Composer\Composer; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; @@ -101,22 +102,39 @@ class RemoteFilesystem } $result = @file_get_contents($fileUrl, false, $ctx); - if (null !== $fileName) { - $result = @file_put_contents($fileName, $result) ? true : false; - } // fix for 5.4.0 https://bugs.php.net/bug.php?id=61336 if (!empty($http_response_header[0]) && preg_match('{^HTTP/\S+ 404}i', $http_response_header[0])) { $result = false; } + // decode gzip + if (false !== $result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') { + foreach ($http_response_header as $header) { + if (preg_match('{^content-encoding: *gzip *$}i', $header)) { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $result = zlib_decode($result); + } else { + // work around issue with gzuncompress & co that do not work with all gzip checksums + $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); + } + break; + } + } + } + + // handle copy command if download was successful + if (false !== $result && null !== $fileName) { + $result = (Boolean) @file_put_contents($fileName, $result); + } + // avoid overriding if content was loaded by a sub-call to get() if (null === $this->result) { $this->result = $result; } if ($this->progress) { - $this->io->overwrite(" Downloading", false); + $this->io->write(''); } if (false === $this->result) { @@ -184,17 +202,21 @@ class RemoteFilesystem } } - protected function getOptionsForUrl($url) + protected function getOptionsForUrl($originUrl) { - $options = array(); - if ($this->io->hasAuthorization($url)) { - $auth = $this->io->getAuthorization($url); + $options['http']['header'] = 'User-Agent: Composer/'.Composer::VERSION."\r\n"; + if (extension_loaded('zlib')) { + $options['http']['header'] .= 'Accept-Encoding: gzip'."\r\n"; + } + + if ($this->io->hasAuthorization($originUrl)) { + $auth = $this->io->getAuthorization($originUrl); $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); + $options['http']['header'] .= "Authorization: Basic $authStr\r\n"; } elseif (null !== $this->io->getLastUsername()) { $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); - $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); - $this->io->setAuthorization($url, $this->io->getLastUsername(), $this->io->getLastPassword()); + $options['http']['header'] .= "Authorization: Basic $authStr\r\n"; + $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); } return $options; diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index 97e50b848..e196fa3f4 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -31,7 +31,8 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue(null)) ; - $this->assertEquals(array(), $this->callGetOptionsForUrl($io, array('http://example.org'))); + $res = $this->callGetOptionsForUrl($io, array('http://example.org')); + $this->assertTrue(isset($res['http']['header']) && false !== strpos($res['http']['header'], 'User-Agent'), 'getOptions must return an array with a header containing a User-Agent'); } public function testGetOptionsForUrlWithAuthorization() From e4cce193cf513c76f0dd3d18d1cce74b565aec24 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 22:12:25 +0100 Subject: [PATCH 08/25] Fix PEAR repository --- src/Composer/Repository/PearRepository.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 44a454128..8b48d2db5 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -12,8 +12,10 @@ namespace Composer\Repository; +use Composer\IO\IOInterface; use Composer\Package\Loader\ArrayLoader; use Composer\Util\RemoteFilesystem; +use Composer\Downloader\TransportException; /** * @author Benjamin Eberlei @@ -23,6 +25,7 @@ class PearRepository extends ArrayRepository { private $url; private $channel; + private $io; private $rfs; public function __construct(array $config, IOInterface $io, RemoteFilesystem $rfs = null) @@ -37,18 +40,16 @@ class PearRepository extends ArrayRepository $this->url = rtrim($config['url'], '/'); $this->channel = !empty($config['channel']) ? $config['channel'] : null; - $this->rfs = $rfs ?: new RemoteFilesystem($io); + $this->io = $io; + $this->rfs = $rfs ?: new RemoteFilesystem($this->io); } protected function initialize() { parent::initialize(); - set_error_handler(function($severity, $message, $file, $line) { - throw new \ErrorException($message, $severity, $severity, $file, $line); - }); + $this->io->write('Initializing PEAR repository '.$this->url); $this->fetchFromServer(); - restore_error_handler(); } protected function fetchFromServer() @@ -67,7 +68,7 @@ class PearRepository extends ArrayRepository try { $packagesLink = str_replace("info.xml", "packagesinfo.xml", $link); $this->fetchPear2Packages($this->url . $packagesLink); - } catch (\ErrorException $e) { + } catch (TransportException $e) { if (false === strpos($e->getMessage(), '404')) { throw $e; } @@ -80,7 +81,7 @@ class PearRepository extends ArrayRepository /** * @param string $categoryLink - * @throws ErrorException + * @throws TransportException * @throws InvalidArgumentException */ private function fetchPearPackages($categoryLink) @@ -98,7 +99,7 @@ class PearRepository extends ArrayRepository try { $releasesXML = $this->requestXml($allReleasesLink); - } catch (\ErrorException $e) { + } catch (TransportException $e) { if (strpos($e->getMessage(), '404')) { continue; } @@ -120,7 +121,7 @@ class PearRepository extends ArrayRepository try { $deps = $this->rfs->getContents($this->url, $releaseLink . "/deps.".$pearVersion.".txt", false); - } catch (\ErrorException $e) { + } catch (TransportException $e) { if (strpos($e->getMessage(), '404')) { continue; } @@ -225,6 +226,7 @@ class PearRepository extends ArrayRepository { $loader = new ArrayLoader(); $packagesXml = $this->requestXml($packagesLink); + $informations = $packagesXml->getElementsByTagName('pi'); foreach ($informations as $information) { $package = $information->getElementsByTagName('p')->item(0); From 22149d3a70c726c7709a221bfc62f65b18049bb9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 22:12:48 +0100 Subject: [PATCH 09/25] Fix gzip decoding after a redirect --- src/Composer/Util/RemoteFilesystem.php | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 6c70a2f14..a77b6e63e 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -110,15 +110,22 @@ class RemoteFilesystem // decode gzip if (false !== $result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') { + $decode = false; foreach ($http_response_header as $header) { if (preg_match('{^content-encoding: *gzip *$}i', $header)) { - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - $result = zlib_decode($result); - } else { - // work around issue with gzuncompress & co that do not work with all gzip checksums - $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); - } - break; + $decode = true; + continue; + } elseif (preg_match('{^HTTP/}i', $header)) { + $decode = false; + } + } + + if ($decode) { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $result = zlib_decode($result); + } else { + // work around issue with gzuncompress & co that do not work with all gzip checksums + $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); } } } @@ -138,7 +145,7 @@ class RemoteFilesystem } if (false === $this->result) { - throw new TransportException("The '$fileUrl' file could not be downloaded"); + throw new TransportException('The "'.$fileUrl.'" file could not be downloaded'); } } @@ -156,7 +163,7 @@ class RemoteFilesystem { switch ($notificationCode) { case STREAM_NOTIFY_FAILURE: - throw new TransportException(trim($message), $messageCode); + throw new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.trim($message).')', $messageCode); break; case STREAM_NOTIFY_AUTH_REQUIRED: From b12e73792cf0a45123effa75c20a85b0d3007b5a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 22:43:07 +0100 Subject: [PATCH 10/25] Handle SolverProblemException nicer --- src/Composer/Command/InstallCommand.php | 2 +- src/Composer/Installer.php | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 7bf90709e..cee9a9603 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -61,6 +61,6 @@ EOT ->setInstallSuggests($input->getOption('install-suggests')) ; - return $install->run(); + return $install->run() ? 0 : 1; } } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index dad3dc08e..34e01fee6 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -18,6 +18,7 @@ use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; +use Composer\DependencyResolver\SolverProblemsException; use Composer\Downloader\DownloadManager; use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; @@ -163,7 +164,7 @@ class Installer $installFromLock = false; $request = new Request($pool); if ($this->update) { - $this->io->write('Updating dependencies'); + $this->io->write('Updating dependencies'); $request->updateAll(); @@ -174,7 +175,7 @@ class Installer } } elseif ($this->locker->isLocked()) { $installFromLock = true; - $this->io->write('Installing from lock file'); + $this->io->write('Installing from lock file'); if (!$this->locker->isFresh()) { $this->io->write('Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies'); @@ -192,7 +193,7 @@ class Installer $request->install($package->getName(), $constraint); } } else { - $this->io->write('Installing dependencies'); + $this->io->write('Installing dependencies'); $links = $this->collectLinks(); @@ -206,7 +207,14 @@ class Installer $solver = new Solver($policy, $pool, $installedRepo); // solve dependencies - $operations = $solver->solve($request); + try { + $operations = $solver->solve($request); + } catch (SolverProblemsException $e) { + $this->io->write('Your requirements could not be solved to an installable set of packages.'); + $this->io->write($e->getMessage()); + + return false; + } // force dev packages to be updated to latest reference on update if ($this->update) { @@ -299,6 +307,8 @@ class Installer $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $this->eventDispatcher->dispatchCommandEvent($eventName); } + + return true; } private function collectLinks() From 5fd50080abbff4c36b1882d83d8081ca3e4dac39 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 22:45:22 +0100 Subject: [PATCH 11/25] Print details about missing extensions, fixes #312 --- src/Composer/DependencyResolver/Problem.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index c40367d4c..8ac2ed4bb 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -75,6 +75,12 @@ class Problem $job = $reason['job']; if ($job && $job['cmd'] === 'install' && empty($job['packages'])) { + // handle php extensions + if (0 === stripos($job['packageName'], 'ext-')) { + $ext = substr($job['packageName'], 4); + $error = extension_loaded($ext) ? 'has the wrong version ('.phpversion($ext).') installed' : 'is missing from your system'; + return 'The requested PHP extension "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).$error.'.'; + } return 'The requested package "'.$job['packageName'].'" '.$this->constraintToText($job['constraint']).'could not be found.'; } } From 39f16fb7ff16afc387aa4e249139d6907e4503cd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 22:49:15 +0100 Subject: [PATCH 12/25] Update CHANGELOG --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 985127bbe..2f630cede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,14 @@ * Added `create-project` command to install a project from scratch with composer * Added automated `classmap` autoloading support for non-PSR-0 compliant projects - * Improved clones from GitHub which now automatically select between git/https/http protocols + * Added human readable error reporting when deps can not be solved * Added support for private GitHub repositories (use --no-interaction for CI) - * Improved `validate` command to give more feedback * Added "file" downloader type to download plain files * Added support for authentication with svn repositories - * Removed dependency on filter_var + * Improved clones from GitHub which now automatically select between git/https/http protocols + * Improved `validate` command to give more feedback * Improved the `search` & `show` commands output + * Removed dependency on filter_var * Various robustness & error handling improvements, docs fixes and more * 1.0.0-alpha1 (2012-03-01) From 72b7b169e75fba8a4b93223daafb0e3b8a8592a0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 22:58:53 +0100 Subject: [PATCH 13/25] Fix warning --- src/Composer/Repository/VcsRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 65e432b18..110cabd27 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -35,7 +35,7 @@ class VcsRepository extends ArrayRepository $this->url = $config['url']; $this->io = $io; - $this->type = $config['type']; + $this->type = isset($config['type']) ? $config['type'] : 'vcs'; } public function setDebug($debug) From bcbdb97cc63dec28a641d505a7745fa5e910b7e4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 18 Mar 2012 23:02:46 +0100 Subject: [PATCH 14/25] Fix message when no composer file is found in a tag --- src/Composer/Repository/VcsRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 110cabd27..ace6c5297 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -118,7 +118,7 @@ class VcsRepository extends ArrayRepository } } catch (\Exception $e) { if ($debug) { - $this->io->write('Skipped tag '.$tag.', '.$e->getMessage()); + $this->io->write('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage())); } continue; } From 79c2bf139ea5aa9e01c0f89d0e768d947665a930 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 19 Mar 2012 08:36:03 +0100 Subject: [PATCH 15/25] Update vendors --- composer.lock | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 56479e5f5..4c921e393 100644 --- a/composer.lock +++ b/composer.lock @@ -14,18 +14,20 @@ { "package": "symfony/console", "version": "dev-master", - "source-reference": "2ee50c7c845ef7f8bce9c540709ecfd64cbcda87" + "source-reference": "88a283f6c18d1e644ebca1e3df4c870eac8108dd" }, { "package": "symfony/finder", "version": "dev-master", - "source-reference": "b3adc8d5c29593db93c0abc4711a1e25fd3a6fa0" + "source-reference": "be30ecc95281d729ee51b9e89644d442bcf60451" }, { "package": "symfony/process", "version": "dev-master", - "source-reference": "6aceac404d8574cf7da57e7e29b00a665b7bd559" + "source-reference": "0aad81ae9f884cf7df6387cb52a11b5b4f07b3d6" } ], - "aliases": [] + "aliases": [ + + ] } From 1a32b15003ea873d824dfef4538265c80d5f87a3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 20 Mar 2012 12:50:23 +0100 Subject: [PATCH 16/25] Fix docs --- doc/00-intro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/00-intro.md b/doc/00-intro.md index 289483336..2437b7437 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -44,7 +44,7 @@ any version beginning with `1.0`. ## Installation -### 1) Downloading the Composer Executable +### Downloading the Composer Executable To actually get Composer, we need to do two things. The first one is installing Composer (again, this mean downloading it into your project): @@ -65,7 +65,7 @@ You can place this file anywhere you wish. If you put it in your `PATH`, you can access it globally. On unixy systems you can even make it executable and invoke it without `php`. -### 2) Using Composer +### Using Composer Next, run the command the `install` command to resolve and download dependencies: From 31ed0c1fb06486c3ce1b05b62673b8d5a711817e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 20 Mar 2012 12:50:44 +0100 Subject: [PATCH 17/25] Fix progress display getting stuck at 95% --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index a77b6e63e..4d0433f38 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -141,7 +141,7 @@ class RemoteFilesystem } if ($this->progress) { - $this->io->write(''); + $this->io->overwrite(" Downloading: 100%"); } if (false === $this->result) { From 81fdd790f20eb79ff2389b4e687624131be47e4a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 20 Mar 2012 14:03:01 +0100 Subject: [PATCH 18/25] Notes on HTTP_PROXY env var --- doc/03-cli.md | 9 +++++++-- src/Composer/Util/StreamContextFactory.php | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 0ee6a140e..45344adea 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -179,10 +179,15 @@ directory to something other than `vendor/bin`. This env var controls the time composer waits for commands (such as git commands) to finish executing. The default value is 60 seconds. -### HTTP_PROXY +### http_proxy or HTTP_PROXY If you are using composer from behind an HTTP proxy, you can use the standard -`HTTP_PROXY` or `http_proxy` env vars. Simply set it to the URL of your proxy. +`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy. Many operating systems already set this variable for you. +Using `http_proxy` (lowercased) or even defining both might be preferrable since +some tools like git or curl will only use the lower-cased `http_proxy` version. +Alternatively you can also define the git proxy using +`git config --global http.proxy `. + ← [Libraries](02-libraries.md) | [Schema](04-schema.md) → \ No newline at end of file diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 4ea31da4d..6541d811b 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -34,15 +34,15 @@ final class StreamContextFactory // Handle system proxy if (isset($_SERVER['HTTP_PROXY']) || isset($_SERVER['http_proxy'])) { // Some systems seem to rely on a lowercased version instead... - $proxy = isset($_SERVER['HTTP_PROXY']) ? $_SERVER['HTTP_PROXY'] : $_SERVER['http_proxy']; - + $proxy = isset($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']; + // http(s):// is not supported in proxy $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxy); if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); } - + $options['http'] = array( 'proxy' => $proxy, 'request_fulluri' => true, From 45b2165057013f9d7d251a9c4e7dec0375568e02 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 20 Mar 2012 14:37:14 +0100 Subject: [PATCH 19/25] Fix tests --- tests/Composer/Test/Util/StreamContextFactoryTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Composer/Test/Util/StreamContextFactoryTest.php b/tests/Composer/Test/Util/StreamContextFactoryTest.php index eab6e7936..8429d078b 100644 --- a/tests/Composer/Test/Util/StreamContextFactoryTest.php +++ b/tests/Composer/Test/Util/StreamContextFactoryTest.php @@ -57,13 +57,13 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase public function testHttpProxy() { - $_SERVER['HTTP_PROXY'] = 'http://username:password@proxyserver.net:port/'; - $_SERVER['http_proxy'] = 'http://proxyserver/'; + $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:port/'; + $_SERVER['HTTP_PROXY'] = 'http://proxyserver/'; $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET'))); $options = stream_context_get_options($context); - $this->assertSame('http://proxyserver/', $_SERVER['http_proxy']); + $this->assertSame('http://proxyserver/', $_SERVER['HTTP_PROXY']); $this->assertEquals(array('http' => array( 'proxy' => 'tcp://username:password@proxyserver.net:port/', From ea2d46bcffa033f10f266f885229375f303b4b3b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 20 Mar 2012 17:42:37 +0100 Subject: [PATCH 20/25] Fix ComposerRepository calls, fixes #475 --- src/Composer/Command/CreateProjectCommand.php | 4 ++-- src/Composer/Command/InitCommand.php | 2 +- src/Composer/Command/SearchCommand.php | 3 ++- src/Composer/Command/ShowCommand.php | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 88da8adc8..0c98f5f27 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -85,11 +85,11 @@ EOT } if (null === $repositoryUrl) { - $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org')); + $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()); } elseif (".json" === substr($repositoryUrl, -5)) { $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io))); } elseif (0 === strpos($repositoryUrl, 'http')) { - $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl)); + $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $this->getIO()); } else { throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url."); } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index cdd5c4e34..290898c7e 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -229,7 +229,7 @@ EOT if (!$this->repos) { $this->repos = new CompositeRepository(array( new PlatformRepository, - new ComposerRepository(array('url' => 'http://packagist.org')) + new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()) )); } diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 0a4779ea5..2a25dc66c 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -54,7 +54,8 @@ EOT } else { $output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); $installedRepo = $platformRepo; - $repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org')))); + $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()); + $repos = new CompositeRepository(array($installedRepo, $packagist)); } $tokens = $input->getArgument('tokens'); diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 042ff420b..167d34b30 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -65,7 +65,8 @@ EOT } else { $output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); $installedRepo = $platformRepo; - $repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org')))); + $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()); + $repos = new CompositeRepository(array($installedRepo, $packagist)); } // show single package or single version From 108c921fde37eb4b26933e5ce1fecede8f6d8160 Mon Sep 17 00:00:00 2001 From: Tony Lemke Date: Wed, 21 Mar 2012 11:37:38 +0100 Subject: [PATCH 21/25] corrected namespace for Logger::WARNING --- doc/01-basic-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index e8037b7cd..161cd6499 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -144,7 +144,7 @@ means that we can just start using classes from it, and they will be autoloaded. $log = new Monolog\Logger('name'); - $log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Logger::WARNING)); + $log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING)); $log->addWarning('Foo'); From 2a9e8350b0cf5c66daf62dd54e632d252315f7d7 Mon Sep 17 00:00:00 2001 From: Mateusz Heleniak Date: Thu, 22 Mar 2012 00:38:05 +0100 Subject: [PATCH 22/25] fixed GitDriver - set correct working directory for cli commands --- src/Composer/Repository/Vcs/GitDriver.php | 22 ++++++++++------------ src/Composer/Util/ProcessExecutor.php | 9 +++++---- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 90f5ff926..0a35f2698 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -35,11 +35,10 @@ class GitDriver extends VcsDriver $this->repoDir = $this->url; } else { $this->repoDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/'; - $repoDir = escapeshellarg($this->repoDir); if (is_dir($this->repoDir)) { - $this->process->execute(sprintf('cd %s && git fetch origin', $repoDir), $output); + $this->process->execute('git fetch origin', $output, $this->repoDir); } else { - $this->process->execute(sprintf('git clone %s %s', $url, $repoDir), $output); + $this->process->execute(sprintf('git clone %s %s', $url, escapeshellarg($this->repoDir)), $output); } } @@ -57,7 +56,7 @@ class GitDriver extends VcsDriver if ($this->isLocal) { // select currently checked out branch if master is not available - $this->process->execute(sprintf('cd %s && git branch --no-color', escapeshellarg($this->repoDir)), $output); + $this->process->execute('git branch --no-color', $output, $this->repoDir); $branches = $this->process->splitLines($output); if (!in_array('* master', $branches)) { foreach ($branches as $branch) { @@ -69,7 +68,7 @@ class GitDriver extends VcsDriver } } else { // try to find a non-master remote HEAD branch - $this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->repoDir)), $output); + $this->process->execute('git branch --no-color -r', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) { $this->rootIdentifier = $match[1]; @@ -114,7 +113,7 @@ class GitDriver extends VcsDriver public function getComposerInformation($identifier) { if (!isset($this->infoCache[$identifier])) { - $this->process->execute(sprintf('cd %s && git show %s:composer.json', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $composer); + $this->process->execute(sprintf('git show %s:composer.json', escapeshellarg($identifier)), $composer, $this->repoDir); if (!trim($composer)) { return; @@ -123,7 +122,7 @@ class GitDriver extends VcsDriver $composer = JsonFile::parseJson($composer); if (!isset($composer['time'])) { - $this->process->execute(sprintf('cd %s && git log -1 --format=%%at %s', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $output); + $this->process->execute(sprintf('git log -1 --format=%%at %s', escapeshellarg($identifier)), $output, $this->repoDir); $date = new \DateTime('@'.trim($output)); $composer['time'] = $date->format('Y-m-d H:i:s'); } @@ -139,7 +138,7 @@ class GitDriver extends VcsDriver public function getTags() { if (null === $this->tags) { - $this->process->execute(sprintf('cd %s && git tag', escapeshellarg($this->repoDir)), $output); + $this->process->execute('git tag', $output, $this->repoDir); $output = $this->process->splitLines($output); $this->tags = $output ? array_combine($output, $output) : array(); } @@ -156,10 +155,9 @@ class GitDriver extends VcsDriver $branches = array(); $this->process->execute(sprintf( - 'cd %s && git branch --no-color --no-abbrev -v %s', - escapeshellarg($this->repoDir), + 'git branch --no-color --no-abbrev -v %s', $this->isLocal ? '' : '-r' - ), $output); + ), $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { preg_match('{^(?:\* )? *(?:[^/]+/)?(\S+) *([a-f0-9]+) .*$}', $branch, $match); @@ -186,7 +184,7 @@ class GitDriver extends VcsDriver if (static::isLocalUrl($url)) { $process = new ProcessExecutor(); // check whether there is a git repo in that path - if ($process->execute(sprintf('cd %s && git tag', escapeshellarg($url)), $output) === 0) { + if ($process->execute('git tag', $output, $url) === 0) { return true; } } diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 500a23481..c1125a615 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -26,15 +26,16 @@ class ProcessExecutor /** * runs a process on the commandline * - * @param $command the command to execute - * @param null $output the output will be written into this var if passed + * @param string $command the command to execute + * @param null $output the output will be written into this var if passed + * @param string $cwd the working directory * @return int statuscode */ - public function execute($command, &$output = null) + public function execute($command, &$output = null, $cwd = null) { $captureOutput = count(func_get_args()) > 1; $this->errorOutput = null; - $process = new Process($command, null, null, null, static::getTimeout()); + $process = new Process($command, $cwd, null, null, static::getTimeout()); $process->run(function($type, $buffer) use ($captureOutput) { if ($captureOutput) { return; From 678228709141057d1f1bee4c0386d63418b55890 Mon Sep 17 00:00:00 2001 From: "Baptiste \"Talus\" Clavie" Date: Thu, 22 Mar 2012 11:12:30 +0100 Subject: [PATCH 23/25] [ClassLoader] Got an if-condition out of a unnecessary loop --- src/Composer/Autoload/ClassLoader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 94fc76ac1..88b4c3fb7 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -181,8 +181,8 @@ class ClassLoader $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; foreach ($this->prefixes as $prefix => $dirs) { - foreach ($dirs as $dir) { - if (0 === strpos($class, $prefix)) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { return $dir . DIRECTORY_SEPARATOR . $classPath; } From 6957743aee8f6ab22ff241f340f665b0580a103d Mon Sep 17 00:00:00 2001 From: "Baptiste \"Talus\" Clavie" Date: Thu, 22 Mar 2012 11:19:01 +0100 Subject: [PATCH 24/25] [ClassLoader] Corrected indentation --- src/Composer/Autoload/ClassLoader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 88b4c3fb7..ae1cb9731 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -180,9 +180,9 @@ class ClassLoader $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; - foreach ($this->prefixes as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { + foreach ($this->prefixes as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { return $dir . DIRECTORY_SEPARATOR . $classPath; } From 15b70e2096fbfa31439f1f20c88724cf0a083c64 Mon Sep 17 00:00:00 2001 From: "Baptiste \"Talus\" Clavie" Date: Thu, 22 Mar 2012 11:45:44 +0100 Subject: [PATCH 25/25] [ClassLoader] Removing trailing whitespaces --- src/Composer/Autoload/ClassLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index ae1cb9731..d07cb31e9 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -180,7 +180,7 @@ class ClassLoader $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; - foreach ($this->prefixes as $prefix => $dirs) { + foreach ($this->prefixes as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {