1
0
Fork 0

Fix all 5.3 $this-in-closure usages

pull/10420/head
Jordi Boggiano 2022-01-03 15:40:32 +01:00
parent 5805a68645
commit 4e6d54b731
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
25 changed files with 120 additions and 330 deletions

View File

@ -69,13 +69,8 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Composer\\Test\\": "tests/Composer/Test", "Composer\\Test\\": "tests/Composer/Test"
"Composer\\PHPStanRules\\": "phpstan/Rules/src", }
"Composer\\PHPStanRulesTests\\": "phpstan/Rules/tests"
},
"classmap": [
"phpstan/Rules/tests/data"
]
}, },
"bin": [ "bin": [
"bin/composer" "bin/composer"

View File

@ -1,46 +0,0 @@
<?php declare(strict_types = 1);
namespace Composer\PHPStanRules;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
/**
* @phpstan-implements Rule<\PhpParser\Node\Expr\Variable>
*/
final class AnonymousFunctionWithThisRule implements Rule
{
/**
* @inheritDoc
*/
public function getNodeType(): string
{
return \PhpParser\Node\Expr\Variable::class;
}
/**
* @inheritDoc
*/
public function processNode(Node $node, Scope $scope): array
{
if (!\is_string($node->name) || $node->name !== 'this') {
return [];
}
if ($scope->isInClosureBind()) {
return [];
}
if (!$scope->isInClass()) {
// reported in other standard rule on level 0
return [];
}
if ($scope->isInAnonymousFunction()) {
return ['Using $this inside anonymous function is prohibited because of PHP 5.3 support.'];
}
return [];
}
}

View File

@ -1,28 +0,0 @@
<?php declare(strict_types = 1);
namespace Composer\PHPStanRulesTests;
use Composer\PHPStanRules\AnonymousFunctionWithThisRule;
use PHPStan\Testing\RuleTestCase;
/**
* @phpstan-extends RuleTestCase<AnonymousFunctionWithThisRule>
*/
final class AnonymousFunctionWithThisRuleTest extends RuleTestCase
{
/**
* @inheritDoc
*/
protected function getRule(): \PHPStan\Rules\Rule
{
return new AnonymousFunctionWithThisRule();
}
public function testWithThis(): void
{
$this->analyse([__DIR__ . '/data/method-with-this.php'], [
['Using $this inside anonymous function is prohibited because of PHP 5.3 support.', 13],
['Using $this inside anonymous function is prohibited because of PHP 5.3 support.', 17],
]);
}
}

View File

@ -1,34 +0,0 @@
<?php
class FirstClass
{
/**
* @var int
*/
private $firstProp = 9;
public function funMethod()
{
function() {
$this->firstProp;
};
call_user_func(function() {
$this->funMethod();
}, $this);
$bind = 'bind';
function() use($bind) {
};
}
}
function global_ok() {
$_SERVER['REMOTE_ADDR'];
}
function global_this() {
// not checked by our rule, it is checked with standard phpstan rule on level 0
$this['REMOTE_ADDR'];
}

View File

@ -56,6 +56,3 @@ parameters:
- Composer\Composer::VERSION - Composer\Composer::VERSION
- Composer\Composer::RELEASE_DATE - Composer\Composer::RELEASE_DATE
- Composer\Composer::SOURCE_VERSION - Composer\Composer::SOURCE_VERSION
rules:
- Composer\PHPStanRules\AnonymousFunctionWithThisRule

View File

@ -348,15 +348,14 @@ EOT
} }
} }
$self = $this;
$author = $io->askAndValidate( $author = $io->askAndValidate(
'Author [<comment>'.$author.'</comment>, n to skip]: ', 'Author [<comment>'.$author.'</comment>, n to skip]: ',
function ($value) use ($self, $author) { function ($value) use ($author) {
if ($value === 'n' || $value === 'no') { if ($value === 'n' || $value === 'no') {
return; return;
} }
$value = $value ?: $author; $value = $value ?: $author;
$author = $self->parseAuthorString($value); $author = $this->parseAuthorString($value);
return sprintf('%s <%s>', $author['name'], $author['email']); return sprintf('%s <%s>', $author['name'], $author['email']);
}, },

View File

@ -514,14 +514,12 @@ class Config
*/ */
private function process($value, $flags) private function process($value, $flags)
{ {
$config = $this;
if (!is_string($value)) { if (!is_string($value)) {
return $value; return $value;
} }
return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($config, $flags) { return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) {
return $config->get($match[1], $flags); return $this->get($match[1], $flags);
}, $value); }, $value);
} }

View File

@ -64,11 +64,10 @@ class DefaultPolicy implements PolicyInterface
public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null) public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null)
{ {
$packages = $this->groupLiteralsByName($pool, $literals); $packages = $this->groupLiteralsByName($pool, $literals);
$policy = $this;
foreach ($packages as &$nameLiterals) { foreach ($packages as &$nameLiterals) {
usort($nameLiterals, function ($a, $b) use ($policy, $pool, $requiredPackage) { usort($nameLiterals, function ($a, $b) use ($pool, $requiredPackage) {
return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true); return $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
}); });
} }
@ -80,8 +79,8 @@ class DefaultPolicy implements PolicyInterface
$selected = \call_user_func_array('array_merge', array_values($packages)); $selected = \call_user_func_array('array_merge', array_values($packages));
// now sort the result across all packages to respect replaces across packages // now sort the result across all packages to respect replaces across packages
usort($selected, function ($a, $b) use ($policy, $pool, $requiredPackage) { usort($selected, function ($a, $b) use ($pool, $requiredPackage) {
return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage); return $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
}); });
return $selected; return $selected;

