Merge pull request #267 from meadsteve/plugin-discovery
RFC: Plugin discovery
This commit is contained in:
commit
3370240242
|
@ -11,6 +11,9 @@ namespace PHPCI\Controller;
|
|||
|
||||
use b8;
|
||||
use PHPCI\Model\Build;
|
||||
use PHPCI\Plugin\Util\ComposerPluginInformation;
|
||||
use PHPCI\Plugin\Util\FilesPluginInformation;
|
||||
use PHPCI\Plugin\Util\PluginInformationCollection;
|
||||
|
||||
/**
|
||||
* Plugin Controller - Provides support for installing Composer packages.
|
||||
|
@ -59,8 +62,18 @@ class PluginController extends \PHPCI\Controller
|
|||
$this->view->required = $this->required;
|
||||
|
||||
$json = $this->getComposerJson();
|
||||
$this->view->installed = $json['require'];
|
||||
$this->view->suggested = $json['suggest'];
|
||||
$this->view->installedPackages = $json['require'];
|
||||
$this->view->suggestedPackages = $json['suggest'];
|
||||
|
||||
$pluginInfo = new PluginInformationCollection();
|
||||
$pluginInfo->add(FilesPluginInformation::newFromDir(
|
||||
PHPCI_DIR . "PHPCI/Plugin/"
|
||||
));
|
||||
$pluginInfo->add(ComposerPluginInformation::buildFromYaml(
|
||||
PHPCI_DIR . "vendor/composer/installed.json"
|
||||
));
|
||||
|
||||
$this->view->plugins = $pluginInfo->getInstalledPlugins();
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
|
147
PHPCI/Plugin/Util/ComposerPluginInformation.php
Normal file
147
PHPCI/Plugin/Util/ComposerPluginInformation.php
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCI\Plugin\Util;
|
||||
|
||||
|
||||
class ComposerPluginInformation implements InstalledPluginInformation
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $composerPackages;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $pluginInfo = null;
|
||||
|
||||
/**
|
||||
* @param string $filePath The path of installed.json created by composer.
|
||||
* @return ComposerPluginInformation
|
||||
*/
|
||||
public static function buildFromYaml($filePath)
|
||||
{
|
||||
if (file_exists($filePath)) {
|
||||
$installed = json_decode(file_get_contents($filePath));
|
||||
}
|
||||
else {
|
||||
$installed = array();
|
||||
}
|
||||
return new self($installed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass[] $composerPackages This should be the contents of the
|
||||
* installed.json file created by composer
|
||||
*/
|
||||
public function __construct(array $composerPackages)
|
||||
{
|
||||
$this->composerPackages = $composerPackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects. Each one represents an available plugin
|
||||
* and will have the following properties:
|
||||
* name - The friendly name of the plugin (may be an empty string)
|
||||
* class - The class of the plugin (will include namespace)
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
public function getInstalledPlugins()
|
||||
{
|
||||
$this->loadPluginInfo();
|
||||
return $this->pluginInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the class names of plugins that have been
|
||||
* loaded.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPluginClasses()
|
||||
{
|
||||
return array_map(
|
||||
function($plugin) {
|
||||
return $plugin->class;
|
||||
},
|
||||
$this->getInstalledPlugins()
|
||||
);
|
||||
}
|
||||
|
||||
protected function loadPluginInfo()
|
||||
{
|
||||
if ($this->pluginInfo !== null) {
|
||||
return;
|
||||
}
|
||||
$this->pluginInfo = array();
|
||||
foreach($this->composerPackages as $package) {
|
||||
$this->addPluginsFromPackage($package);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $package
|
||||
*/
|
||||
protected function addPluginsFromPackage($package)
|
||||
{
|
||||
if (isset($package->extra->phpci)) {
|
||||
$phpciData = $package->extra->phpci;
|
||||
|
||||
if (isset($phpciData->pluginNamespace)) {
|
||||
$rootNamespace = $phpciData->pluginNamespace;
|
||||
}
|
||||
else {
|
||||
$rootNamespace = "";
|
||||
}
|
||||
|
||||
if (is_array($phpciData->suppliedPlugins)) {
|
||||
$this->addPlugins(
|
||||
$phpciData->suppliedPlugins,
|
||||
$package->name,
|
||||
$rootNamespace
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass[] $plugins
|
||||
* @param string $sourcePackageName
|
||||
* @param string $rootNamespace
|
||||
*/
|
||||
protected function addPlugins(
|
||||
array $plugins,
|
||||
$sourcePackageName,
|
||||
$rootNamespace = "")
|
||||
{
|
||||
foreach($plugins as $plugin) {
|
||||
if (!isset($plugin->class)) {
|
||||
continue;
|
||||
}
|
||||
$this->addPlugin($plugin, $sourcePackageName, $rootNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $plugin
|
||||
* @param string $sourcePackageName
|
||||
* @param string $rootNamespace
|
||||
*/
|
||||
protected function addPlugin(
|
||||
$plugin,
|
||||
$sourcePackageName,
|
||||
$rootNamespace = "")
|
||||
{
|
||||
$newPlugin = clone $plugin;
|
||||
|
||||
$newPlugin->class = $rootNamespace . $newPlugin->class;
|
||||
|
||||
if (!isset($newPlugin->name)) {
|
||||
$newPlugin->name = "";
|
||||
}
|
||||
|
||||
$newPlugin->source = $sourcePackageName;
|
||||
|
||||
$this->pluginInfo[] = $newPlugin;
|
||||
}
|
||||
}
|
105
PHPCI/Plugin/Util/FilesPluginInformation.php
Normal file
105
PHPCI/Plugin/Util/FilesPluginInformation.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCI\Plugin\Util;
|
||||
|
||||
class FilesPluginInformation implements InstalledPluginInformation
|
||||
{
|
||||
|
||||
/**
|
||||
* A collection of all the file path information for
|
||||
* the installed plugins.
|
||||
*
|
||||
* @var \SplFileInfo[]
|
||||
*/
|
||||
protected $files;
|
||||
|
||||
/**
|
||||
* Each item in the array contains the information for
|
||||
* a single plugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pluginInfo = null;
|
||||
|
||||
public static function newFromDir($dirPath)
|
||||
{
|
||||
return new self(new \DirectoryIterator($dirPath));
|
||||
}
|
||||
|
||||
function __construct(\Iterator $files)
|
||||
{
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects. Each one represents an available plugin
|
||||
* and will have the following properties:
|
||||
* name - The friendly name of the plugin (may be an empty string)
|
||||
* class - The class of the plugin (will include namespace)
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
public function getInstalledPlugins()
|
||||
{
|
||||
if ($this->pluginInfo === null) {
|
||||
$this->loadPluginInfo();
|
||||
}
|
||||
return $this->pluginInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the class names of plugins that have been
|
||||
* loaded.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPluginClasses()
|
||||
{
|
||||
return array_map(
|
||||
function($plugin) {
|
||||
return $plugin->class;
|
||||
},
|
||||
$this->getInstalledPlugins()
|
||||
);
|
||||
}
|
||||
|
||||
protected function loadPluginInfo()
|
||||
{
|
||||
$this->pluginInfo = array();
|
||||
foreach($this->files as $fileInfo) {
|
||||
if ($fileInfo instanceof \SplFileInfo) {
|
||||
if ($fileInfo->isFile()) {
|
||||
$this->addPluginFromFile($fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function addPluginFromFile(\SplFileInfo $fileInfo)
|
||||
{
|
||||
$newPlugin = new \stdClass();
|
||||
$newPlugin->class = $this->getFullClassFromFile($fileInfo);
|
||||
$newPlugin->source = "core";
|
||||
$parts = explode('\\', $newPlugin->class);
|
||||
$newPlugin->name = end($parts);
|
||||
|
||||
$this->pluginInfo[] = $newPlugin;
|
||||
}
|
||||
|
||||
protected function getFullClassFromFile(\SplFileInfo $fileInfo)
|
||||
{
|
||||
//TODO: Something less horrible than a regular expression
|
||||
// on the contents of a file
|
||||
$contents = file_get_contents($fileInfo->getRealPath());
|
||||
|
||||
$matches = array();
|
||||
preg_match('#class +([A-Za-z]+) +implements#i', $contents, $matches);
|
||||
$className = $matches[1];
|
||||
|
||||
$matches = array();
|
||||
preg_match('#namespace +([A-Za-z\\\\]+);#i', $contents, $matches);
|
||||
$namespace = $matches[1];
|
||||
|
||||
return $namespace . '\\' . $className;
|
||||
}
|
||||
|
||||
}
|
23
PHPCI/Plugin/Util/InstalledPluginInformation.php
Normal file
23
PHPCI/Plugin/Util/InstalledPluginInformation.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
namespace PHPCI\Plugin\Util;
|
||||
|
||||
|
||||
interface InstalledPluginInformation
|
||||
{
|
||||
/**
|
||||
* Returns an array of objects. Each one represents an available plugin
|
||||
* and will have the following properties:
|
||||
* name - The friendly name of the plugin (may be an empty string)
|
||||
* class - The class of the plugin (will include namespace)
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
public function getInstalledPlugins();
|
||||
|
||||
/**
|
||||
* Returns an array of all the class names of plugins that have been
|
||||
* loaded.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPluginClasses();
|
||||
}
|
48
PHPCI/Plugin/Util/PluginInformationCollection.php
Normal file
48
PHPCI/Plugin/Util/PluginInformationCollection.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCI\Plugin\Util;
|
||||
|
||||
class PluginInformationCollection implements InstalledPluginInformation
|
||||
{
|
||||
/**
|
||||
* @var InstalledPluginInformation[]
|
||||
*/
|
||||
protected $pluginInformations = array();
|
||||
|
||||
public function add(InstalledPluginInformation $information)
|
||||
{
|
||||
$this->pluginInformations[] = $information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects. Each one represents an available plugin
|
||||
* and will have the following properties:
|
||||
* name - The friendly name of the plugin (may be an empty string)
|
||||
* class - The class of the plugin (will include namespace)
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
public function getInstalledPlugins()
|
||||
{
|
||||
$arr = array();
|
||||
foreach($this->pluginInformations as $single) {
|
||||
$arr = array_merge($arr, $single->getInstalledPlugins());
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the class names of plugins that have been
|
||||
* loaded.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPluginClasses()
|
||||
{
|
||||
$arr = array();
|
||||
foreach($this->pluginInformations as $single) {
|
||||
$arr = array_merge($arr, $single->getPluginClasses());
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<h1 id="title">Plugins</h1>
|
||||
<h1 id="title">Packages and Provided Plugins</h1>
|
||||
<?php if (!$canInstall): ?>
|
||||
<p class="alert alert-danger">PHPCI cannot automatically install/remove plugins for you, as either the <strong>shell_exec()</strong>
|
||||
function is disabled or PHPCI could not find Composer. PHPCI will update composer.json for you, but you will need to run Composer manually to make the changes.</p>
|
||||
|
@ -21,7 +21,30 @@
|
|||
<?php endif; ?>
|
||||
|
||||
<div class="box">
|
||||
<h3 class="title">Installed Plugins</h3>
|
||||
<h3 class="title">Available Plugins</h3>
|
||||
|
||||
<table class="table-striped table-bordered table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Class</th>
|
||||
<th>Provided by Package</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($plugins as $plugin): ?>
|
||||
<tr>
|
||||
<td><?= $plugin->name; ?></td>
|
||||
<td><?= $plugin->class; ?></td>
|
||||
<td><?= $plugin->source; ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h3 class="title">Installed Packages</h3>
|
||||
|
||||
<table class="table-striped table-bordered table">
|
||||
<thead>
|
||||
|
@ -32,7 +55,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($installed as $package => $version): ?>
|
||||
<?php foreach ($installedPackages as $package => $version): ?>
|
||||
<tr>
|
||||
<td><?php echo $package; ?></td>
|
||||
<td><?php echo $version; ?></td>
|
||||
|
@ -48,7 +71,7 @@
|
|||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h3 class="title">Suggested Plugins</h3>
|
||||
<h3 class="title">Suggested Packages</h3>
|
||||
|
||||
<table class="table-striped table-bordered table">
|
||||
<thead>
|
||||
|
@ -59,8 +82,8 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($suggested as $package => $version): ?>
|
||||
<?php if (in_array($package, array_keys($installed))) { continue; } ?>
|
||||
<?php foreach ($suggestedPackages as $package => $version): ?>
|
||||
<?php if (in_array($package, array_keys($installedPackages))) { continue; } ?>
|
||||
<tr>
|
||||
<td><?php echo $package; ?></td>
|
||||
<td><?php echo $version; ?></td>
|
||||
|
@ -76,7 +99,7 @@
|
|||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h3 class="title">Search Packagist for More Plugins</h3>
|
||||
<h3 class="title">Search Packagist for More Packages</h3>
|
||||
|
||||
<div class="input-group">
|
||||
<input id="search-query" type="text" class="form-control">
|
||||
|
|
51
Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php
Normal file
51
Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCI\Plugin\Tests\Util;
|
||||
|
||||
use PHPCI\Plugin\Util\ComposerPluginInformation;
|
||||
|
||||
class ComposerPluginInformationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var ComposerPluginInformation
|
||||
*/
|
||||
protected $testedInformation;
|
||||
|
||||
protected function setUpFromFile($file)
|
||||
{
|
||||
$this->testedInformation = ComposerPluginInformation::buildFromYaml($file);
|
||||
}
|
||||
|
||||
protected function phpciSetup()
|
||||
{
|
||||
$this->setUpFromFile(
|
||||
__DIR__ . "/../../../../vendor/composer/installed.json"
|
||||
);
|
||||
}
|
||||
|
||||
public function testBuildFromYaml_ReturnsInstance()
|
||||
{
|
||||
$this->phpciSetup();
|
||||
$this->assertInstanceOf(
|
||||
'\PHPCI\Plugin\Util\ComposerPluginInformation',
|
||||
$this->testedInformation
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetInstalledPlugins_ReturnsStdClassArray()
|
||||
{
|
||||
$this->phpciSetup();
|
||||
$plugins = $this->testedInformation->getInstalledPlugins();
|
||||
$this->assertInternalType("array", $plugins);
|
||||
$this->assertContainsOnly("stdClass", $plugins);
|
||||
}
|
||||
|
||||
public function testGetPluginClasses_ReturnsStringArray()
|
||||
{
|
||||
$this->phpciSetup();
|
||||
$classes = $this->testedInformation->getPluginClasses();
|
||||
$this->assertInternalType("array", $classes);
|
||||
$this->assertContainsOnly("string", $classes);
|
||||
}
|
||||
}
|
||||
|
26
Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php
Normal file
26
Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCI\Plugin\Tests\Util;
|
||||
|
||||
use PHPCI\Plugin\Util\FilesPluginInformation;
|
||||
|
||||
class FilesPluginInformationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
public function testGetInstalledPlugins_returnsObjectes()
|
||||
{
|
||||
$pluginDirPath = realpath(__DIR__ . "/../../../../PHPCI/Plugin/");
|
||||
$test = FilesPluginInformation::newFromDir($pluginDirPath);
|
||||
$pluginInfos = $test->getInstalledPlugins();
|
||||
$this->assertContainsOnlyInstancesOf('stdClass', $pluginInfos);
|
||||
}
|
||||
|
||||
public function testGetPluginClasses_returnsStrings()
|
||||
{
|
||||
$pluginDirPath = realpath(__DIR__ . "/../../../../PHPCI/Plugin/");
|
||||
$test = FilesPluginInformation::newFromDir($pluginDirPath);
|
||||
$pluginInfos = $test->getPluginClasses();
|
||||
$this->assertContainsOnly('string', $pluginInfos);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue