Skip to content

Commit

Permalink
Merge pull request #165 from lemberg/issue/164-php-fatal
Browse files Browse the repository at this point in the history
PHP Fatal error upon installation/update
  • Loading branch information
T2L committed Mar 6, 2020
2 parents 6f0537e + 54244ca commit 851d955
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 52 deletions.
22 changes: 18 additions & 4 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ final class App {
*/
private $shouldRunInstallation = FALSE;

/**
* Boolean indicating whether the update process should run.
*
* @var bool
*/
private $shouldRunUpdate = FALSE;

/**
* Draft Environment app constructor.
*
Expand Down Expand Up @@ -126,7 +133,9 @@ private function onPostPackageUpdate(UpdateOperation $operation): void {
$targetReleaseDate = $operation->getTargetPackage()->getReleaseDate() ?? $now;
// Package downgrading is not supported by the update manager.
if ($targetReleaseDate >= $initialReleaseDate) {
$this->configUpdateManager->update();
// Run update later (during post command phase) in order to have
// dependencies autoloaded.
$this->shouldRunUpdate = TRUE;
}
}
}
Expand All @@ -149,8 +158,8 @@ private function onPrePackageUninstall(UninstallOperation $operation): void {
* @param \Composer\Script\Event $event
*/
private function handleScriptEvent(ScriptEvent $event): void {
if ($event->getName() === ScriptEvents::POST_INSTALL_CMD || $event->getName() === ScriptEvents::POST_UPDATE_CMD) {
$this->onPostInstallCommand($event);
if ($event->getName() === ScriptEvents::POST_AUTOLOAD_DUMP) {
$this->onPostAutoloadDumpCommand($event);
}
}

Expand All @@ -159,7 +168,7 @@ private function handleScriptEvent(ScriptEvent $event): void {
*
* @param \Composer\Script\Event $event
*/
private function onPostInstallCommand(ScriptEvent $event): void {
private function onPostAutoloadDumpCommand(ScriptEvent $event): void {
if ($this->shouldRunInstallation) {
$this->configInstallManager->install();

Expand All @@ -169,6 +178,11 @@ private function onPostInstallCommand(ScriptEvent $event): void {
$this->configUpdateManager->setLastAppliedUpdateWeight($lastAvailableWeight);
}
$this->shouldRunInstallation = FALSE;

if ($this->shouldRunUpdate) {
$this->configUpdateManager->update();
}
$this->shouldRunUpdate = FALSE;
}

}
3 changes: 1 addition & 2 deletions src/Composer/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ public static function getSubscribedEvents(): array {
PackageEvents::POST_PACKAGE_INSTALL => 'onComposerEvent',
PackageEvents::POST_PACKAGE_UPDATE => 'onComposerEvent',
PackageEvents::PRE_PACKAGE_UNINSTALL => 'onComposerEvent',
ScriptEvents::POST_INSTALL_CMD => 'onComposerEvent',
ScriptEvents::POST_UPDATE_CMD => 'onComposerEvent',
ScriptEvents::POST_AUTOLOAD_DUMP => 'onComposerEvent',
];
}

Expand Down
22 changes: 22 additions & 0 deletions src/Config/Manager/AbstractConfigManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Lemberg\Draft\Environment\Config\Manager;

use Composer\Autoload\ClassLoader;
use Composer\Autoload\ClassMapGenerator;
use Composer\Composer;
use Composer\Factory;
use Composer\IO\IOInterface;
Expand Down Expand Up @@ -43,6 +45,12 @@ final public function __construct(Composer $composer, IOInterface $io, Config $c
$this->composer = $composer;
$this->io = $io;
$this->setConfig($config);

// This code is running in Composer context, newly added packages might
// not be autoloaded.
if (!class_exists(RobotLoader::class)) {
$this->autoloadDependencies();
}
}

/**
Expand Down Expand Up @@ -110,4 +118,18 @@ final protected function setPackageExtra(array $extra): void {
}
}

/**
* Manually autoload dependencies from Nette framework.
*/
final private function autoloadDependencies(): void {
$loader = new ClassLoader();

$vendorDir = $this->composer->getConfig()->get('vendor-dir');
foreach (['nette/utils', 'nette/finder', 'nette/robot-loader'] as $path) {
$loader->addClassMap(ClassMapGenerator::createMap("$vendorDir/$path"));
}

$loader->register();
}

}
81 changes: 37 additions & 44 deletions tests/AppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,14 @@ public function testComposerPostPackageUpdateEventHandlerDoesNotRunWithOtherPack
$initial = new Package('dummy', '1.0.0.0', '^1.0');
$target = new Package('dummy', '1.2.0.0', '^1.0');
$operation = new UpdateOperation($initial, $target);
$event = new PackageEvent(PackageEvents::POST_PACKAGE_UPDATE, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$packageEvent = new PackageEvent(PackageEvents::POST_PACKAGE_UPDATE, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

$this->configUpdateManager
->expects(self::never())
->method('update');

$this->app->handleEvent($packageEvent);
$this->app->handleEvent($event);
}

Expand All @@ -187,10 +191,14 @@ public function testComposerPostPackageUpdateEventHandlerDoesNotRunWithOtherEven
// PackageEvents::PRE_PACKAGE_UNINSTALL event is dispatched.
$initial = new Package(App::PACKAGE_NAME, '1.0.0.0', '^1.0');
$operation = new InstallOperation($initial);
$event = new PackageEvent(PackageEvents::PRE_PACKAGE_INSTALL, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$packageEvent = new PackageEvent(PackageEvents::PRE_PACKAGE_INSTALL, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

$this->configUpdateManager
->expects(self::never())
->method('update');

$this->app->handleEvent($packageEvent);
$this->app->handleEvent($event);
}

Expand All @@ -204,10 +212,14 @@ public function testComposerPostPackageUpdateEventHandlerDoesNotRunWhenDowngradi
$target = new Package(App::PACKAGE_NAME, '1.2.0.0', '^1.0');
$target->setReleaseDate(new \DateTime('yesterday'));
$operation = new UpdateOperation($initial, $target);
$event = new PackageEvent(PackageEvents::POST_PACKAGE_UPDATE, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$packageEvent = new PackageEvent(PackageEvents::POST_PACKAGE_UPDATE, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

$this->configUpdateManager
->expects(self::never())
->method('update');

$this->app->handleEvent($packageEvent);
$this->app->handleEvent($event);
}

Expand All @@ -219,36 +231,36 @@ public function testComposerPostPackageUpdateEventHandlerDoesRun(): void {
$initial = new Package(App::PACKAGE_NAME, '1.0.0.0', '^1.0');
$target = new Package(App::PACKAGE_NAME, '1.2.0.0', '^1.0');
$operation = new UpdateOperation($initial, $target);
$event = new PackageEvent(PackageEvents::POST_PACKAGE_UPDATE, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$packageEvent = new PackageEvent(PackageEvents::POST_PACKAGE_UPDATE, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

$this->configUpdateManager
->expects(self::once())
->method('update');

$this->app->handleEvent($packageEvent);
$this->app->handleEvent($event);
}

/**
* Tests Composer ScriptEvents::POST_INSTALL_CMD and
* ScriptEvents::POST_UPDATE_CMD event handlers.
*
* @dataProvider composerPostInstallOrUpdateCommandEventHandlerDataProvider
* Tests Composer ScriptEvents::POST_AUTOLOAD_DUMP event handler.
*/
public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWithEveryEvent(string $scriptEventName): void {
// Install should not run on every ScriptEvents::POST_INSTALL_CMD or
// ScriptEvents::POST_UPDATE_CMD event dispatch. Install should only run if
// the package itself is being installed.
public function testComposerPostDumpAutoloadCommandEventHandlerDoesNotRunWithEveryEvent(): void {
// Install should not run on every ScriptEvents::POST_AUTOLOAD_DUMP event
// dispatch. Install should only run if the package itself is being
// installed.
$this->configInstallManager
->expects(self::never())
->method('install');

$event = new ScriptEvent($scriptEventName, $this->composer, $this->io);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);
$this->app->handleEvent($event);
}

/**
* Tests Composer ScriptEvents::POST_INSTALL_CMD and
* ScriptEvents::POST_UPDATE_CMD event handlers.
* Tests Composer ScriptEvents::POST_AUTOLOAD_DUMP event handler.
*/
public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWithOtherScriptEvents(): void {
public function testComposerPostDumpAutoloadCommandEventHandlerDoesNotRunWithOtherScriptEvents(): void {
// Install should not run on any other script event even if the package
// itself is being installed.
$package = new Package(App::PACKAGE_NAME, '1.0.0.0', '^1.0');
Expand All @@ -265,18 +277,15 @@ public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWith
}

/**
* Tests Composer ScriptEvents::POST_INSTALL_CMD and
* ScriptEvents::POST_UPDATE_CMD event handlers.
*
* @dataProvider composerPostInstallOrUpdateCommandEventHandlerDataProvider
* Tests Composer ScriptEvents::POST_AUTOLOAD_DUMP event handler.
*/
public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWithOtherPackageEvents(string $scriptEventName): void {
public function testComposerPostDumpAutoloadCommandEventHandlerDoesNotRunWithOtherPackageEvents(): void {
// Install should not run on any other script event even if the package
// itself is being installed.
$package = new Package(App::PACKAGE_NAME, '1.0.0.0', '^1.0');
$operation = new InstallOperation($package);
$packageEvent = new PackageEvent(PackageEvents::PRE_PACKAGE_INSTALL, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent($scriptEventName, $this->composer, $this->io);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

$this->configInstallManager
->expects(self::never())
Expand All @@ -287,17 +296,14 @@ public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWith
}

/**
* Tests Composer ScriptEvents::POST_INSTALL_CMD and
* ScriptEvents::POST_UPDATE_CMD event handlers.
*
* @dataProvider composerPostInstallOrUpdateCommandEventHandlerDataProvider
* Tests Composer ScriptEvents::POST_AUTOLOAD_DUMP event handler.
*/
public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWithOtherPackages(string $scriptEventName): void {
public function testComposerPostDumpAutoloadCommandEventHandlerDoesNotRunWithOtherPackages(): void {
// Install should not run if any other package is being installed.
$package = new Package('dummy', '1.0.0.0', '^1.0');
$operation = new InstallOperation($package);
$packageEvent = new PackageEvent(PackageEvents::POST_PACKAGE_INSTALL, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent($scriptEventName, $this->composer, $this->io);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

$this->configInstallManager
->expects(self::never())
Expand All @@ -308,16 +314,13 @@ public function testComposerPostInstallOrUpdateCommandEventHandlerDoesNotRunWith
}

/**
* Tests Composer ScriptEvents::POST_INSTALL_CMD and
* ScriptEvents::POST_UPDATE_CMD event handlers.
*
* @dataProvider composerPostInstallOrUpdateCommandEventHandlerDataProvider
* Tests Composer ScriptEvents::POST_AUTOLOAD_DUMP event handler.
*/
public function testComposerPostInstallOrUpdateCommandEventHandlerDoesRun(string $scriptEventName): void {
public function testComposerPostDumpAutoloadCommandEventHandlerDoesRun(): void {
$package = new Package(App::PACKAGE_NAME, '1.0.0.0', '^1.0');
$operation = new InstallOperation($package);
$packageEvent = new PackageEvent(PackageEvents::POST_PACKAGE_INSTALL, $this->composer, $this->io, FALSE, $this->policy, $this->pool, $this->installedRepo, $this->request, [$operation], $operation);
$event = new ScriptEvent($scriptEventName, $this->composer, $this->io);
$event = new ScriptEvent(ScriptEvents::POST_AUTOLOAD_DUMP, $this->composer, $this->io);

// Install should run.
$this->configInstallManager
Expand All @@ -334,14 +337,4 @@ public function testComposerPostInstallOrUpdateCommandEventHandlerDoesRun(string
$this->app->handleEvent($event);
}

/**
* @return array<int,array<int,string>>
*/
public function composerPostInstallOrUpdateCommandEventHandlerDataProvider(): array {
return [
[ScriptEvents::POST_INSTALL_CMD],
[ScriptEvents::POST_UPDATE_CMD],
];
}

}
9 changes: 7 additions & 2 deletions tests/Composer/PluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Lemberg\Tests\Draft\Environment\Composer;

use Composer\Composer;
use Composer\Config;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Pool;
Expand Down Expand Up @@ -69,8 +70,7 @@ public function testComposerPlugin(): void {
PackageEvents::POST_PACKAGE_INSTALL => 'onComposerEvent',
PackageEvents::POST_PACKAGE_UPDATE => 'onComposerEvent',
PackageEvents::PRE_PACKAGE_UNINSTALL => 'onComposerEvent',
ScriptEvents::POST_INSTALL_CMD => 'onComposerEvent',
ScriptEvents::POST_UPDATE_CMD => 'onComposerEvent',
ScriptEvents::POST_AUTOLOAD_DUMP => 'onComposerEvent',
];
self::assertSame($expected, Plugin::getSubscribedEvents());

Expand Down Expand Up @@ -138,6 +138,7 @@ private function setUpComposerMock(bool $returnPackage): void {
'findPackage',
'getInstallationManager',
'getInstallPath',
'getConfig',
])
->getMock();
$this->composer->expects(self::any())
Expand All @@ -158,6 +159,10 @@ private function setUpComposerMock(bool $returnPackage): void {
->method('getInstallPath')
->with($findPackageReturnValue)
->willReturn(sys_get_temp_dir());

$this->composer->expects(self::any())
->method('getConfig')
->willReturn(new Config());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Lemberg\Tests\Draft\Environment\Config\Update\Step;

use Composer\Composer;
use Composer\Config as ComposerConfig;
use Composer\IO\IOInterface;
use Lemberg\Draft\Environment\Config\Config;
use Lemberg\Draft\Environment\Config\Manager\UpdateManager;
Expand Down Expand Up @@ -51,6 +52,7 @@ final class ExportAllAvailableConfigurationTest extends TestCase {
*/
protected function setUp(): void {
$this->composer = new Composer();
$this->composer->setConfig(new ComposerConfig());
$this->io = $this->createMock(IOInterface::class);

// Mock source and target configuration directories.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Lemberg\Tests\Draft\Environment\Config\Update\Step;

use Composer\Composer;
use Composer\Config as ComposerConfig;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
Expand Down Expand Up @@ -53,6 +54,7 @@ protected function setUp(): void {
$this->composer = new Composer();
$package = new RootPackage(App::PACKAGE_NAME, '^3.0', '3.0.0.0');
$this->composer->setPackage($package);
$this->composer->setConfig(new ComposerConfig());
$this->io = $this->createMock(IOInterface::class);

// Mock source and target configuration directories.
Expand Down
2 changes: 2 additions & 0 deletions tests/Config/Update/Step/SetAsAlreadyInstalledStepTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Lemberg\Tests\Draft\Environment\Config\Update\Step;

use Composer\Composer;
use Composer\Config as ComposerConfig;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
Expand Down Expand Up @@ -73,6 +74,7 @@ protected function setUp(): void {
->with(App::PACKAGE_NAME, '*')
->willReturn($package);
$this->composer->setRepositoryManager($manager);
$this->composer->setConfig(new ComposerConfig());

$this->io = $this->createMock(IOInterface::class);

Expand Down

0 comments on commit 851d955

Please sign in to comment.