Register requirements as objects

This neutralizes the need for a unique name per requirement as
requirements are now compared based on their type and condition.
It also allows to implement a solution to add simple conditional
requirements.

refs #8508
This commit is contained in:
Johannes Meyer 2015-02-25 13:38:38 +01:00
parent 85e6fce867
commit 24d0999fa4
3 changed files with 316 additions and 130 deletions

View File

@ -1,7 +1,6 @@
<?php
use Icinga\Web\Wizard;
use Icinga\Module\Setup\Requirements;
$requirements = $form->getRequirements();
@ -10,21 +9,22 @@ $requirements = $form->getRequirements();
<tbody>
<?php foreach ($requirements as $requirement): ?>
<tr>
<td><h2><?= $requirement->title; ?></h2></td>
<td><h2><?= $requirement->getTitle(); ?></h2></td>
<td style="width: 50%">
<?php if (is_array($requirement->description)): ?>
<?php $descriptions = $requirement->getDescriptions(); ?>
<?php if (count($descriptions) > 1): ?>
<ul>
<?php foreach ($requirement->description as $desc): ?>
<?php foreach ($descriptions as $desc): ?>
<li><?= $desc; ?></li>
<?php endforeach ?>
</ul>
<?php else: ?>
<?= $requirement->description; ?>
<?php elseif (! empty($descriptions)): ?>
<?= $descriptions[0]; ?>
<?php endif ?>
</td>
<td class="state <?= $requirement->state === Requirements::STATE_OK ? 'fulfilled' : (
$requirement->state === Requirements::STATE_OPTIONAL ? 'not-available' : 'missing'
); ?>"><?= $requirement->message; ?></td>
<td class="state <?= $requirement->getState() ? 'fulfilled' : (
$requirement->isOptional() ? 'not-available' : 'missing'
); ?>"><?= $requirement->getStateText(); ?></td>
</tr>
<?php endforeach ?>
<tr>

View File

@ -0,0 +1,279 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Setup;
use LogicException;
abstract class Requirement
{
/**
* The state of this requirement
*
* @var bool
*/
protected $state;
/**
* A descriptive text representing the current state of this requirement
*
* @var string
*/
protected $stateText;
/**
* The descriptions of this requirement
*
* @var array
*/
protected $descriptions;
/**
* The title of this requirement
*
* @var string
*/
protected $title;
/**
* The condition of this requirement
*
* @var mixed
*/
protected $condition;
/**
* Whether this requirement is optional
*
* @var bool
*/
protected $optional;
/**
* The alias to display the condition with in a human readable way
*
* @var string
*/
protected $alias;
/**
* Create a new requirement
*
* @param array $options
*
* @throws LogicException In case there exists no setter for an option's key
*/
public function __construct(array $options = array())
{
$this->optional = false;
$this->descriptions = array();
foreach ($options as $key => $value) {
$setMethod = 'set' . ucfirst($key);
$addMethod = 'add' . ucfirst($key);
if (method_exists($this, $setMethod)) {
$this->$setMethod($value);
} elseif (method_exists($this, $addMethod)) {
$this->$addMethod($value);
} else {
throw LogicException('No setter found for option key: ' . $key);
}
}
}
/**
* Set the state of this requirement
*
* @param bool $state
*
* @return Requirement
*/
public function setState($state)
{
$this->state = (bool) $state;
return $this;
}
/**
* Return the state of this requirement
*
* Evaluates the requirement in case there is no state set yet.
*
* @return int
*/
public function getState()
{
if ($this->state === null) {
$this->state = $this->evaluate();
}
return $this->state;
}
/**
* Set a descriptive text for this requirement's current state
*
* @param string $text
*
* @return Requirement
*/
public function setStateText($text)
{
$this->stateText = $text;
return $this;
}
/**
* Return a descriptive text for this requirement's current state
*
* @return string
*/
public function getStateText()
{
return $this->stateText;
}
/**
* Add a description for this requirement
*
* @param string $description
*
* @return Requirement
*/
public function addDescription($description)
{
$this->descriptions[] = $description;
return $this;
}
/**
* Return the descriptions of this wizard
*
* @return array
*/
public function getDescriptions()
{
return $this->descriptions;
}
/**
* Set the title for this requirement
*
* @param string $title
*
* @return Requirement
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Return the title of this requirement
*
* In case there is no title set the alias is returned instead.
*
* @return string
*/
public function getTitle()
{
if ($this->title === null) {
return $this->getAlias();
}
return $this->title;
}
/**
* Set the condition for this requirement
*
* @param mixed $condition
*
* @return Requirement
*/
public function setCondition($condition)
{
$this->condition = $condition;
return $this;
}
/**
* Return the condition of this requirement
*
* @return mixed
*/
public function getCondition()
{
return $this->condition;
}
/**
* Set whether this requirement is optional
*
* @param bool $state
*
* @return Requirement
*/
public function setOptional($state = true)
{
$this->optional = (bool) $state;
return $this;
}
/**
* Return whether this requirement is optional
*
* @return bool
*/
public function isOptional()
{
return $this->optional;
}
/**
* Set the alias to display the condition with in a human readable way
*
* @param string $alias
*
* @return Requirement
*/
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
/**
* Return the alias to display the condition with in a human readable way
*
* @return string
*/
public function getAlias()
{
return $this->alias;
}
/**
* Evaluate this requirement and return whether it is fulfilled
*
* @return bool
*/
abstract protected function evaluate();
/**
* Return whether the given requirement equals this one
*
* @param Requirement $requirement
*
* @return bool
*/
public function equals(Requirement $requirement)
{
if ($requirement instanceof static) {
return $this->getCondition() === $requirement->getCondition();
}
return false;
}
}

