Skip to content

Commit 5868f89

Browse files
authored
Merge pull request #4339 from quonly/patch-1
changed $trenType to $trendMethod in case TREND_BEST_FIT
2 parents 20aac08 + 8f3bb96 commit 5868f89

File tree

5 files changed

+108
-94
lines changed

5 files changed

+108
-94
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
2020

2121
- Phpstan Version 2. [PR #4384](https://github.com/PHPOffice/PhpSpreadsheet/pull/4384)
2222
- Start migration to Phpstan level 9. [PR #4396](https://github.com/PHPOffice/PhpSpreadsheet/pull/4396)
23+
- TREND_POLYNOMIAL_* and TREND_BEST_FIT do not work, and are changed to throw Exceptions if attempted. (TREND_BEST_FIT_NO_POLY works.) An attempt to use an unknown trend type will now also throw an exception. [Issue #4400](https://github.com/PHPOffice/PhpSpreadsheet/issues/4400) [PR #4339](https://github.com/PHPOffice/PhpSpreadsheet/pull/4339)
2324

2425
### Moved
2526

@@ -32,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3233
### Fixed
3334

3435
- BIN2DEC, OCT2DEC, and HEX2DEC return numbers rather than strings. [Issue #4383](https://github.com/PHPOffice/PhpSpreadsheet/issues/4383) [PR #4389](https://github.com/PHPOffice/PhpSpreadsheet/pull/4389)
36+
- Fix TREND_BEST_FIT_NO_POLY. [Issue #4400](https://github.com/PHPOffice/PhpSpreadsheet/issues/4400) [PR #4339](https://github.com/PHPOffice/PhpSpreadsheet/pull/4339)
3537

3638
## 2025-03-02 - 4.1.0
3739

phpstan-baseline.neon

-84
Original file line numberDiff line numberDiff line change
@@ -1434,90 +1434,6 @@ parameters:
14341434
count: 1
14351435
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
14361436

1437-
-
1438-
message: '#^Cannot call method getCorrelation\(\) on mixed\.$#'
1439-
identifier: method.nonObject
1440-
count: 1
1441-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1442-
1443-
-
1444-
message: '#^Cannot call method getCovariance\(\) on mixed\.$#'
1445-
identifier: method.nonObject
1446-
count: 1
1447-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1448-
1449-
-
1450-
message: '#^Cannot call method getDFResiduals\(\) on mixed\.$#'
1451-
identifier: method.nonObject
1452-
count: 2
1453-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1454-
1455-
-
1456-
message: '#^Cannot call method getF\(\) on mixed\.$#'
1457-
identifier: method.nonObject
1458-
count: 2
1459-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1460-
1461-
-
1462-
message: '#^Cannot call method getGoodnessOfFit\(\) on mixed\.$#'
1463-
identifier: method.nonObject
1464-
count: 3
1465-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1466-
1467-
-
1468-
message: '#^Cannot call method getIntersect\(\) on mixed\.$#'
1469-
identifier: method.nonObject
1470-
count: 5
1471-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1472-
1473-
-
1474-
message: '#^Cannot call method getIntersectSE\(\) on mixed\.$#'
1475-
identifier: method.nonObject
1476-
count: 2
1477-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1478-
1479-
-
1480-
message: '#^Cannot call method getSSRegression\(\) on mixed\.$#'
1481-
identifier: method.nonObject
1482-
count: 2
1483-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1484-
1485-
-
1486-
message: '#^Cannot call method getSSResiduals\(\) on mixed\.$#'
1487-
identifier: method.nonObject
1488-
count: 2
1489-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1490-
1491-
-
1492-
message: '#^Cannot call method getSlope\(\) on mixed\.$#'
1493-
identifier: method.nonObject
1494-
count: 5
1495-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1496-
1497-
-
1498-
message: '#^Cannot call method getSlopeSE\(\) on mixed\.$#'
1499-
identifier: method.nonObject
1500-
count: 2
1501-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1502-
1503-
-
1504-
message: '#^Cannot call method getStdevOfResiduals\(\) on mixed\.$#'
1505-
identifier: method.nonObject
1506-
count: 3
1507-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1508-
1509-
-
1510-
message: '#^Cannot call method getValueOfYForX\(\) on mixed\.$#'
1511-
identifier: method.nonObject
1512-
count: 3
1513-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1514-
1515-
-
1516-
message: '#^Cannot call method getXValues\(\) on mixed\.$#'
1517-
identifier: method.nonObject
1518-
count: 2
1519-
path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php
1520-
15211437
-
15221438
message: '#^Parameter \#1 \$yValues of static method PhpOffice\\PhpSpreadsheet\\Calculation\\Statistical\\Trends\:\:validateTrendArrays\(\) expects array, mixed given\.$#'
15231439
identifier: argument.type

src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php

+9
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
44

55
use Matrix\Matrix;
6+
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
67

78
// Phpstan and Scrutinizer seem to have legitimate complaints.
89
// $this->slope is specified where an array is expected in several places.
910
// But it seems that it should always be float.
1011
// This code is probably not exercised at all in unit tests.
12+
// Private bool property $implemented is set to indicate
13+
// whether this implementation is correct.
1114
class PolynomialBestFit extends BestFit
1215
{
1316
/**
@@ -21,6 +24,8 @@ class PolynomialBestFit extends BestFit
2124
*/
2225
protected int $order = 0;
2326

27+
private bool $implemented = false;
28+
2429
/**
2530
* Return the order of this polynomial.
2631
*/
@@ -187,6 +192,10 @@ private function polynomialRegression(int $order, array $yValues, array $xValues
187192
*/
188193
public function __construct(int $order, array $yValues, array $xValues = [])
189194
{
195+
if (!$this->implemented) {
196+
throw new SpreadsheetException('Polynomial Best Fit not yet implemented');
197+
}
198+
190199
parent::__construct($yValues, $xValues);
191200

192201
if (!$this->error) {

src/PhpSpreadsheet/Shared/Trend/Trend.php

+9-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
44

5+
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
6+
57
class Trend
68
{
79
const TREND_LINEAR = 'Linear';
@@ -18,10 +20,8 @@ class Trend
1820

1921
/**
2022
* Names of the best-fit Trend analysis methods.
21-
*
22-
* @var string[]
2323
*/
24-
private static array $trendTypes = [
24+
private const TREND_TYPES = [
2525
self::TREND_LINEAR,
2626
self::TREND_LOGARITHMIC,
2727
self::TREND_EXPONENTIAL,
@@ -48,7 +48,7 @@ class Trend
4848
*/
4949
private static array $trendCache = [];
5050

51-
public static function calculate(string $trendType = self::TREND_BEST_FIT, array $yValues = [], array $xValues = [], bool $const = true): mixed
51+
public static function calculate(string $trendType = self::TREND_BEST_FIT, array $yValues = [], array $xValues = [], bool $const = true): BestFit
5252
{
5353
// Calculate number of points in each dataset
5454
$nY = count($yValues);
@@ -59,7 +59,7 @@ public static function calculate(string $trendType = self::TREND_BEST_FIT, array
5959
$xValues = range(1, $nY);
6060
} elseif ($nY !== $nX) {
6161
// Ensure both arrays of points are the same size
62-
trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR);
62+
throw new SpreadsheetException('Trend(): Number of elements in coordinate arrays do not match.');
6363
}
6464

6565
$key = md5($trendType . $const . serialize($yValues) . serialize($xValues));
@@ -93,13 +93,12 @@ public static function calculate(string $trendType = self::TREND_BEST_FIT, array
9393
// Start by generating an instance of each available Trend method
9494
$bestFit = [];
9595
$bestFitValue = [];
96-
foreach (self::$trendTypes as $trendMethod) {
97-
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
98-
//* @phpstan-ignore-next-line
96+
foreach (self::TREND_TYPES as $trendMethod) {
97+
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendMethod . 'BestFit';
9998
$bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
10099
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
101100
}
102-
if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
101+
if ($trendType !== self::TREND_BEST_FIT_NO_POLY) {
103102
foreach (self::$trendTypePolynomialOrders as $trendMethod) {
104103
$order = (int) substr($trendMethod, -1);
105104
$bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues);
@@ -116,7 +115,7 @@ public static function calculate(string $trendType = self::TREND_BEST_FIT, array
116115

117116
return $bestFit[$bestFitType];
118117
default:
119-
return false;
118+
throw new SpreadsheetException("Unknown trend type $trendType");
120119
}
121120
}
122121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Shared\Trend;
6+
7+
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
8+
use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class BestFitTest extends TestCase
12+
{
13+
private const LBF_PRECISION = 1.0E-4;
14+
15+
public function testBestFit(): void
16+
{
17+
$xValues = [45, 55, 47, 75, 90, 100, 100, 95, 88, 50, 45, 58];
18+
$yValues = [15, 25, 17, 30, 41, 47, 50, 46, 37, 22, 20, 26];
19+
$maxGoodness = -1000.0;
20+
$maxType = '';
21+
22+
$type = Trend::TREND_LINEAR;
23+
$result = Trend::calculate($type, $yValues, $xValues);
24+
$goodness = $result->getGoodnessOfFit();
25+
if ($maxGoodness < $goodness) {
26+
$maxGoodness = $goodness;
27+
$maxType = $type;
28+
}
29+
self::assertEqualsWithDelta(0.9628, $goodness, self::LBF_PRECISION);
30+
31+
$type = Trend::TREND_EXPONENTIAL;
32+
$result = Trend::calculate($type, $yValues, $xValues);
33+
$goodness = $result->getGoodnessOfFit();
34+
if ($maxGoodness < $goodness) {
35+
$maxGoodness = $goodness;
36+
$maxType = $type;
37+
}
38+
self::assertEqualsWithDelta(0.9952, $goodness, self::LBF_PRECISION);
39+
40+
$type = Trend::TREND_LOGARITHMIC;
41+
$result = Trend::calculate($type, $yValues, $xValues);
42+
$goodness = $result->getGoodnessOfFit();
43+
if ($maxGoodness < $goodness) {
44+
$maxGoodness = $goodness;
45+
$maxType = $type;
46+
}
47+
self::assertEqualsWithDelta(-0.0724, $goodness, self::LBF_PRECISION);
48+
49+
$type = Trend::TREND_POWER;
50+
$result = Trend::calculate($type, $yValues, $xValues);
51+
$goodness = $result->getGoodnessOfFit();
52+
if ($maxGoodness < $goodness) {
53+
$maxGoodness = $goodness;
54+
$maxType = $type;
55+
}
56+
self::assertEqualsWithDelta(0.9946, $goodness, self::LBF_PRECISION);
57+
58+
$type = Trend::TREND_BEST_FIT_NO_POLY;
59+
$result = Trend::calculate($type, $yValues, $xValues);
60+
$goodness = $result->getGoodnessOfFit();
61+
self::assertSame($maxGoodness, $goodness);
62+
self::assertSame(lcfirst($maxType), $result->getBestFitType());
63+
64+
try {
65+
$type = Trend::TREND_BEST_FIT;
66+
Trend::calculate($type, $yValues, [0, 1, 2]);
67+
self::fail('should have failed - mismatched number of elements');
68+
} catch (SpreadsheetException $e) {
69+
self::assertStringContainsString('Number of elements', $e->getMessage());
70+
}
71+
72+
try {
73+
$type = Trend::TREND_BEST_FIT;
74+
Trend::calculate($type, $yValues, $xValues);
75+
self::fail('should have failed - TREND_BEST_FIT includes polynomials which are not implemented yet');
76+
} catch (SpreadsheetException $e) {
77+
self::assertStringContainsString('not yet implemented', $e->getMessage());
78+
}
79+
80+
try {
81+
$type = 'unknown';
82+
Trend::calculate($type, $yValues, $xValues);
83+
self::fail('should have failed - invalid trend type');
84+
} catch (SpreadsheetException $e) {
85+
self::assertStringContainsString('Unknown trend type', $e->getMessage());
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)