View File

@ -28,9 +28,8 @@ abstract class ArchiveDownloader extends FileDownloader
{ {
/** /**
* @var array<string, true> * @var array<string, true>
* @protected
*/ */
public $cleanupExecuted = array(); protected $cleanupExecuted = array();
/** /**
* @return PromiseInterface|null * @return PromiseInterface|null
@ -92,22 +91,20 @@ abstract class ArchiveDownloader extends FileDownloader
$fileName = $this->getFileName($package, $path); $fileName = $this->getFileName($package, $path);
$filesystem = $this->filesystem; $filesystem = $this->filesystem;
$self = $this;
$cleanup = function () use ($path, $filesystem, $temporaryDir, $package, $self) { $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) {
// remove cache if the file was corrupted // remove cache if the file was corrupted
$self->clearLastCacheWrite($package); $this->clearLastCacheWrite($package);
// clean up // clean up
$filesystem->removeDirectory($temporaryDir); $filesystem->removeDirectory($temporaryDir);
if (is_dir($path) && realpath($path) !== getcwd()) { if (is_dir($path) && realpath($path) !== getcwd()) {
$filesystem->removeDirectory($path); $filesystem->removeDirectory($path);
} }
$self->removeCleanupPath($package, $temporaryDir); $this->removeCleanupPath($package, $temporaryDir);
$self->removeCleanupPath($package, realpath($path)); $this->removeCleanupPath($package, realpath($path));
}; };
$promise = null;
try { try {
$promise = $this->extract($package, $fileName, $temporaryDir); $promise = $this->extract($package, $fileName, $temporaryDir);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -119,7 +116,7 @@ abstract class ArchiveDownloader extends FileDownloader
$promise = \React\Promise\resolve(); $promise = \React\Promise\resolve();
} }
return $promise->then(function () use ($self, $package, $filesystem, $fileName, $temporaryDir, $path) { return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path) {
$filesystem->unlink($fileName); $filesystem->unlink($fileName);
/** /**
@ -203,9 +200,9 @@ abstract class ArchiveDownloader extends FileDownloader
$promise = $filesystem->removeDirectoryAsync($temporaryDir); $promise = $filesystem->removeDirectoryAsync($temporaryDir);
return $promise->then(function () use ($self, $package, $path, $temporaryDir) { return $promise->then(function () use ($package, $path, $temporaryDir) {
$self->removeCleanupPath($package, $temporaryDir); $this->removeCleanupPath($package, $temporaryDir);
$self->removeCleanupPath($package, $path); $this->removeCleanupPath($package, $path);
}); });
}, function ($e) use ($cleanup) { }, function ($e) use ($cleanup) {
$cleanup(); $cleanup();

View File

@ -192,16 +192,15 @@ class DownloadManager
$sources = $this->getAvailableSources($package, $prevPackage); $sources = $this->getAvailableSources($package, $prevPackage);
$io = $this->io; $io = $this->io;
$self = $this;
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download, $prevPackage) { $download = function ($retry = false) use (&$sources, $io, $package, $targetDir, &$download, $prevPackage) {
$source = array_shift($sources); $source = array_shift($sources);
if ($retry) { if ($retry) {
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>'); $io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
} }
$package->setInstallationSource($source); $package->setInstallationSource($source);
$downloader = $self->getDownloaderForPackage($package); $downloader = $this->getDownloaderForPackage($package);
if (!$downloader) { if (!$downloader) {
return \React\Promise\resolve(); return \React\Promise\resolve();
} }
@ -332,10 +331,8 @@ class DownloadManager
// we wipe the dir and do a new install instead of updating it // we wipe the dir and do a new install instead of updating it
$promise = $initialDownloader->remove($initial, $targetDir); $promise = $initialDownloader->remove($initial, $targetDir);
if ($promise) { if ($promise) {
$self = $this; return $promise->then(function ($res) use ($target, $targetDir) {
return $this->install($target, $targetDir);
return $promise->then(function ($res) use ($self, $target, $targetDir) {
return $self->install($target, $targetDir);
}); });
} }

View File

@ -150,11 +150,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$httpDownloader = $this->httpDownloader; $httpDownloader = $this->httpDownloader;
$eventDispatcher = $this->eventDispatcher; $eventDispatcher = $this->eventDispatcher;
$filesystem = $this->filesystem; $filesystem = $this->filesystem;
$self = $this;
$accept = null; $accept = null;
$reject = null; $reject = null;
$download = function () use ($io, $output, $httpDownloader, $cache, $cacheKeyGenerator, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject, $self) { $download = function () use ($io, $output, $httpDownloader, $cache, $cacheKeyGenerator, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject) {
/** @var array{base: string, processed: string, cacheKey: string} $url */ /** @var array{base: string, processed: string, cacheKey: string} $url */
$url = reset($urls); $url = reset($urls);
$index = key($urls); $index = key($urls);
@ -184,7 +183,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
// the cache is corrupt the archive will be deleted and the next attempt will re-download it // the cache is corrupt the archive will be deleted and the next attempt will re-download it
// see https://github.com/composer/composer/issues/10028 // see https://github.com/composer/composer/issues/10028
if (!$cache->isReadOnly()) { if (!$cache->isReadOnly()) {
$self->lastCacheWrites[$package->getName()] = $cacheKey; $this->lastCacheWrites[$package->getName()] = $cacheKey;
} }
$result = \React\Promise\resolve($fileName); $result = \React\Promise\resolve($fileName);
} else { } else {
@ -222,13 +221,13 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
}); });
}; };
$accept = function ($response) use ($cache, $package, $fileName, $self, &$urls) { $accept = function ($response) use ($cache, $package, $fileName, &$urls) {
$url = reset($urls); $url = reset($urls);
$cacheKey = $url['cacheKey']; $cacheKey = $url['cacheKey'];
FileDownloader::$downloadMetadata[$package->getName()] = @filesize($fileName) ?: $response->getHeader('Content-Length') ?: '?'; FileDownloader::$downloadMetadata[$package->getName()] = @filesize($fileName) ?: $response->getHeader('Content-Length') ?: '?';
if ($cache && !$cache->isReadOnly()) { if ($cache && !$cache->isReadOnly()) {
$self->lastCacheWrites[$package->getName()] = $cacheKey; $this->lastCacheWrites[$package->getName()] = $cacheKey;
$cache->copyFrom($cacheKey, $fileName); $cache->copyFrom($cacheKey, $fileName);
} }
@ -237,12 +236,12 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
return $fileName; return $fileName;
}; };
$reject = function ($e) use ($io, &$urls, $download, $fileName, $package, &$retries, $filesystem, $self) { $reject = function ($e) use ($io, &$urls, $download, $fileName, $package, &$retries, $filesystem) {
// clean up // clean up
if (file_exists($fileName)) { if (file_exists($fileName)) {
$filesystem->unlink($fileName); $filesystem->unlink($fileName);
} }
$self->clearLastCacheWrite($package); $this->clearLastCacheWrite($package);
if ($e instanceof IrrecoverableDownloadException) { if ($e instanceof IrrecoverableDownloadException) {
throw $e; throw $e;
@ -361,12 +360,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
} }
/** /**
* TODO mark private in v3
* @protected This is public due to PHP 5.3
*
* @return void * @return void
*/ */
public function clearLastCacheWrite(PackageInterface $package) protected function clearLastCacheWrite(PackageInterface $package)
{ {
if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) {
$this->cache->remove($this->lastCacheWrites[$package->getName()]); $this->cache->remove($this->lastCacheWrites[$package->getName()]);
@ -375,27 +371,21 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
} }
/** /**
* TODO mark private in v3
* @protected This is public due to PHP 5.3
*
* @param string $path * @param string $path
* *
* @return void * @return void
*/ */
public function addCleanupPath(PackageInterface $package, $path) protected function addCleanupPath(PackageInterface $package, $path)
{ {
$this->additionalCleanupPaths[$package->getName()][] = $path; $this->additionalCleanupPaths[$package->getName()][] = $path;
} }
/** /**
* TODO mark private in v3
* @protected This is public due to PHP 5.3
*
* @param string $path * @param string $path
* *
* @return void * @return void
*/ */
public function removeCleanupPath(PackageInterface $package, $path) protected function removeCleanupPath(PackageInterface $package, $path)
{ {
if (isset($this->additionalCleanupPaths[$package->getName()])) { if (isset($this->additionalCleanupPaths[$package->getName()])) {
$idx = array_search($path, $this->additionalCleanupPaths[$package->getName()]); $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()]);
@ -416,11 +406,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
if (!$promise instanceof PromiseInterface) { if (!$promise instanceof PromiseInterface) {
$promise = \React\Promise\resolve(); $promise = \React\Promise\resolve();
} }
$self = $this;
$io = $this->io;
return $promise->then(function () use ($self, $target, $path) { return $promise->then(function () use ($target, $path) {
$promise = $self->install($target, $path, false); $promise = $this->install($target, $path, false);
return $promise; return $promise;
}); });

View File

@ -126,9 +126,8 @@ class ZipDownloader extends ArchiveDownloader
$executable = $commandSpec[0]; $executable = $commandSpec[0];
$self = $this;
$io = $this->io; $io = $this->io;
$tryFallback = function ($processError) use ($isLastChance, $io, $self, $file, $path, $package, $executable) { $tryFallback = function ($processError) use ($isLastChance, $io, $file, $path, $package, $executable) {
if ($isLastChance) { if ($isLastChance) {
throw $processError; throw $processError;
} }
@ -143,15 +142,15 @@ class ZipDownloader extends ArchiveDownloader
$io->writeError(' Unzip with '.$executable.' command failed, falling back to ZipArchive class'); $io->writeError(' Unzip with '.$executable.' command failed, falling back to ZipArchive class');
} }
return $self->extractWithZipArchive($package, $file, $path); return $this->extractWithZipArchive($package, $file, $path);
}; };
try { try {
$promise = $this->process->executeAsync($command); $promise = $this->process->executeAsync($command);
return $promise->then(function ($process) use ($tryFallback, $command, $package, $file, $self) { return $promise->then(function ($process) use ($tryFallback, $command, $package, $file) {
if (!$process->isSuccessful()) { if (!$process->isSuccessful()) {
if (isset($self->cleanupExecuted[$package->getName()])) { if (isset($this->cleanupExecuted[$package->getName()])) {
throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.'); throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.');
} }
@ -174,11 +173,8 @@ class ZipDownloader extends ArchiveDownloader
* @param string $file File to extract * @param string $file File to extract
* @param string $path Path where to extract file * @param string $path Path where to extract file
* @return PromiseInterface * @return PromiseInterface
*
* TODO v3 should make this private once we can drop PHP 5.3 support
* @protected
*/ */
public function extractWithZipArchive(PackageInterface $package, $file, $path) private function extractWithZipArchive(PackageInterface $package, $file, $path)
{ {
$processError = null; $processError = null;
$zipArchive = $this->zipArchiveObject ?: new ZipArchive(); $zipArchive = $this->zipArchiveObject ?: new ZipArchive();
@ -214,11 +210,8 @@ class ZipDownloader extends ArchiveDownloader
* @param string $file File to extract * @param string $file File to extract
* @param string $path Path where to extract file * @param string $path Path where to extract file
* @return PromiseInterface|null * @return PromiseInterface|null
*
* TODO v3 should make this private once we can drop PHP 5.3 support
* @protected
*/ */
public function extract(PackageInterface $package, $file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
return $this->extractWithSystemUnzip($package, $file, $path); return $this->extractWithSystemUnzip($package, $file, $path);
} }

View File

@ -436,7 +436,6 @@ class InstallationManager
} }
$dispatcher = $this->eventDispatcher; $dispatcher = $this->eventDispatcher;
$installManager = $this;
$io = $this->io; $io = $this->io;
$promise = $installer->prepare($opType, $package, $initialPackage); $promise = $installer->prepare($opType, $package, $initialPackage);
@ -444,11 +443,11 @@ class InstallationManager
$promise = \React\Promise\resolve(); $promise = \React\Promise\resolve();
} }
$promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) { $promise = $promise->then(function () use ($opType, $repo, $operation) {
return $installManager->$opType($repo, $operation); return $this->$opType($repo, $operation);
})->then($cleanupPromises[$index]) })->then($cleanupPromises[$index])
->then(function () use ($installManager, $devMode, $repo) { ->then(function () use ($devMode, $repo) {
$repo->write($devMode, $installManager); $repo->write($devMode, $this);
}, function ($e) use ($opType, $package, $io) { }, function ($e) use ($opType, $package, $io) {
$io->writeError(' <error>' . ucfirst($opType) .' of '.$package->getPrettyName().' failed</error>'); $io->writeError(' <error>' . ucfirst($opType) .' of '.$package->getPrettyName().' failed</error>');

View File

@ -287,15 +287,8 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
$promise = \React\Promise\resolve(); $promise = \React\Promise\resolve();
} }
$self = $this; return $promise->then(function () use ($target) {
return $this->installCode($target);
return $promise->then(function () use ($self, $target) {
$reflMethod = new \ReflectionMethod($self, 'installCode');
$reflMethod->setAccessible(true);
// equivalent of $this->installCode($target) with php 5.3 support
// TODO remove this once 5.3 support is dropped
return $reflMethod->invoke($self, $target);
}); });
} }

View File

@ -70,15 +70,12 @@ class PluginInstaller extends LibraryInstaller
$promise = \React\Promise\resolve(); $promise = \React\Promise\resolve();
} }
$pluginManager = $this->composer->getPluginManager(); return $promise->then(function () use ($package, $repo) {
$self = $this;
return $promise->then(function () use ($self, $pluginManager, $package, $repo) {
try { try {
Platform::workaroundFilesystemIssues(); Platform::workaroundFilesystemIssues();
$pluginManager->registerPackage($package, true); $this->composer->getPluginManager()->registerPackage($package, true);
} catch (\Exception $e) { } catch (\Exception $e) {
$self->rollbackInstall($e, $repo, $package); $this->rollbackInstall($e, $repo, $package);
} }
}); });
} }
@ -93,16 +90,13 @@ class PluginInstaller extends LibraryInstaller
$promise = \React\Promise\resolve(); $promise = \React\Promise\resolve();
} }
$pluginManager = $this->composer->getPluginManager(); return $promise->then(function () use ($initial, $target, $repo) {
$self = $this;
return $promise->then(function () use ($self, $pluginManager, $initial, $target, $repo) {
try { try {
Platform::workaroundFilesystemIssues(); Platform::workaroundFilesystemIssues();
$pluginManager->deactivatePackage($initial); $this->composer->getPluginManager()->deactivatePackage($initial);
$pluginManager->registerPackage($target, true); $this->composer->getPluginManager()->registerPackage($target, true);
} catch (\Exception $e) { } catch (\Exception $e) {
$self->rollbackInstall($e, $repo, $target); $this->rollbackInstall($e, $repo, $target);
} }
}); });
} }
@ -114,13 +108,7 @@ class PluginInstaller extends LibraryInstaller
return parent::uninstall($repo, $package); return parent::uninstall($repo, $package);
} }
/** private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package): void
* TODO v3 should make this private once we can drop PHP 5.3 support
* @private
*
* @return void
*/
public function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package)
{ {
$this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin');
parent::uninstall($repo, $package); parent::uninstall($repo, $package);

View File

@ -294,12 +294,10 @@ class JsonManipulator
return false; return false;
} }
$that = $this;
// child exists // child exists
$childRegex = '{'.self::$DEFINES.'(?P<start>"'.preg_quote($name).'"\s*:\s*)(?P<content>(?&json))(?P<end>,?)}x'; $childRegex = '{'.self::$DEFINES.'(?P<start>"'.preg_quote($name).'"\s*:\s*)(?P<content>(?&json))(?P<end>,?)}x';
if (Preg::isMatch($childRegex, $children, $matches)) { if (Preg::isMatch($childRegex, $children, $matches)) {
$children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value, $that) { $children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value) {
if ($subName !== null) { if ($subName !== null) {
$curVal = json_decode($matches['content'], true); $curVal = json_decode($matches['content'], true);
if (!is_array($curVal)) { if (!is_array($curVal)) {
@ -309,7 +307,7 @@ class JsonManipulator
$value = $curVal; $value = $curVal;
} }
return $matches['start'] . $that->format($value, 1) . $matches['end']; return $matches['start'] . $this->format($value, 1) . $matches['end'];
}, $children); }, $children);
} else { } else {
Preg::match('#^{ (?P<leadingspace>\s*?) (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx', $children, $match); Preg::match('#^{ (?P<leadingspace>\s*?) (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx', $children, $match);
@ -452,12 +450,11 @@ class JsonManipulator
return true; return true;
} }
$that = $this; $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($name, $subName, $childrenClean) {
$this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($that, $name, $subName, $childrenClean) {
if ($subName !== null) { if ($subName !== null) {
$curVal = json_decode($matches['content'], true); $curVal = json_decode($matches['content'], true);
unset($curVal[$name][$subName]); unset($curVal[$name][$subName]);
$childrenClean = $that->format($curVal); $childrenClean = $this->format($curVal);
} }
return $matches['start'] . $childrenClean . $matches['end']; return $matches['start'] . $childrenClean . $matches['end'];

View File

@ -106,28 +106,23 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
private $partialPackagesByName = null; private $partialPackagesByName = null;
/** /**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @private
* @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times * @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times
* useful for v2 metadata repositories with lazy providers * useful for v2 metadata repositories with lazy providers
* @phpstan-var array<string, true> * @phpstan-var array<string, true>
*/ */
public $freshMetadataUrls = array(); private $freshMetadataUrls = array();
/** /**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @private
* @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times * @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times
* useful for v2 metadata repositories with lazy providers * useful for v2 metadata repositories with lazy providers
* @phpstan-var array<string, true> * @phpstan-var array<string, true>
*/ */
public $packagesNotFoundCache = array(); private $packagesNotFoundCache = array();
/** /**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @private
* @var VersionParser * @var VersionParser
*/ */
public $versionParser; private $versionParser;
/** /**
* @param array<string, mixed> $repoConfig * @param array<string, mixed> $repoConfig
@ -867,7 +862,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$packages = array(); $packages = array();
$namesFound = array(); $namesFound = array();
$promises = array(); $promises = array();
$repo = $this;
if (!$this->lazyProvidersUrl) { if (!$this->lazyProvidersUrl) {
throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url');
@ -903,7 +897,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
$promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified) $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified)
->then(function ($response) use (&$packages, &$namesFound, $url, $cacheKey, $contents, $realName, $constraint, $repo, $acceptableStabilities, $stabilityFlags, $alreadyLoaded) { ->then(function ($response) use (&$packages, &$namesFound, $url, $cacheKey, $contents, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded) {
$packagesSource = 'downloaded file ('.Url::sanitize($url).')'; $packagesSource = 'downloaded file ('.Url::sanitize($url).')';
if (true === $response) { if (true === $response) {
@ -925,10 +919,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$versionsToLoad = array(); $versionsToLoad = array();
foreach ($versions as $version) { foreach ($versions as $version) {
if (!isset($version['version_normalized'])) { if (!isset($version['version_normalized'])) {
$version['version_normalized'] = $repo->versionParser->normalize($version['version']); $version['version_normalized'] = $this->versionParser->normalize($version['version']);
} elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) {
// handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
$version['version_normalized'] = $repo->versionParser->normalize($version['version']); $version['version_normalized'] = $this->versionParser->normalize($version['version']);
} }
// avoid loading packages which have already been loaded // avoid loading packages which have already been loaded
@ -936,18 +930,18 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
continue; continue;
} }
if ($repo->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) { if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) {
$versionsToLoad[] = $version; $versionsToLoad[] = $version;
} }
} }
$loadedPackages = $repo->createPackages($versionsToLoad, $packagesSource); $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource);
foreach ($loadedPackages as $package) { foreach ($loadedPackages as $package) {
$package->setRepository($repo); $package->setRepository($this);
$packages[spl_object_hash($package)] = $package; $packages[spl_object_hash($package)] = $package;
if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
$package->getAliasOf()->setRepository($repo); $package->getAliasOf()->setRepository($this);
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
} }
} }
@ -961,10 +955,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
/** /**
* TODO v3 should make this private once we can drop PHP 5.3 support
*
* @private
*
* @param ConstraintInterface|null $constraint * @param ConstraintInterface|null $constraint
* @param string $name package name (must be lowercased already) * @param string $name package name (must be lowercased already)
* @param array<string, mixed> $versionData * @param array<string, mixed> $versionData
@ -975,7 +965,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
* *
* @return bool * @return bool
*/ */
public function isVersionAcceptable($constraint, $name, $versionData, array $acceptableStabilities = null, array $stabilityFlags = null) private function isVersionAcceptable($constraint, $name, $versionData, array $acceptableStabilities = null, array $stabilityFlags = null)
{ {
$versions = array($versionData['version_normalized']); $versions = array($versionData['version_normalized']);
@ -1248,15 +1238,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
/** /**
* TODO v3 should make this private once we can drop PHP 5.3 support
* @private
*
* @param mixed[] $packages * @param mixed[] $packages
* @param string|null $source * @param string|null $source
* *
* @return list<CompletePackage|CompleteAliasPackage> * @return list<CompletePackage|CompleteAliasPackage>
*/ */
public function createPackages(array $packages, $source = null) private function createPackages(array $packages, $source = null)
{ {
if (!$packages) { if (!$packages) {
return array(); return array();
@ -1499,25 +1486,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$cache = $this->cache; $cache = $this->cache;
$degradedMode = &$this->degradedMode; $degradedMode = &$this->degradedMode;
$eventDispatcher = $this->eventDispatcher; $eventDispatcher = $this->eventDispatcher;
$repo = $this;
$accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher, $repo) { $accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) {
// package not found is acceptable for a v2 protocol repository // package not found is acceptable for a v2 protocol repository
if ($response->getStatusCode() === 404) { if ($response->getStatusCode() === 404) {
$repo->packagesNotFoundCache[$filename] = true; $this->packagesNotFoundCache[$filename] = true;
return array('packages' => array()); return array('packages' => array());
} }
$json = (string) $response->getBody(); $json = (string) $response->getBody();
if ($json === '' && $response->getStatusCode() === 304) { if ($json === '' && $response->getStatusCode() === 304) {
$repo->freshMetadataUrls[$filename] = true; $this->freshMetadataUrls[$filename] = true;
return true; return true;
} }
if ($eventDispatcher) { if ($eventDispatcher) {
$postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', array('response' => $response, 'repository' => $repo)); $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', array('response' => $response, 'repository' => $this));
$eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
} }
@ -1533,14 +1519,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
if (!$cache->isReadOnly()) { if (!$cache->isReadOnly()) {
$cache->write($cacheKey, $json); $cache->write($cacheKey, $json);
} }
$repo->freshMetadataUrls[$filename] = true; $this->freshMetadataUrls[$filename] = true;
return $data; return $data;
}; };
$reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $repo, $lastModifiedTime) { $reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) {
if ($e instanceof TransportException && $e->getStatusCode() === 404) { if ($e instanceof TransportException && $e->getStatusCode() === 404) {
$repo->packagesNotFoundCache[$filename] = true; $this->packagesNotFoundCache[$filename] = true;
return false; return false;
} }

View File

@ -157,9 +157,7 @@ class Filesystem
$promise = $this->getProcess()->executeAsync($cmd); $promise = $this->getProcess()->executeAsync($cmd);
$self = $this; return $promise->then(function ($process) use ($directory) {
return $promise->then(function ($process) use ($directory, $self) {
// clear stat cache because external processes aren't tracked by the php stat cache // clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache(); clearstatcache();
@ -169,7 +167,7 @@ class Filesystem
} }
} }
return \React\Promise\resolve($self->removeDirectoryPhp($directory)); return \React\Promise\resolve($this->removeDirectoryPhp($directory));
}); });
} }

