workflows: Add Phpstan (#378)

With 8.2 php tests
This commit is contained in:
Johannes Meyer 2023-08-31 11:24:37 +02:00 committed by GitHub
commit f35fa250f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 4692 additions and 143 deletions

View file

@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1']
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
os: ['ubuntu-latest']
steps:
@ -31,16 +31,26 @@ jobs:
tools: phpcs
- name: Setup dependencies
run: composer require -n --no-progress overtrue/phplint
run: |
composer require -n --no-progress overtrue/phplint
git clone --depth 1 https://github.com/Icinga/icingaweb2.git vendor/icingaweb2
git clone --depth 1 https://github.com/Icinga/icingadb-web.git vendor/icingadb-web
git clone --depth 1 https://github.com/Icinga/icingaweb2-module-director.git vendor/icingaweb2-module-director
git clone --depth 1 -b snapshot/nightly https://github.com/Icinga/icinga-php-library.git vendor/icinga-php-library
git clone --depth 1 -b snapshot/nightly https://github.com/Icinga/icinga-php-thirdparty.git vendor/icinga-php-thirdparty
- name: PHP Lint
if: success() || matrix.allow_failure
if: ${{ ! cancelled() }}
run: ./vendor/bin/phplint -n --exclude={^vendor/.*} -- .
- name: PHP CodeSniffer
if: success() || matrix.allow_failure
if: ${{ ! cancelled() }}
run: phpcs
- name: PHPStan
if: ${{ ! cancelled() }}
uses: php-actions/phpstan@v3
test:
name: Unit tests with php ${{ matrix.php }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}

View file

@ -110,8 +110,8 @@ class ProcessCommand extends Command
exit(1);
}
$name = $this->params->get('config');
try {
$name = $this->params->get('config');
if ($name === null) {
$name = $this->getFirstProcessName();
}
@ -132,8 +132,8 @@ class ProcessCommand extends Command
}
}
/** @var BpNode $node */
try {
/** @var BpNode $node */
$node = $bp->getNode($nodeName);
if (Module::exists('icingadb')
&& (! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())

View file

@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\IcingaDbObject;
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\DataView\DataView;
use Icinga\Web\Url;
use ipl\Stdlib\Filter;
@ -54,7 +55,8 @@ class HostController extends Controller
->from('hoststatus', array('host_name'))
->where('host_name', $hostName);
if ($this->applyRestriction('monitoring/filter/objects', $query)->fetchRow() !== false) {
$this->applyRestriction('monitoring/filter/objects', $query);
if ($query->fetchRow() !== false) {
$this->redirectNow(Url::fromPath('monitoring/host/show')->setParams($this->params));
}
}

View file

@ -35,6 +35,7 @@ use ipl\Html\HtmlString;
use ipl\Html\TemplateString;
use ipl\Html\Text;
use ipl\Web\Control\SortControl;
use ipl\Web\FormElement\TermInput;
use ipl\Web\Widget\Link;
use ipl\Web\Widget\Icon;
@ -199,7 +200,7 @@ class ProcessController extends Controller
$controls->add(Breadcrumb::create(clone $renderer));
if (! $this->showFullscreen && ! $this->view->compact) {
$controls->add(
new RenderedProcessActionBar($bp, $renderer, $this->Auth(), $this->url())
new RenderedProcessActionBar($bp, $renderer, $this->url())
);
}
@ -282,7 +283,9 @@ class ProcessController extends Controller
->handleRequest($this->getServerRequest());
if ($form->hasElement('children')) {
foreach ($form->getElement('children')->prepareMultipartUpdate($this->getServerRequest()) as $update) {
/** @var TermInput $childrenElement */
$childrenElement = $form->getElement('children');
foreach ($childrenElement->prepareMultipartUpdate($this->getServerRequest()) as $update) {
if (! is_array($update)) {
$update = [$update];
}

View file

@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\IcingaDbObject;
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
use Icinga\Module\Icingadb\Model\Service;
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\DataView\DataView;
use Icinga\Web\Url;
use ipl\Stdlib\Filter;
@ -61,7 +62,8 @@ class ServiceController extends Controller
->where('host_name', $hostName)
->where('service_description', $serviceName);
if ($this->applyRestriction('monitoring/filter/objects', $query)->fetchRow() !== false) {
$this->applyRestriction('monitoring/filter/objects', $query);
if ($query->fetchRow() !== false) {
$this->redirectNow(Url::fromPath('monitoring/service/show')->setParams($this->params));
}
}

View file

@ -378,9 +378,11 @@ class AddNodeForm extends CompatForm
$changes->createNode(BpConfig::escapeName($this->getValue('name')), $properties);
} else {
/** @var TermInput $term */
$term = $this->getElement('children');
$children = array_unique(array_map(function ($term) {
return $term->getSearchValue();
}, $this->getElement('children')->getTerms()));
}, $term->getTerms()));
if ($nodeType === 'host' || $nodeType === 'service') {
$stateOverrides = $this->getValue('stateOverrides');

View file

@ -155,7 +155,7 @@ class BpUploadForm extends BpConfigBaseForm
protected function processUploadedSource()
{
/** @var \Zend_Form_Element_File $el */
/** @var ?\Zend_Form_Element_File $el */
$el = $this->getElement('uploaded_file');
if ($el && $this->hasBeenSent()) {

View file

@ -6,19 +6,22 @@ use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
use Icinga\Web\View;
class DeleteNodeForm extends BpConfigBaseForm
{
/** @var Node */
protected $node;
/** @var BpNode */
/** @var ?BpNode */
protected $parentNode;
public function setup()
{
$node = $this->node;
$nodeName = $node->getAlias() ?? $node->getName();
/** @var View $view */
$view = $this->getView();
$this->addHtml(
'<h2>' . $view->escape(

View file

@ -3,6 +3,7 @@
namespace Icinga\Module\Businessprocess\Forms;
use Icinga\Application\Icinga;
use Icinga\Application\Web;
use Icinga\Exception\Http\HttpException;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\BpNode;
@ -136,7 +137,9 @@ class MoveNodeForm extends BpConfigBaseForm
);
} catch (ModificationError $e) {
$this->notifyError($e->getMessage());
Icinga::app()->getResponse()
/** @var Web $app */
$app = Icinga::app();
$app->getResponse()
// Web 2's JS forces a content update for non-200s. Our own JS
// can't prevent this, hence we're not making this a 400 :(
//->setHttpResponseCode(400)

View file

@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\Modification\ProcessChanges;
use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
use Icinga\Web\Notification;
use Icinga\Web\View;
class ProcessForm extends BpConfigBaseForm
{
@ -16,8 +17,11 @@ class ProcessForm extends BpConfigBaseForm
public function setup()
{
if ($this->node !== null) {
/** @var View $view */
$view = $this->getView();
$this->addHtml(
'<h2>' . $this->getView()->escape(
'<h2>' . $view->escape(
sprintf($this->translate('Modify "%s"'), $this->node->getAlias())
) . '</h2>'
);

View file

@ -5,13 +5,14 @@ namespace Icinga\Module\Businessprocess\Forms;
use Icinga\Module\Businessprocess\MonitoredNode;
use Icinga\Module\Businessprocess\Simulation;
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
use Icinga\Web\View;
class SimulationForm extends BpConfigBaseForm
{
/** @var MonitoredNode */
protected $node;
/** @var MonitoredNode */
/** @var ?MonitoredNode */
protected $simulatedNode;
/** @var Simulation */
@ -36,6 +37,7 @@ class SimulationForm extends BpConfigBaseForm
$node = $this->node;
}
/** @var View $view */
$view = $this->getView();
if ($hasSimulation) {
$title = $this->translate('Modify simulation for %s');

View file

@ -1,40 +0,0 @@
<?php
// Avoid complaints about missing namespace and invalid class name
// @codingStandardsIgnoreStart
class Zend_View_Helper_FormStateOverrides extends Zend_View_Helper_FormElement
{
// @codingStandardsIgnoreEnd
public function formStateOverrides($name, $value = null, $attribs = null)
{
$states = $attribs['states'];
unset($attribs['states']);
$attribs['multiple'] = '';
$html = '';
foreach ($states as $state => $label) {
if ($state === 0) {
continue;
}
$chosen = $state;
if (isset($value[$state])) {
$chosen = $value[$state];
}
$options = [$state => t('Keep actual state')] + $states;
$html .= '<label><span>' . $this->view->escape($label) . '</span>';
$html .= $this->view->formSelect(
sprintf('%s[%d]', substr($name, 0, -2), $state),
$chosen,
$attribs,
$options
);
$html .= '</label>';
}
return $html;
}
}

View file

@ -226,7 +226,7 @@ class BpConfig
/**
* Whether changes have been applied to this configuration
*
* @return int
* @return bool
*/
public function hasChanges()
{
@ -631,7 +631,7 @@ class BpConfig
/**
* @param string $name
* @return Node
* @return MonitoredNode|BpNode
* @throws Exception
*/
public function getNode($name)

View file

@ -21,7 +21,7 @@ class BpNode extends Node
protected $display = 0;
/** @var Node[] */
/** @var ?Node[] */
protected $children;
/** @var array */
@ -623,18 +623,14 @@ class BpNode extends Node
switch ($this->getOperator()) {
case self::OP_AND:
return 'AND';
break;
case self::OP_OR:
return 'OR';
case self::OP_XOR:
return 'XOR';
break;
case self::OP_NOT:
return 'NOT';
break;
case self::OP_DEGRADED:
return 'DEG';
break;
default:
// MIN
$this->assertNumericOperator();

View file

@ -10,10 +10,10 @@ use ipl\Stdlib\Str;
trait Sort
{
/** @var string Current sort specification */
/** @var ?string Current sort specification */
protected $sort;
/** @var callable Actual sorting function */
/** @var ?callable Actual sorting function */
protected $sortFn;
/**
@ -29,14 +29,18 @@ trait Sort
/**
* Set the sort specification
*
* @param string $sort
* @param ?string $sort
*
* @return $this
*
* @throws InvalidArgumentException When sorting according to the specified specification is not possible
*/
public function setSort(string $sort): self
public function setSort(?string $sort): self
{
if (empty($sort)) {
return $this;
}
list($sortBy, $direction) = Str::symmetricSplit($sort, ' ', 2, 'asc');
switch ($sortBy) {

View file

@ -1,36 +0,0 @@
<?php
namespace Icinga\Module\Businessprocess;
use Icinga\Web\Request;
use Icinga\Web\Form as WebForm;
class Form extends WebForm
{
public function __construct($options = null)
{
parent::__construct($options);
$this->setup();
}
public function addHidden($name, $value = null)
{
$this->addElement('hidden', $name);
$this->getElement($name)->setDecorators(array('ViewHelper'));
if ($value !== null) {
$this->setDefault($name, $value);
}
return $this;
}
public function handleRequest(Request $request = null)
{
parent::handleRequest();
return $this;
}
public static function construct()
{
return new static;
}
}

View file

@ -111,8 +111,8 @@ abstract class NodeAction
public static function create($actionName, $nodeName)
{
$className = __NAMESPACE__ . '\\Node' . ucfirst($actionName) . 'Action';
$object = new $className($nodeName);
return $object;
return new $className($nodeName);
}
/**
@ -144,7 +144,7 @@ abstract class NodeAction
*/
public static function unSerialize($string)
{
$object = json_decode($string, JSON_FORCE_OBJECT);
$object = json_decode($string, true);
$action = self::create($object['actionName'], $object['nodeName']);
foreach ($object['properties'] as $key => $val) {

View file

@ -68,10 +68,9 @@ class NodeRemoveAction extends NodeAction
$parentName = $this->getParentName();
$node = $config->getNode($name);
$this->updateStateOverrides(
$node,
$parentName ? $config->getNode($parentName) : null
);
/** @var ?BpNode $parentBpNode */
$parentBpNode = $parentName ? $config->getNode($parentName) : null;
$this->updateStateOverrides($node, $parentBpNode);
if ($parentName === null) {
if (! $config->hasBpNode($name)) {

View file

@ -100,7 +100,6 @@ class ProcessChanges
/**
* @param $nodeName
* @param Node|null $parent
* @return $this
*/
public function copyNode($nodeName)
@ -211,7 +210,7 @@ class ProcessChanges
/**
* Number of stacked changes
*
* @return bool
* @return int
*/
public function count()
{

View file

@ -47,7 +47,7 @@ abstract class Node
self::NODE_EMPTY => 0
);
/** @var string Alias of the node */
/** @var ?string Alias of the node */
protected $alias;
/**
@ -74,7 +74,7 @@ abstract class Node
/**
* Node state
*
* @var int
* @var ?int
*/
protected $state;
@ -98,7 +98,7 @@ abstract class Node
/**
* This node's icon
*
* @var string
* @var ?string
*/
protected $icon;
@ -346,7 +346,7 @@ abstract class Node
/**
* Get the alias of the node
*
* @return string
* @return ?string
*/
public function getAlias()
{
@ -443,7 +443,7 @@ abstract class Node
throw new ProgrammingError(
'Got invalid state for node %s: %s',
$this->getName(),
var_export($state, 1) . var_export($this->stateToSortStateMap, 1)
var_export($state, true) . var_export($this->stateToSortStateMap, true)
);
}

View file

@ -16,7 +16,7 @@ use ipl\Html\ValidHtml;
class ServiceDetailExtension extends ServiceDetailExtensionHook
{
/** @var LegacyStorage */
/** @var ?LegacyStorage */
private $storage;
/** @var string */

View file

@ -13,7 +13,7 @@ use Icinga\Module\Monitoring\Object\Service;
class DetailviewExtension extends DetailviewExtensionHook
{
/** @var LegacyStorage */
/** @var ?LegacyStorage */
private $storage;
/** @var string */

View file

@ -48,6 +48,7 @@ class Breadcrumb extends BaseHtmlElement
$parts = array();
while ($nodeName = array_pop($path)) {
/** @var BpNode $node */
$node = $bp->getNode($nodeName);
$renderer->setParentNode($node);
array_unshift(

View file

@ -145,6 +145,10 @@ abstract class Renderer extends HtmlDocument
*/
public function appliesCustomSorting(): bool
{
if (empty($this->getSort())) {
return false;
}
list($sortBy, $_) = Str::symmetricSplit($this->getSort(), ' ', 2);
list($defaultSortBy, $_) = Str::symmetricSplit($this->getDefaultSort(), ' ', 2);
@ -165,12 +169,10 @@ abstract class Renderer extends HtmlDocument
/**
* @param $summary
* @return BaseHtmlElement
* @return ?BaseHtmlElement
*/
public function renderStateBadges($summary, $totalChildren)
{
$elements = [];
$itemCount = Html::tag(
'span',
[
@ -181,7 +183,7 @@ abstract class Renderer extends HtmlDocument
sprintf(mtp('businessprocess', '%u Child', '%u Children', $totalChildren), $totalChildren)
);
$elements[] = array_filter([
$elements = array_filter([
$this->createBadgeGroup($summary, 'CRITICAL'),
$this->createBadgeGroup($summary, 'UNKNOWN'),
$this->createBadgeGroup($summary, 'WARNING'),

View file

@ -35,9 +35,8 @@ class NodeTile extends BaseHtmlElement
/**
* NodeTile constructor.
* @param Renderer $renderer
* @param $name
* @param Node $node
* @param null $path
* @param ?array $path
*/
public function __construct(Renderer $renderer, Node $node, $path = null)
{

View file

@ -11,7 +11,7 @@ use Icinga\Module\Businessprocess\Metadata;
class LegacyConfigParser
{
/** @var string */
/** @var ?string */
protected static $prevKey;
/** @var int */
@ -247,6 +247,7 @@ class LegacyConfigParser
{
// state_overrides <bp-node>!<child>|n-n[,n-n]!<child>|n-n[,n-n]
$segments = preg_split('~\s*!\s*~', substr($line, 16));
/** @var BpNode $node */
$node = $bp->getNode(array_shift($segments));
foreach ($segments as $overrideDef) {
list($childName, $overrides) = preg_split('~\s*\|\s*~', $overrideDef, 2);

View file

@ -11,6 +11,8 @@ class LegacyConfigRenderer
/** @var array */
protected $renderedNodes;
protected $config;
/**
* LecagyConfigRenderer constructor
*

View file

@ -49,7 +49,7 @@ abstract class BaseTestCase extends \Icinga\Test\BaseTestCase
}
/**
* @param null $subDir
* @param ?string $subDir
* @return string
*/
protected function getTestsBaseDir($subDir = null)

View file

@ -12,7 +12,7 @@ use ipl\Web\Widget\Icon;
class RenderedProcessActionBar extends ActionBar
{
public function __construct(BpConfig $config, Renderer $renderer, Auth $auth, Url $url)
public function __construct(BpConfig $config, Renderer $renderer, Url $url)
{
$meta = $config->getMetadata();

View file

@ -210,6 +210,7 @@ class Controller extends CompatController
protected function loadBpConfig()
{
$name = $this->params->get('config');
/** @var LegacyStorage $storage */
$storage = $this->storage();
if (! $storage->hasProcess($name)) {
@ -248,7 +249,7 @@ class Controller extends CompatController
}
/**
* @return LegacyStorage|Storage
* @return LegacyStorage
*/
protected function storage()
{

View file

@ -17,6 +17,8 @@ class FormLoader
$basedir = $module->getFormDir();
$ns = '\\Icinga\\Module\\' . ucfirst($module->getName()) . '\\Forms\\';
}
$file = null;
if (preg_match('~^[a-z0-9/]+$~i', $name)) {
$parts = preg_split('~/~', $name);
$class = ucfirst(array_pop($parts)) . 'Form';

View file

@ -13,7 +13,7 @@ abstract class QuickBaseForm extends Zend_Form implements ValidHtml
* The Icinga module this form belongs to. Usually only set if the
* form is initialized through the FormLoader
*
* @var Module
* @var ?Module
*/
protected $icingaModule;
@ -123,7 +123,6 @@ abstract class QuickBaseForm extends Zend_Form implements ValidHtml
}
if (array_key_exists('icingaModule', $options)) {
/** @var Module icingaModule */
$this->icingaModule = $options['icingaModule'];
$this->icingaModuleName = $this->icingaModule->getName();
unset($options['icingaModule']);

View file

@ -3,6 +3,7 @@
namespace Icinga\Module\Businessprocess\Web\Form;
use Icinga\Application\Icinga;
use Icinga\Application\Web;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Notification;
use Icinga\Web\Request;
@ -45,7 +46,7 @@ abstract class QuickForm extends QuickBaseForm
protected $request;
/**
* @var Url
* @var ?Url
*/
protected $successUrl;
@ -251,6 +252,10 @@ abstract class QuickForm extends QuickBaseForm
{
}
/**
* @param $action string|Url
* @return $this
*/
public function setAction($action)
{
if ($action instanceof Url) {
@ -428,14 +433,18 @@ abstract class QuickForm extends QuickBaseForm
protected function redirectAndExit($url)
{
/** @var Web $app */
$app = Icinga::app();
/** @var Response $response */
$response = Icinga::app()->getFrontController()->getResponse();
$response = $app->getFrontController()->getResponse();
$response->redirectAndExit($url);
}
protected function setHttpResponseCode($code)
{
Icinga::app()->getFrontController()->getResponse()->setHttpResponseCode($code);
/** @var Web $app */
$app = Icinga::app();
$app->getFrontController()->getResponse()->setHttpResponseCode($code);
return $this;
}
@ -461,8 +470,10 @@ abstract class QuickForm extends QuickBaseForm
public function getRequest()
{
if ($this->request === null) {
/** @var Web $app */
$app = Icinga::app();
/** @var Request $request */
$request = Icinga::app()->getFrontController()->getRequest();
$request = $app->getFrontController()->getRequest();
$this->setRequest($request);
}
return $this->request;
@ -471,14 +482,15 @@ abstract class QuickForm extends QuickBaseForm
public function hasBeenSent()
{
if ($this->hasBeenSent === null) {
/** @var Request $req */
if ($this->request === null) {
$req = Icinga::app()->getFrontController()->getRequest();
/** @var Web $app */
$app = Icinga::app();
$req = $app->getFrontController()->getRequest();
} else {
$req = $this->request;
}
/** @var Request $req */
if ($req->isPost()) {
$post = $req->getPost();
$this->hasBeenSent = array_key_exists(self::ID, $post) &&

View file

@ -44,6 +44,7 @@ class HostServiceTermValidator extends BaseValidator
$terms = [$terms];
}
$isValid = true;
$testConfig = new BpConfig();
foreach ($terms as $term) {
@ -60,6 +61,7 @@ class HostServiceTermValidator extends BaseValidator
if ($this->parent->hasChild($term->getSearchValue())) {
$term->setMessage($this->translate('Already defined in this process'));
$isValid = false;
} else {
$testConfig->getNode('__unbound__')
->addChild($node);
@ -81,10 +83,14 @@ class HostServiceTermValidator extends BaseValidator
} else {
$term->setMessage($this->translate('Host not found'));
}
$isValid = false;
} else {
$term->setLabel($node->getAlias());
$term->setClass($node->getObjectClassName());
}
}
return $isValid;
}
}

View file

@ -19,12 +19,10 @@ class ProcessProblemsBadge extends BadgeNavigationItemRenderer
public function getCount()
{
$count = 0;
if ($this->count === null) {
$storage = LegacyStorage::getInstance();
$count = 0;
$bp = $storage->loadProcess($this->getBpConfigName());
foreach ($bp->getRootNodes() as $rootNode) {
if (! $rootNode->isEmpty() &&
$rootNode->getState() !== $rootNode::ICINGA_PENDING

View file

@ -3,6 +3,8 @@
namespace Icinga\Module\Businessprocess\Web;
use Icinga\Application\Icinga;
use Icinga\Application\Web;
use Icinga\Web\Request;
use Icinga\Web\Url as WebUrl;
/**
@ -14,13 +16,17 @@ use Icinga\Web\Url as WebUrl;
*/
class Url extends WebUrl
{
/**
* @return FakeRequest|Request
*/
protected static function getRequest()
{
$app = Icinga::app();
if ($app->isCli()) {
return new FakeRequest();
} else {
return $app->getRequest();
}
/** @var Web $app */
return $app->getRequest();
}
}

4536
phpstan-baseline.neon Normal file

File diff suppressed because it is too large Load diff

31
phpstan.neon Normal file
View file

@ -0,0 +1,31 @@
includes:
- phpstan-baseline.neon
parameters:
level: max
checkFunctionNameCase: true
checkInternalClassCaseSensitivity: true
treatPhpDocTypesAsCertain: false
paths:
- application
- library
scanDirectories:
- vendor
excludePaths:
- library/Businessprocess/Test
ignoreErrors:
-
messages:
- '#Unsafe usage of new static\(\)#'
- '#. but return statement is missing#'
reportUnmatched: false
universalObjectCratesClasses:
- Icinga\Web\View
- ipl\Orm\Model
- Icinga\Module\Monitoring\Object\MonitoredObject