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

Commit

Permalink
Merge dd9f778 into d9ae75c
Browse files Browse the repository at this point in the history
  • Loading branch information
weierophinney committed Nov 6, 2017
2 parents d9ae75c + dd9f778 commit 98e23b9
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 79 deletions.
222 changes: 144 additions & 78 deletions src/ComponentInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Zend\ComponentInstaller;

use ArrayObject;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\PackageEvent;
Expand Down Expand Up @@ -203,7 +204,7 @@ public function onPostPackageInstall(PackageEvent $event)

/**
* Find all Module classes in the package and their dependencies
* - method `getModuleDependencies` of Module class.
* via method `getModuleDependencies` of Module class.
*
* These dependencies are used later
* @see \Zend\ComponentInstaller\Injector\AbstractInjector::injectAfterDependencies
Expand All @@ -216,87 +217,13 @@ public function onPostPackageInstall(PackageEvent $event)
*/
private function loadModuleClassesDependencies(PackageInterface $package)
{
$dependencies = [];
$dependencies = new ArrayObject([]);
$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)) {
if ($result = $this->getModuleDependencies($modulePath)) {
$dependencies += $result;
}
}
}
}

return $dependencies;
}

/**
* @param string $file
* @return array
*/
private function getModuleDependencies($file)
{
$content = file_get_contents($file);
if (preg_match('/namespace\s+([^\s]+)\s*;/', $content, $m)) {
$moduleName = $m[1];

// @codingStandardsIgnoreStart
$regExp = '/public\s+function\s+getModuleDependencies\s*\(\s*\)\s*{[^}]*return\s*(?:array\(|\[)([^})\]]*)(\)|\])/';
// @codingStandardsIgnoreEnd
if (preg_match($regExp, $content, $m)) {
$dependencies = array_filter(
explode(',', stripslashes(rtrim(preg_replace('/[\s"\']/', '', $m[1]), ',')))
);

if ($dependencies) {
return [$moduleName => $dependencies];
}
}
}
$this->mapAutoloaders($package->getAutoload(), $dependencies, $packagePath);

return [];
return $dependencies->getArrayCopy();
}

/**
Expand Down Expand Up @@ -687,4 +614,143 @@ private function cacheInjector(Injector\InjectorInterface $injector, $packageTyp
{
$this->cachedInjectors[$packageType] = $injector;
}

/**
* Iterate through each autoloader type to find dependencies.
*
* @param array $autoload List of autoloader types and associated autoloader definitions.
* @param ArrayObject $dependencies Module dependencies defined by the module.
* @param string $packagePath Path to the package on the filesystem.
* @return void
*/
private function mapAutoloaders(array $autoload, ArrayObject $dependencies, $packagePath)
{
foreach ($autoload as $type => $map) {
$this->mapType($map, $type, $dependencies, $packagePath);
}
}

/**
* Iterate through a single autolaoder type to find dependencies.
*
* @param array $map Map of namespace => path(s) pairs.
* @param string $type Type of autoloader being iterated.
* @param ArrayObject $dependencies Module dependencies defined by the module.
* @param string $packagePath Path to the package on the filesystem.
* @return void
*/
private function mapType(array $map, $type, ArrayObject $dependencies, $packagePath)
{
foreach ($map as $namespace => $paths) {
$paths = (array) $paths;
$this->mapNamespacePaths($paths, $namespace, $type, $dependencies, $packagePath);
}
}

/**
* Iterate through the paths defined for a given namespace.
*
* @param array $paths Paths defined for the given namespace.
* @param string $namespace PHP namespace to which the paths map.
* @param string $type Type of autoloader being iterated.
* @param ArrayObject $dependencies Module dependencies defined by the module.
* @param string $packagePath Path to the package on the filesystem.
* @return void
*/
private function mapNamespacePaths(array $paths, $namespace, $type, ArrayObject $dependencies, $packagePath)
{
foreach ($paths as $path) {
$this->mapPath($path, $namespace, $type, $dependencies, $packagePath);
}
}

/**
* Find module dependencies for a given namespace for a given path.
*
* @param string $path Path to inspect.
* @param string $namespace PHP namespace to which the paths map.
* @param string $type Type of autoloader being iterated.
* @param ArrayObject $dependencies Module dependencies defined by the module.
* @param string $packagePath Path to the package on the filesystem.
* @return void
*/
private function mapPath($path, $namespace, $type, ArrayObject $dependencies, $packagePath)
{
switch ($type) {
case 'classmap':
$fullPath = sprintf('%s/%s', $packagePath, $path);
if (substr($path, -10) === 'Module.php') {
$modulePath = $fullPath;
break;
}

$modulePath = sprintf('%s/Module.php', rtrim($fullPath, '/'));
break;
case 'files':
if (substr($path, -10) !== 'Module.php') {
return;
}
$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:
return;
}

if (! file_exists($modulePath)) {
return;
}

$result = $this->getModuleDependencies($modulePath);

if (empty($result)) {
return;
}

// Mimic array + array operation in ArrayObject
$dependencies->exchangeArray($dependencies->getArrayCopy() + $result);
}