View File

@ -8,27 +8,9 @@ use IteratorAggregate;
/**
* Container to store and handle requirements
*
* TODO: Requirements should be registered as objects with a specific purpose (PhpModRequirement, PhpIniRequirement, ..)
* so that it's not necessary to define unique identifiers which may differ between different modules.
*/
class Requirements implements IteratorAggregate
{
/**
* Identifier representing the state OK
*/
const STATE_OK = 2;
/**
* Identifier representing the state OPTIONAL
*/
const STATE_OPTIONAL = 1;
/**
* Identifier representing the state MANDATORY
*/
const STATE_MANDATORY = 0;
/**
* The registered requirements
*
@ -39,44 +21,35 @@ class Requirements implements IteratorAggregate
/**
* Register a requirement
*
* @param object $requirement The requirement to add
* @param Requirement $requirement The requirement to add
*
* @return self
* @return Requirements
*/
public function add($name, $requirement)
public function add(Requirement $requirement)
{
$this->requirements[$name] = array_key_exists($name, $this->requirements)
? $this->combine($this->requirements[$name], $requirement)
: $requirement;
$merged = false;
foreach ($this as $knownRequirement) {
if ($requirement->equals($knownRequirement)) {
if ($knownRequirement->isOptional() && !$requirement->isOptional()) {
$knownRequirement->setOptional(false);
}
foreach ($requirement->getDescriptions() as $description) {
$knownRequirement->addDescription($description);
}
$merged = true;
break;
}
}
if (! $merged) {
$this->requirements[] = $requirement;
}
return $this;
}
/**
* Combine the two given requirements
*
* Returns the most important requirement with the description from the other one being added.
*
* @param object $oldRequirement
* @param object $newRequirement
*
* @return object
*/
protected function combine($oldRequirement, $newRequirement)
{
if ($newRequirement->state === static::STATE_MANDATORY && $oldRequirement->state === static::STATE_OPTIONAL) {
$tempRequirement = $oldRequirement;
$oldRequirement = $newRequirement;
$newRequirement = $tempRequirement;
}
if (! is_array($oldRequirement->description)) {
$oldRequirement->description = array($oldRequirement->description);
}
$oldRequirement->description[] = $newRequirement->description;
return $oldRequirement;
}
/**
* Return all registered requirements
*
@ -97,83 +70,17 @@ class Requirements implements IteratorAggregate
return new ArrayIterator($this->getAll());
}
/**
* Register an optional requirement
*
* @param string $name
* @param string $title
* @param string $description
* @param bool $state
* @param string $message
*
* @return self
*/
public function addOptional($name, $title, $description, $state, $message)
{
$this->add(
$name,
(object) array(
'title' => $title,
'message' => $message,
'description' => $description,
'state' => (bool) $state ? static::STATE_OK : static::STATE_OPTIONAL
)
);
return $this;
}
/**
* Register a mandatory requirement
*
* @param string $name
* @param string $title
* @param string $description
* @param bool $state
* @param string $message
*
* @return self
*/
public function addMandatory($name, $title, $description, $state, $message)
{
$this->add(
$name,
(object) array(
'title' => $title,
'message' => $message,
'description' => $description,
'state' => (bool) $state ? static::STATE_OK : static::STATE_MANDATORY
)
);
return $this;
}
/**
* Register the given requirements
*
* @param Requirements $requirements The requirements to register
*
* @return self
* @return Requirements
*/
public function merge(Requirements $requirements)
{
foreach ($requirements->getAll() as $name => $requirement) {
$this->add($name, $requirement);
}
return $this;
}
/**
* Make all registered requirements being optional
*
* @return self
*/
public function allOptional()
{
foreach ($this->getAll() as $requirement) {
if ($requirement->state === static::STATE_MANDATORY) {
$requirement->state = static::STATE_OPTIONAL;
}
foreach ($requirements as $requirement) {
$this->add($requirement);
}
return $this;
@ -186,8 +93,8 @@ class Requirements implements IteratorAggregate
*/
public function fulfilled()
{
foreach ($this->getAll() as $requirement) {
if ($requirement->state === static::STATE_MANDATORY) {
foreach ($this as $requirement) {
if (! $requirement->getState() && !$requirement->isOptional()) {
return false;
}
}