Add phpstan strict rules (#233)

* Add phpstan strict rules

* Fix travis coveralls

* Add phpstan-phpunit strict rules

* Fix eigen decomposition test name and phpstan ingored error
This commit is contained in:
Arkadiusz Kondas 2018-02-16 07:25:24 +01:00 committed by GitHub
parent 6ac61a860c
commit 16dc16b0d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 164 additions and 71 deletions

View File

@ -41,5 +41,5 @@ after_success:
- |
if [[ $PHPUNIT_FLAGS != "" ]]; then
wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.0.0/php-coveralls.phar
php coveralls.phar --verbose;
php php-coveralls.phar --verbose;
fi

View File

@ -23,7 +23,9 @@
"php": "^7.1"
},
"require-dev": {
"phpstan/phpstan-phpunit": "^0.9.4",
"phpstan/phpstan-shim": "^0.9",
"phpstan/phpstan-strict-rules": "^0.9.0",
"phpunit/phpunit": "^7.0.0",
"symplify/coding-standard": "^3.1",
"symplify/easy-coding-standard": "^3.1"

91
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "3e327a50a76dd6df905cef56cbc37e02",
"content-hash": "9135b3dece8c6938922f757123274e95",
"packages": [],
"packages-dev": [
{
@ -1287,6 +1287,51 @@
],
"time": "2017-11-24T13:59:53+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "0.9.4",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "852411f841a37aeca2fa5af0002b0272c485c9bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/852411f841a37aeca2fa5af0002b0272c485c9bf",
"reference": "852411f841a37aeca2fa5af0002b0272c485c9bf",
"shasum": ""
},
"require": {
"php": "~7.0",
"phpstan/phpstan": "^0.9.1",
"phpunit/phpunit": "^6.3 || ~7.0"
},
"require-dev": {
"consistence/coding-standard": "^2.0",
"jakub-onderka/php-parallel-lint": "^0.9.2",
"phing/phing": "^2.16.0",
"phpstan/phpstan-strict-rules": "^0.9",
"satooshi/php-coveralls": "^1.0",
"slevomat/coding-standard": "^3.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.9-dev"
}
},
"autoload": {
"psr-4": {
"PHPStan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPUnit extensions and rules for PHPStan",
"time": "2018-02-02T09:45:47+00:00"
},
{
"name": "phpstan/phpstan-shim",
"version": "0.9.1",
@ -1324,6 +1369,50 @@
"description": "PHPStan Phar distribution",
"time": "2017-12-02T20:14:45+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "0.9",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "15be9090622c6b85c079922308f831018d8d9e23"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/15be9090622c6b85c079922308f831018d8d9e23",
"reference": "15be9090622c6b85c079922308f831018d8d9e23",
"shasum": ""
},
"require": {
"php": "~7.0",
"phpstan/phpstan": "^0.9"
},
"require-dev": {
"consistence/coding-standard": "^2.0.0",
"jakub-onderka/php-parallel-lint": "^0.9.2",
"phing/phing": "^2.16.0",
"phpstan/phpstan-phpunit": "^0.9",
"phpunit/phpunit": "^6.4",
"slevomat/coding-standard": "^3.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.9-dev"
}
},
"autoload": {
"psr-4": {
"PHPStan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Extra strict and opinionated rules for PHPStan",
"time": "2017-11-26T20:12:30+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "6.0.1",

View File

@ -1,15 +1,17 @@
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-phpunit/strictRules.neon
parameters:
ignoreErrors:
- '#Phpml\\Dataset\\FilesDataset::__construct\(\) does not call parent constructor from Phpml\\Dataset\\ArrayDataset#'
# mocks
- '#PHPUnit_Framework_MockObject_MockObject#'
# wide range cases
- '#Call to function count\(\) with argument type array<int>\|Phpml\\Clustering\\KMeans\\Point will always result in number 1#'
- '#Parameter \#1 \$coordinates of class Phpml\\Clustering\\KMeans\\Point constructor expects array, array<int>\|Phpml\\Clustering\\KMeans\\Point given#'
# probably known value
- '#Variable \$j might not be defined#'
- '#Method Phpml\\Classification\\DecisionTree::getBestSplit\(\) should return Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf but returns Phpml\\Classification\\DecisionTree\\DecisionTreeLeaf\|null#'
- '#Call to an undefined method Phpml\\Helper\\Optimizer\\Optimizer::getCostValues\(\)#'

View File

@ -63,11 +63,11 @@ class Apriori implements Associator
*/
public function getRules(): array
{
if (!$this->large) {
if (empty($this->large)) {
$this->large = $this->apriori();
}
if ($this->rules) {
if (!empty($this->rules)) {
return $this->rules;
}

View File

@ -273,15 +273,15 @@ class DecisionTree implements Classifier
}
if ($allSame || $depth >= $this->maxDepth || count($remainingTargets) === 1) {
$split->isTerminal = 1;
$split->isTerminal = true;
arsort($remainingTargets);
$split->classValue = key($remainingTargets);
} else {
if ($leftRecords) {
if (!empty($leftRecords)) {
$split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1);
}
if ($rightRecords) {
if (!empty($rightRecords)) {
$split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1);
}
}
@ -312,13 +312,13 @@ class DecisionTree implements Classifier
$split->value = $baseValue;
$split->giniIndex = $gini;
$split->columnIndex = $i;
$split->isContinuous = $this->columnTypes[$i] == self::CONTINUOUS;
$split->isContinuous = $this->columnTypes[$i] === self::CONTINUOUS;
$split->records = $records;
// If a numeric column is to be selected, then
// the original numeric value and the selected operator
// will also be saved into the leaf for future access
if ($this->columnTypes[$i] == self::CONTINUOUS) {
if ($this->columnTypes[$i] === self::CONTINUOUS) {
$matches = [];
preg_match("/^([<>=]{1,2})\s*(.*)/", (string) $split->value, $matches);
$split->operator = $matches[1];
@ -349,11 +349,11 @@ class DecisionTree implements Classifier
protected function getSelectedFeatures(): array
{
$allFeatures = range(0, $this->featureCount - 1);
if ($this->numUsableFeatures === 0 && !$this->selectedFeatures) {
if ($this->numUsableFeatures === 0 && empty($this->selectedFeatures)) {
return $allFeatures;
}
if ($this->selectedFeatures) {
if (!empty($this->selectedFeatures)) {
return $this->selectedFeatures;
}
@ -363,7 +363,7 @@ class DecisionTree implements Classifier
}
shuffle($allFeatures);
$selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false);
$selectedFeatures = array_slice($allFeatures, 0, $numFeatures);
sort($selectedFeatures);
return $selectedFeatures;
@ -406,7 +406,7 @@ class DecisionTree implements Classifier
// all values in that column (Lower than or equal to %20 of all values)
$numericValues = array_filter($columnValues, 'is_numeric');
$floatValues = array_filter($columnValues, 'is_float');
if ($floatValues) {
if (!empty($floatValues)) {
return false;
}
@ -433,7 +433,7 @@ class DecisionTree implements Classifier
*/
protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): array
{
if (!$node || $node->isTerminal) {
if ($node->isTerminal) {
return [];
}
@ -444,11 +444,11 @@ class DecisionTree implements Classifier
$lNodes = [];
$rNodes = [];
if ($node->leftLeaf) {
if ($node->leftLeaf !== null) {
$lNodes = $this->getSplitNodesByColumn($column, $node->leftLeaf);
}
if ($node->rightLeaf) {
if ($node->rightLeaf !== null) {
$rNodes = $this->getSplitNodesByColumn($column, $node->rightLeaf);
}
@ -475,6 +475,6 @@ class DecisionTree implements Classifier
}
} while ($node);
return $node ? $node->classValue : $this->labels[0];
return $node !== null ? $node->classValue : $this->labels[0];
}
}

View File

@ -29,14 +29,14 @@ class DecisionTreeLeaf
public $columnIndex;
/**
* @var DecisionTreeLeaf
* @var ?DecisionTreeLeaf
*/
public $leftLeaf = null;
public $leftLeaf;
/**
* @var DecisionTreeLeaf
* @var ?DecisionTreeLeaf
*/
public $rightLeaf = null;
public $rightLeaf;
/**
* @var array
@ -52,7 +52,7 @@ class DecisionTreeLeaf
public $classValue = '';
/**
* @var bool|int
* @var bool
*/
public $isTerminal = false;
@ -103,12 +103,12 @@ class DecisionTreeLeaf
$nodeSampleCount = (float) count($this->records);
$iT = $this->giniIndex;
if ($this->leftLeaf) {
if ($this->leftLeaf !== null) {
$pL = count($this->leftLeaf->records) / $nodeSampleCount;
$iT -= $pL * $this->leftLeaf->giniIndex;
}
if ($this->rightLeaf) {
if ($this->rightLeaf !== null) {
$pR = count($this->rightLeaf->records) / $nodeSampleCount;
$iT -= $pR * $this->rightLeaf->giniIndex;
}
@ -140,16 +140,16 @@ class DecisionTreeLeaf
$str = "<table ><tr><td colspan=3 align=center style='border:1px solid;'>${value}</td></tr>";
if ($this->leftLeaf || $this->rightLeaf) {
if ($this->leftLeaf !== null || $this->rightLeaf !== null) {
$str .= '<tr>';
if ($this->leftLeaf) {
if ($this->leftLeaf !== null) {
$str .= '<td valign=top><b>| Yes</b><br>'.$this->leftLeaf->getHTML($columnNames).'</td>';
} else {
$str .= '<td></td>';
}
$str .= '<td>&nbsp;</td>';
if ($this->rightLeaf) {
if ($this->rightLeaf !== null) {
$str .= '<td valign=top align=right><b>No |</b><br>'.$this->rightLeaf->getHTML($columnNames).'</td>';
} else {
$str .= '<td></td>';

View File

@ -158,7 +158,7 @@ class AdaBoost implements Classifier
protected function getBestClassifier(): Classifier
{
$ref = new ReflectionClass($this->baseClassifier);
if ($this->classifierOptions) {
if (!empty($this->classifierOptions)) {
$classifier = $ref->newInstanceArgs($this->classifierOptions);
} else {
$classifier = $ref->newInstance();

View File

@ -145,7 +145,7 @@ class Bagging implements Classifier
$classifiers = [];
for ($i = 0; $i < $this->numClassifier; ++$i) {
$ref = new ReflectionClass($this->classifier);
if ($this->classifierOptions) {
if (!empty($this->classifierOptions)) {
$obj = $ref->newInstanceArgs($this->classifierOptions);
} else {
$obj = $ref->newInstance();

View File

@ -42,7 +42,7 @@ class Adaline extends Perceptron
bool $normalizeInputs = true,
int $trainingType = self::BATCH_TRAINING
) {
if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) {
if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING], true)) {
throw new Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm');
}

View File

@ -118,7 +118,7 @@ class DecisionStump extends WeightedClassifier
// Check the size of the weights given.
// If none given, then assign 1 as a weight to each sample
if ($this->weights) {
if (!empty($this->weights)) {
$numWeights = count($this->weights);
if ($numWeights != count($samples)) {
throw new Exception('Number of sample weights does not match with number of samples');

View File

@ -71,13 +71,13 @@ class LogisticRegression extends Adaline
string $penalty = 'L2'
) {
$trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING);
if (!in_array($trainingType, $trainingTypes)) {
if (!in_array($trainingType, $trainingTypes, true)) {
throw new Exception('Logistic regression can only be trained with '.
'batch (gradient descent), online (stochastic gradient descent) '.
'or conjugate batch (conjugate gradients) algorithms');
}
if (!in_array($cost, ['log', 'sse'])) {
if (!in_array($cost, ['log', 'sse'], true)) {
throw new Exception("Logistic regression cost function can be one of the following: \n".
"'log' for log-likelihood and 'sse' for sum of squared errors");
}

View File

@ -97,7 +97,7 @@ class Perceptron implements Classifier, IncrementalEstimator
public function trainBinary(array $samples, array $targets, array $labels): void
{
if ($this->normalizer) {
if ($this->normalizer !== null) {
$this->normalizer->transform($samples);
}
@ -196,7 +196,7 @@ class Perceptron implements Classifier, IncrementalEstimator
*/
protected function checkNormalizedSample(array $sample): array
{
if ($this->normalizer) {
if ($this->normalizer !== null) {
$samples = [$sample];
$this->normalizer->transform($samples);
$sample = $samples[0];

View File

@ -16,11 +16,11 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier
*/
public function getTargetClass($target): int
{
if (!in_array($target, $this->classes)) {
if (!in_array($target, $this->classes, true)) {
throw InvalidArgumentException::invalidTarget($target);
}
return array_search($target, $this->classes);
return array_search($target, $this->classes, true);
}
/**

View File

@ -20,7 +20,7 @@ class FuzzyCMeans implements Clusterer
/**
* @var array|Cluster[]
*/
private $clusters = null;
private $clusters = [];
/**
* @var Space
@ -152,8 +152,7 @@ class FuzzyCMeans implements Clusterer
protected function updateClusters(): void
{
$dim = $this->space->getDimension();
if (!$this->clusters) {
$this->clusters = [];
if (empty($this->clusters)) {
for ($i = 0; $i < $this->clustersNumber; ++$i) {
$this->clusters[] = new Cluster($this->space, array_fill(0, $dim, 0.0));
}

View File

@ -76,8 +76,7 @@ class Cluster extends Point implements IteratorAggregate, Countable
public function updateCentroid(): void
{
$count = count($this->points);
if (!$count) {
if (empty($this->points)) {
return;
}
@ -89,6 +88,7 @@ class Cluster extends Point implements IteratorAggregate, Countable
}
}
$count = count($this->points);
for ($n = 0; $n < $this->dimension; ++$n) {
$this->coordinates[$n] = $centroid->coordinates[$n] / $count;
}

View File

@ -16,7 +16,7 @@ class Space extends SplObjectStorage
*/
protected $dimension;
public function __construct($dimension)
public function __construct(int $dimension)
{
if ($dimension < 1) {
throw new LogicException('a space dimension cannot be null or negative');
@ -75,7 +75,7 @@ class Space extends SplObjectStorage
*/
public function getBoundaries()
{
if (!count($this)) {
if (empty($this)) {
return false;
}
@ -153,8 +153,8 @@ class Space extends SplObjectStorage
$closest = $point->getClosest($clusters);
if ($closest !== $cluster) {
isset($attach[$closest]) || $attach[$closest] = new SplObjectStorage();
isset($detach[$cluster]) || $detach[$cluster] = new SplObjectStorage();
$attach[$closest] ?? $attach[$closest] = new SplObjectStorage();
$detach[$cluster] ?? $detach[$cluster] = new SplObjectStorage();
$attach[$closest]->attach($point);
$detach[$cluster]->attach($point);

View File

@ -58,7 +58,7 @@ class KernelPCA extends PCA
public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVariance = null, ?int $numFeatures = null, ?float $gamma = null)
{
$availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR];
if (!in_array($kernel, $availableKernels)) {
if (!in_array($kernel, $availableKernels, true)) {
throw new Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian');
}

View File

@ -130,7 +130,7 @@ class LDA extends EigenTransformerBase
$overallMean = array_fill(0, count($data[0]), 0.0);
foreach ($data as $index => $row) {
$label = array_search($classes[$index], $this->labels);
$label = array_search($classes[$index], $this->labels, true);
foreach ($row as $col => $val) {
if (!isset($means[$label][$col])) {
@ -177,7 +177,7 @@ class LDA extends EigenTransformerBase
$sW = new Matrix($s, false);
foreach ($data as $index => $row) {
$label = array_search($classes[$index], $this->labels);
$label = array_search($classes[$index], $this->labels, true);
$means = $this->means[$label];
$row = $this->calculateVar($row, $means);

View File

@ -13,9 +13,9 @@ class TfIdfTransformer implements Transformer
*/
private $idf = [];
public function __construct(?array $samples = null)
public function __construct(array $samples = [])
{
if ($samples) {
if (!empty($samples)) {
$this->fit($samples);
}
}

View File

@ -123,7 +123,7 @@ class TokenCountVectorizer implements Transformer
private function isStopWord(string $token): bool
{
return $this->stopWords && $this->stopWords->isStopWord($token);
return $this->stopWords !== null && $this->stopWords->isStopWord($token);
}
private function updateFrequency(string $token): void

View File

@ -274,6 +274,7 @@ class EigenvalueDecomposition
}
// Accumulate transformations.
$j = 0;
for ($i = 0; $i < $this->n - 1; ++$i) {
$this->V[$this->n - 1][$i] = $this->V[$i][$i];
$this->V[$i][$i] = 1.0;

View File

@ -105,7 +105,7 @@ class Matrix
*/
public function getDeterminant()
{
if ($this->determinant) {
if ($this->determinant !== null) {
return $this->determinant;
}

View File

@ -50,7 +50,7 @@ class Mean
$values = array_count_values($numbers);
return array_search(max($values), $values);
return array_search(max($values), $values, true);
}
/**

View File

@ -57,7 +57,7 @@ class ClassificationReport
public function __construct(array $actualLabels, array $predictedLabels, int $average = self::MACRO_AVERAGE)
{
$averagingMethods = range(self::MICRO_AVERAGE, self::WEIGHTED_AVERAGE);
if (!in_array($average, $averagingMethods)) {
if (!in_array($average, $averagingMethods, true)) {
throw new InvalidArgumentException('Averaging method must be MICRO_AVERAGE, MACRO_AVERAGE or WEIGHTED_AVERAGE');
}

View File

@ -6,9 +6,9 @@ namespace Phpml\Metric;
class ConfusionMatrix
{
public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null): array
public static function compute(array $actualLabels, array $predictedLabels, array $labels = []): array
{
$labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels);
$labels = !empty($labels) ? array_flip($labels) : self::getUniqueLabels($actualLabels);
$matrix = self::generateMatrixWithZeros($labels);
foreach ($actualLabels as $index => $actual) {

View File

@ -19,7 +19,7 @@ class Layer
*/
public function __construct(int $nodesNumber = 0, string $nodeClass = Neuron::class, ?ActivationFunction $activationFunction = null)
{
if (!in_array(Node::class, class_implements($nodeClass))) {
if (!in_array(Node::class, class_implements($nodeClass), true)) {
throw InvalidArgumentException::invalidLayerNodeClass();
}

View File

@ -41,7 +41,7 @@ class Normalizer implements Preprocessor
*/
public function __construct(int $norm = self::NORM_L2)
{
if (!in_array($norm, [self::NORM_L1, self::NORM_L2, self::NORM_STD])) {
if (!in_array($norm, [self::NORM_L1, self::NORM_L2, self::NORM_STD], true)) {
throw NormalizerException::unknownNorm();
}

View File

@ -42,7 +42,7 @@ class DataTransformer
$results = [];
foreach (explode(PHP_EOL, $rawPredictions) as $result) {
if (isset($result[0])) {
$results[] = array_search($result, $numericLabels);
$results[] = array_search((int) $result, $numericLabels, true);
}
}
@ -61,7 +61,7 @@ class DataTransformer
$columnLabels = [];
foreach ($headerColumns as $numericLabel) {
$columnLabels[] = array_search($numericLabel, $numericLabels);
$columnLabels[] = array_search((int) $numericLabel, $numericLabels, true);
}
$results = [];

View File

@ -149,7 +149,7 @@ class SupportVectorMachine
$this->samples = array_merge($this->samples, $samples);
$this->targets = array_merge($this->targets, $targets);
$trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR]));
$trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR], true));
file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet);
$modelFileName = $trainingSetFileName.'-model';
@ -182,7 +182,7 @@ class SupportVectorMachine
{
$predictions = $this->runSvmPredict($samples, false);
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC], true)) {
$predictions = DataTransformer::predictions($predictions, $this->targets);
} else {
$predictions = explode(PHP_EOL, trim($predictions));
@ -208,7 +208,7 @@ class SupportVectorMachine
$predictions = $this->runSvmPredict($samples, true);
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC], true)) {
$predictions = DataTransformer::probabilities($predictions, $this->targets);
} else {
$predictions = explode(PHP_EOL, trim($predictions));

View File

@ -16,7 +16,7 @@ class FuzzyCMeansTest extends TestCase
$clusters = $fcm->cluster($samples);
$this->assertCount(2, $clusters);
foreach ($samples as $index => $sample) {
if (in_array($sample, $clusters[0]) || in_array($sample, $clusters[1])) {
if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) {
unset($samples[$index]);
}
}

View File

@ -20,7 +20,7 @@ class KMeansTest extends TestCase
$this->assertCount(2, $clusters);
foreach ($samples as $index => $sample) {
if (in_array($sample, $clusters[0]) || in_array($sample, $clusters[1])) {
if (in_array($sample, $clusters[0], true) || in_array($sample, $clusters[1], true)) {
unset($samples[$index]);
}
}

View File

@ -8,7 +8,7 @@ use Phpml\Math\LinearAlgebra\EigenvalueDecomposition;
use Phpml\Math\Matrix;
use PHPUnit\Framework\TestCase;
class EigenDecompositionTest extends TestCase
class EigenvalueDecompositionTest extends TestCase
{
public function testSymmetricMatrixEigenPairs(): void
{