View File

@ -264,7 +264,6 @@ class HttpDownloader
}; };
} }
$downloader = $this;
$curl = $this->curl; $curl = $this->curl;
$canceler = function () use (&$job, $curl) { $canceler = function () use (&$job, $curl) {
@ -282,19 +281,18 @@ class HttpDownloader
}; };
$promise = new Promise($resolver, $canceler); $promise = new Promise($resolver, $canceler);
$promise = $promise->then(function ($response) use (&$job, $downloader) { $promise = $promise->then(function ($response) use (&$job) {
$job['status'] = HttpDownloader::STATUS_COMPLETED; $job['status'] = HttpDownloader::STATUS_COMPLETED;
$job['response'] = $response; $job['response'] = $response;
// TODO 3.0 this should be done directly on $this when PHP 5.3 is dropped $this->markJobDone();
$downloader->markJobDone();
return $response; return $response;
}, function ($e) use (&$job, $downloader) { }, function ($e) use (&$job) {
$job['status'] = HttpDownloader::STATUS_FAILED; $job['status'] = HttpDownloader::STATUS_FAILED;
$job['exception'] = $e; $job['exception'] = $e;
$downloader->markJobDone(); $this->markJobDone();
throw $e; throw $e;
}); });
@ -351,11 +349,7 @@ class HttpDownloader
} }
} }
/** private function markJobDone(): void
* @private
* @return void
*/
public function markJobDone()
{ {
$this->runningJobs--; $this->runningJobs--;
} }