/**
* @param string $file
* @return array
*/
private function getModuleDependencies($file)
{
$content = file_get_contents($file);
if (preg_match('/namespace\s+([^\s]+)\s*;/', $content, $m)) {
$moduleName = $m[1];

// @codingStandardsIgnoreStart
$regExp = '/public\s+function\s+getModuleDependencies\s*\(\s*\)\s*{[^}]*return\s*(?:array\(|\[)([^})\]]*)(\)|\])/';
// @codingStandardsIgnoreEnd
if (preg_match($regExp, $content, $m)) {
$dependencies = array_filter(
explode(',', stripslashes(rtrim(preg_replace('/[\s"\']/', '', $m[1]), ',')))
);

if ($dependencies) {
return [$moduleName => $dependencies];
}
}
}

return [];
}
}
120 changes: 119 additions & 1 deletion test/ComponentInstallerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
use Composer\Package\PackageInterface;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStreamWrapper;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use ReflectionObject;
use Zend\ComponentInstaller\ComponentInstaller;

class ComponentInstallerTest extends TestCase
Expand Down Expand Up @@ -1503,12 +1505,128 @@ public function moduleClass()
*/
public function testGetModuleDependenciesFromModuleClass($file, $result)
{
$r = new \ReflectionObject($this->installer);
$r = new ReflectionObject($this->installer);
$rm = $r->getMethod('getModuleDependencies');
$rm->setAccessible(true);

$dependencies = $rm->invoke($this->installer, $file);

$this->assertEquals($result, $dependencies);
}

public function testGetModuleClassesDependenciesHandlesAutoloadersWithMultiplePathsMappedToSameNamespace()
{
$installPath = 'install/path';
$this->setUpModuleDependencies($installPath);

$autoloaders = [
'psr-0' => [
'DoesNotExist\\' => [
'src/Psr0/',
'src/Psr0Too/',
],
],
'psr-4' => [
'DoesNotExistEither\\' => [
'src/Psr4/',
'src/Psr4Too/',
],
],
'classmap' => [
'src/Classmapped/',
'src/ClassmappedToo/',
],
'files' => [
'src/File/Module.php',
'src/FileToo/Module.php',
],
];

$package = $this->prophesize(PackageInterface::class);
$package->getAutoload()->willReturn($autoloaders);

$this->installationManager
->getInstallPath(Argument::that([$package, 'reveal']))
->willReturn(vfsStream::url('project/' . $installPath));

$r = new ReflectionObject($this->installer);
$rm = $r->getMethod('loadModuleClassesDependencies');
$rm->setAccessible(true);

$dependencies = $rm->invoke($this->installer, $package->reveal());
$this->assertEquals([
'DoesNotExist' => ['DoesNotExistDependency'],
'DoesNotExistEither' => ['DoesNotExistEitherDependency'],
'ClassmappedToo' => ['ClassmappedTooDependency'],
'File' => ['FileDependency'],
], $dependencies);
}

public function setUpModuleDependencies($path)
{
$this->createModuleClass(
$path . '/src/Psr0Too/DoesNotExist/Module.php',
<<<CONTENT
<?php
namespace DoesNotExist;
class Module
{
public function getModuleDependencies()
{
return ['DoesNotExistDependency'];
}
}
CONTENT
);

$this->createModuleClass(
$path . '/src/Psr4/Module.php',
<<<CONTENT
<?php
namespace DoesNotExistEither;
class Module
{
public function getModuleDependencies()
{
return ['DoesNotExistEitherDependency'];
}
}
CONTENT
);

mkdir(sprintf('%s/%s/src/ClassmappedToo', vfsStream::url('project'), $path));
$this->createModuleClass(
$path . '/src/ClassmappedToo/Module.php',
<<<CONTENT
<?php
namespace ClassmappedToo;
class Module
{
public function getModuleDependencies()
{
return ['ClassmappedTooDependency'];
}
}
CONTENT
);

$this->createModuleClass(
$path . '/src/File/Module.php',
<<<CONTENT
<?php
namespace File;
class Module
{
public function getModuleDependencies()
{
return ['FileDependency'];
}
}
CONTENT
);
}
}

0 comments on commit 98e23b9

Please sign in to comment.