Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merge branch 'hotfix/20'
Browse files Browse the repository at this point in the history
Close #20
  • Loading branch information
weierophinney committed Oct 11, 2016
2 parents 70911e5 + dc28eda commit 3396c7f
Show file tree
Hide file tree
Showing 12 changed files with 966 additions and 7 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ All notable changes to this project will be documented in this file, in reverse
support for defining both a module and a component in the same package,
ensuring that they are both injected, and at the appropriate positions in the
module list.
- [#20](https://github.com/zendframework/zend-component-installer/pull/20) adds
support for modules that define `getModuleDependencies()`. When such a module
is encountered, the installer will now also inject entries for these modules
into the application module list, such that they *always* appear before the
current module. This change ensures that dependencies are loaded in the
correct order.

### Deprecated

Expand Down
102 changes: 99 additions & 3 deletions src/ComponentInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\InstallerEvent;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event as CommandEvent;
use Composer\Script\PackageEvent;
use DirectoryIterator;

/**
* If a package represents a component module, update the application configuration.
Expand Down Expand Up @@ -178,6 +178,9 @@ public function onPostPackageInstall(PackageEvent $event)
return;
}

$this->includeModuleClasses($package);
$applicationModules = $this->findApplicationModules();

$this->marshalInstallableModules($extra, $options)
->each(function ($module) use ($name) {
})
Expand All @@ -187,11 +190,104 @@ public function onPostPackageInstall(PackageEvent $event)
return $injectors;
}, new Collection([]))
// Inject modules into configuration
->each(function ($injector, $module) use ($name, $packageTypes) {
->each(function ($injector, $module) use ($name, $packageTypes, $applicationModules) {
$injector->setApplicationModules($applicationModules);
$this->injectModuleIntoConfig($name, $module, $injector, $packageTypes[$module]);
});
}

/**
* Find all Module classes in the package and include them.
* Module classes is used later
* @see \Zend\ComponentInstaller\Injector\AbstractInjector::injectAfterDependencies
* - to get package dependencies - module method `getModuleDependencies`
* and add component in a correct order on the module list.
*
* It works with PSR-0, PSR-4, 'classmap' and 'files' composer autoloading.
*
* @param PackageInterface $package
* @return void
*/
private function includeModuleClasses(PackageInterface $package)
{
$installer = $this->composer->getInstallationManager();
$packagePath = $installer->getInstallPath($package);

$autoload = $package->getAutoload();
foreach ($autoload as $type => $map) {
foreach ($map as $namespace => $path) {
switch ($type) {
case 'classmap':
$fullPath = sprintf('%s/%s', $packagePath, $path);
if (is_dir(rtrim($fullPath, '/'))) {
$modulePath = sprintf('%s%s', $fullPath, 'Module.php');
} elseif (substr($path, -10) == 'Module.php') {
$modulePath = $fullPath;
} else {
continue 2;
}
break;
case 'files':
if (substr($path, -10) != 'Module.php') {
continue 2;
}
$modulePath = sprintf('%s/%s', $packagePath, $path);
break;
case 'psr-0':
$modulePath = sprintf(
'%s/%s%s%s',
$packagePath,
$path,
str_replace('\\', '/', $namespace),
'Module.php'
);
break;
case 'psr-4':
$modulePath = sprintf(
'%s/%s%s',
$packagePath,
$path,
'Module.php'
);
break;
default:
continue 2;
}

if (file_exists($modulePath)) {
include $modulePath;
}
}
}
}

/**
* Find all modules of the application.
*
* @return array
*/
private function findApplicationModules()
{
$modulePath = is_string($this->projectRoot) && ! empty($this->projectRoot)
? sprintf('%s/module', $this->projectRoot)
: 'module';

$modules = [];

if (is_dir($modulePath)) {
$directoryIterator = new DirectoryIterator($modulePath);
foreach ($directoryIterator as $file) {
if ($file->isDot() || ! $file->isDir()) {
continue;
}

$modules[] = $file->getBasename();
}
}

return $modules;
}

/**
* post-package-uninstall event hook
*
Expand Down
170 changes: 170 additions & 0 deletions src/Injector/AbstractInjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ abstract class AbstractInjector implements InjectorInterface
protected $allowedTypes = [
self::TYPE_COMPONENT,
self::TYPE_MODULE,
self::TYPE_DEPENDENCY,
self::TYPE_BEFORE_APPLICATION,
];

/**
Expand Down Expand Up @@ -99,6 +101,13 @@ abstract class AbstractInjector implements InjectorInterface
*/
protected $removalPatterns = [];

/**
* Modules of the application.
*
* @var array
*/
protected $applicationModules = [];

/**
* Constructor
*
Expand Down Expand Up @@ -151,6 +160,18 @@ public function inject($package, $type, IOInterface $io)
return;
}

if ($type == self::TYPE_COMPONENT
&& $this->injectAfterDependencies($package, $config, $io)
) {
return;
}

if ($type == self::TYPE_MODULE
&& $this->injectBeforeApplicationModules($package, $config, $io)
) {
return;
}

$pattern = $this->injectionPatterns[$type]['pattern'];
$replacement = sprintf(
$this->injectionPatterns[$type]['replacement'],
Expand All @@ -161,6 +182,155 @@ public function inject($package, $type, IOInterface $io)
file_put_contents($this->configFile, $config);
}

/**
* Inject component $package after all dependencies into $config and return true.
* If any of dependencies is not registered the method will write error to $io
* and will also return true, to prevent injecting this package later.
* Method return false only in case when dependencies for the package are not found.
*
* @param string $package
* @param string $config
* @param IOInterface $io
* @return bool
*/
private function injectAfterDependencies($package, $config, IOInterface $io)
{
$moduleClass = sprintf('%s\\%s', $package, 'Module');
if (class_exists($moduleClass) && method_exists($moduleClass, 'getModuleDependencies')) {
$module = new $moduleClass();
$dependencies = $module->getModuleDependencies();

if ($dependencies) {
foreach ($dependencies as $dependency) {
if (! $this->isRegisteredInConfig($dependency, $config)) {
$io->write(sprintf(
'<error> Dependency %s is not registered in the configuration</error>',
$dependency
));

return true;
}
}

$lastDependency = $this->findLastDependency($dependencies, $config);

$pattern = sprintf(
$this->injectionPatterns[self::TYPE_DEPENDENCY]['pattern'],
preg_quote($lastDependency, '/')
);
$replacement = sprintf(
$this->injectionPatterns[self::TYPE_DEPENDENCY]['replacement'],
$package
);

$config = preg_replace($pattern, $replacement, $config);
file_put_contents($this->configFile, $config);

return true;
}
}

return false;
}

/**
* Find which of dependency packages is the last one on the module list.
*
* @param array $dependencies
* @param string $config
* @return string
*/
private function findLastDependency(array $dependencies, $config)
{
if (count($dependencies) == 1) {
return reset($dependencies);
}

$longLength = 0;
$last = null;
foreach ($dependencies as $dependency) {
preg_match(sprintf($this->isRegisteredPattern, preg_quote($dependency, '/')), $config, $matches);

$length = strlen($matches[0]);
if ($length > $longLength) {
$longLength = $length;
$last = $dependency;
}
}

return $last;
}

/**
* Inject module $package into $config before the first found application module
* and return true.
* If there is no any enabled application module, this method will return false.
*
* @param string $package
* @param string $config
* @param IOInterface $io
* @return bool
*/
private function injectBeforeApplicationModules($package, $config, IOInterface $io)
{
if ($firstApplicationModule = $this->findFirstEnabledApplicationModule($this->applicationModules, $config)) {
$pattern = sprintf(
$this->injectionPatterns[self::TYPE_BEFORE_APPLICATION]['pattern'],
preg_quote($firstApplicationModule, '/')
);
$replacement = sprintf(
$this->injectionPatterns[self::TYPE_BEFORE_APPLICATION]['replacement'],
$package
);

$config = preg_replace($pattern, $replacement, $config);
file_put_contents($this->configFile, $config);

return true;
}

return false;
}

/**
* Find the first enabled application module from list $modules in the $config.
* If any module is not found method will return null.
*
* @param array $modules
* @param string $config
* @return string|null
*/
private function findFirstEnabledApplicationModule(array $modules, $config)
{
$shortest = strlen($config);
$first = null;
foreach ($modules as $module) {
if (! $this->isRegistered($module)) {
continue;
}

preg_match(sprintf($this->isRegisteredPattern, preg_quote($module, '/')), $config, $matches);

$length = strlen($matches[0]);
if ($length < $shortest) {
$shortest = $length;
$first = $module;
}
}

return $first;
}

/**
* {@inheritDoc}
*/
public function setApplicationModules(array $modules)
{
$this->applicationModules = $modules;

return $this;
}

/**
* Remove a package from the configuration.
*
Expand Down
8 changes: 8 additions & 0 deletions src/Injector/ApplicationConfigInjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ class ApplicationConfigInjector extends AbstractInjector
'pattern' => "/('modules'\s*\=\>\s*(?:array\s*\(|\[).*?)\n(\s+)(\)|\])/s",
'replacement' => "\$1\n\$2 '%s',\n\$2\$3",
],
self::TYPE_DEPENDENCY => [
'pattern' => '/^(\s+)(\'modules\'\s*\=\>\s*(?:array\s*\(|\[)[^)\]]*\'%s\')/m',
'replacement' => "\$1\$2,\n\$1 '%s'",
],
self::TYPE_BEFORE_APPLICATION => [
'pattern' => '/^(\s+)(\'modules\'\s*\=\>\s*(?:array\s*\(|\[)[^)\]]*)(\'%s\')/m',
'replacement' => "\$1\$2'%s',\n$1 \$3",
],
];

/**
Expand Down
8 changes: 8 additions & 0 deletions src/Injector/ConfigInjectorChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,12 @@ public function getCollection()
{
return $this->chain;
}

/**
* {@inheritDoc}
*/
public function setApplicationModules(array $modules)
{
return $this;
}
}
10 changes: 10 additions & 0 deletions src/Injector/InjectorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface InjectorInterface
const TYPE_CONFIG_PROVIDER = 0;
const TYPE_COMPONENT = 1;
const TYPE_MODULE = 2;
const TYPE_DEPENDENCY = 3;
const TYPE_BEFORE_APPLICATION = 4;

/**
* Whether or not the injector can handle the given type.
Expand Down Expand Up @@ -55,4 +57,12 @@ public function inject($package, $type, IOInterface $io);
* @return void
*/
public function remove($package, IOInterface $io);

/**
* Set modules of the application.
*
* @param array $modules
* @return self
*/
public function setApplicationModules(array $modules);
}

0 comments on commit 3396c7f

Please sign in to comment.