View File

@ -344,12 +344,7 @@ class ProcessExecutor
return $active; return $active;
} }
/** private function markJobDone(): void
* @private
*
* @return void
*/
public function markJobDone()
{ {
$this->runningJobs--; $this->runningJobs--;
} }

View File

@ -92,7 +92,6 @@ class AutoloadGeneratorTest extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
$this->fs = new Filesystem; $this->fs = new Filesystem;
$that = $this;
$this->workingDir = $this->getUniqueTmpDirectory(); $this->workingDir = $this->getUniqueTmpDirectory();
$this->vendorDir = $this->workingDir.DIRECTORY_SEPARATOR.'composer-test-autoload'; $this->vendorDir = $this->workingDir.DIRECTORY_SEPARATOR.'composer-test-autoload';
@ -101,8 +100,8 @@ class AutoloadGeneratorTest extends TestCase
$this->config = $this->getMockBuilder('Composer\Config')->getMock(); $this->config = $this->getMockBuilder('Composer\Config')->getMock();
$this->configValueMap = array( $this->configValueMap = array(
'vendor-dir' => function () use ($that) { 'vendor-dir' => function () {
return $that->vendorDir; return $this->vendorDir;
}, },
'platform-check' => function () { 'platform-check' => function () {
return true; return true;
@ -111,10 +110,10 @@ class AutoloadGeneratorTest extends TestCase
$this->config->expects($this->atLeastOnce()) $this->config->expects($this->atLeastOnce())
->method('get') ->method('get')
->will($this->returnCallback(function ($arg) use ($that) { ->will($this->returnCallback(function ($arg) {
$ret = null; $ret = null;
if (isset($that->configValueMap[$arg])) { if (isset($this->configValueMap[$arg])) {
$ret = $that->configValueMap[$arg]; $ret = $this->configValueMap[$arg];
if (is_callable($ret)) { if (is_callable($ret)) {
$ret = $ret(); $ret = $ret();
} }
@ -131,10 +130,10 @@ class AutoloadGeneratorTest extends TestCase
->getMock(); ->getMock();
$this->im->expects($this->any()) $this->im->expects($this->any())
->method('getInstallPath') ->method('getInstallPath')
->will($this->returnCallback(function ($package) use ($that) { ->will($this->returnCallback(function ($package) {
$targetDir = $package->getTargetDir(); $targetDir = $package->getTargetDir();
return $that->vendorDir.'/'.$package->getName() . ($targetDir ? '/'.$targetDir : ''); return $this->vendorDir.'/'.$package->getName() . ($targetDir ? '/'.$targetDir : '');
})); }));
$this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock();
$this->repository->expects($this->any()) $this->repository->expects($this->any())

View File

@ -180,8 +180,6 @@ class FileDownloaderTest extends TestCase
public function testDownloadWithCustomProcessedUrl() public function testDownloadWithCustomProcessedUrl()
{ {
$self = $this;
$path = $this->getUniqueTmpDirectory(); $path = $this->getUniqueTmpDirectory();
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
@ -232,16 +230,16 @@ class FileDownloaderTest extends TestCase
$cacheMock $cacheMock
->expects($this->any()) ->expects($this->any())
->method('copyTo') ->method('copyTo')
->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey) {
$self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:');
return false; return false;
})); }));
$cacheMock $cacheMock
->expects($this->any()) ->expects($this->any())
->method('copyFrom') ->method('copyFrom')
->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey) {
$self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:');
return false; return false;
})); }));
@ -250,8 +248,8 @@ class FileDownloaderTest extends TestCase
$httpDownloaderMock $httpDownloaderMock
->expects($this->any()) ->expects($this->any())
->method('addCopy') ->method('addCopy')
->will($this->returnCallback(function ($url) use ($self, $expectedUrl) { ->will($this->returnCallback(function ($url) use ($expectedUrl) {
$self->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); $this->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:');
return \React\Promise\resolve( return \React\Promise\resolve(
new Response(array('url' => 'http://example.org/'), 200, array(), 'file~') new Response(array('url' => 'http://example.org/'), 200, array(), 'file~')
@ -281,8 +279,6 @@ class FileDownloaderTest extends TestCase
public function testDownloadWithCustomCacheKey() public function testDownloadWithCustomCacheKey()
{ {
$self = $this;
$path = $this->getUniqueTmpDirectory(); $path = $this->getUniqueTmpDirectory();
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
@ -334,16 +330,16 @@ class FileDownloaderTest extends TestCase
$cacheMock $cacheMock
->expects($this->any()) ->expects($this->any())
->method('copyTo') ->method('copyTo')
->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey) {
$self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:');
return false; return false;
})); }));
$cacheMock $cacheMock
->expects($this->any()) ->expects($this->any())
->method('copyFrom') ->method('copyFrom')
->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { ->will($this->returnCallback(function ($cacheKey) use ($expectedCacheKey) {
$self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); $this->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:');
return false; return false;
})); }));
@ -352,8 +348,8 @@ class FileDownloaderTest extends TestCase
$httpDownloaderMock $httpDownloaderMock
->expects($this->any()) ->expects($this->any())
->method('addCopy') ->method('addCopy')
->will($this->returnCallback(function ($url) use ($self, $expectedUrl) { ->will($this->returnCallback(function ($url) use ($expectedUrl) {
$self->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); $this->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:');
return \React\Promise\resolve( return \React\Promise\resolve(
new Response(array('url' => 'http://example.org/'), 200, array(), 'file~') new Response(array('url' => 'http://example.org/'), 200, array(), 'file~')

View File

@ -79,13 +79,12 @@ class BasePackageTest extends TestCase
), ),
); );
$self = $this; $createPackage = function ($arr) {
$createPackage = function ($arr) use ($self) { $package = $this->getMockForAbstractClass('\Composer\Package\BasePackage', array(), '', false);
$package = $self->getMockForAbstractClass('\Composer\Package\BasePackage', array(), '', false); $package->expects($this->once())->method('isDev')->will($this->returnValue(true));
$package->expects($self->once())->method('isDev')->will($self->returnValue(true)); $package->expects($this->any())->method('getSourceType')->will($this->returnValue('git'));
$package->expects($self->any())->method('getSourceType')->will($self->returnValue('git')); $package->expects($this->once())->method('getPrettyVersion')->will($this->returnValue('PrettyVersion'));
$package->expects($self->once())->method('getPrettyVersion')->will($self->returnValue('PrettyVersion')); $package->expects($this->any())->method('getSourceReference')->will($this->returnValue($arr['sourceReference']));
$package->expects($self->any())->method('getSourceReference')->will($self->returnValue($arr['sourceReference']));
return array($package, $arr['truncate'], $arr['expected']); return array($package, $arr['truncate'], $arr['expected']);
}; };

View File

@ -37,7 +37,7 @@ class ComposerRepositoryTest extends TestCase
); );
$repository = $this->getMockBuilder('Composer\Repository\ComposerRepository') $repository = $this->getMockBuilder('Composer\Repository\ComposerRepository')
->onlyMethods(array('loadRootServerFile', 'createPackages')) ->onlyMethods(array('loadRootServerFile'))
->setConstructorArgs(array( ->setConstructorArgs(array(
$repoConfig, $repoConfig,
new NullIO, new NullIO,
@ -52,22 +52,15 @@ class ComposerRepositoryTest extends TestCase
->method('loadRootServerFile') ->method('loadRootServerFile')
->will($this->returnValue($repoPackages)); ->will($this->returnValue($repoPackages));
$stubs = array();
foreach ($expected as $at => $arg) {
$stubs[] = $this->getPackage('stub/stub', '1.0.0');
}
$repository
->expects($this->once())
->method('createPackages')
->with($this->identicalTo($expected), $this->equalTo('root file (http://example.org/packages.json)'))
->will($this->returnValue($stubs));
// Triggers initialization // Triggers initialization
$packages = $repository->getPackages(); $packages = $repository->getPackages();
// Final sanity check, ensure the correct number of packages were added. // Final sanity check, ensure the correct number of packages were added.
$this->assertCount(count($expected), $packages); $this->assertCount(count($expected), $packages);
foreach ($expected as $index => $pkg) {
self::assertSame($pkg['name'].' '.$pkg['version'], $packages[$index]->getName().' '.$packages[$index]->getPrettyVersion());
}
} }
public function loadDataProvider() public function loadDataProvider()

View File

@ -49,9 +49,8 @@ class GitTest extends TestCase
*/ */
public function testRunCommandPublicGitHubRepositoryNotInitialClone($protocol, $expectedUrl) public function testRunCommandPublicGitHubRepositoryNotInitialClone($protocol, $expectedUrl)
{ {
$that = $this; $commandCallable = function ($url) use ($expectedUrl) {
$commandCallable = function ($url) use ($that, $expectedUrl) { $this->assertSame($expectedUrl, $url);
$that->assertSame($expectedUrl, $url);
return 'git command'; return 'git command';
}; };
@ -75,9 +74,8 @@ class GitTest extends TestCase
{ {
self::expectException('RuntimeException'); self::expectException('RuntimeException');
$that = $this; $commandCallable = function ($url) {
$commandCallable = function ($url) use ($that) { $this->assertSame('https://github.com/acme/repo', $url);
$that->assertSame('https://github.com/acme/repo', $url);
return 'git command'; return 'git command';
}; };