feat: add PHPStan level 5 with zero errors

- Install phpstan/phpstan, add phpstan.neon at level 5 targeting PHP 8.3
- Add phpstan-bootstrap.php stubs for Laravel helper functions (app,
  config, config_path, app_path, session) unavailable in standalone packages
- Fix CartCondition::setOrder() - removed incorrect @return int (method is void)
- Fix CartCondition::getCalculatedValue() - removed incorrect @return mixed PHPDoc
- Fix Cart::condition() - remove redundant instanceof check the type system already guarantees
- Fix Cart::fireEvent() - remove array_values() on already-list literal array
- Fix ItemCollection - add @property annotations for magic __get properties
- Fix ItemCollection::getAssociatedModel() - correct @return bool to @return mixed
- Fix ItemCollection::getConditions() - remove dead is_array && !instanceof check
- Fix Validator - add @method stub so PHPStan resolves make() through __callStatic
- Add ignoreErrors for trait.unused (HasCart designed for external Livewire use)
- Add ignoreErrors for round() mode mismatch (PHPStan 2.x PHP 8.4 stubs bug)
- Add phpstan step to CI (PHP 8.3 only, matches phpVersion config)
This commit is contained in:
Joel Male
2026-06-06 16:02:16 +10:00
parent 88a118d81b
commit 2ced1038af
9 changed files with 147 additions and 17 deletions
+4
View File
@@ -37,3 +37,7 @@ jobs:
- name: Execute tests - name: Execute tests
run: vendor/bin/pest run: vendor/bin/pest
- name: Static analysis
run: vendor/bin/phpstan analyse --no-progress
if: matrix.php == '8.3'
+2 -1
View File
@@ -30,7 +30,8 @@
"mockery/mockery": "^2.0", "mockery/mockery": "^2.0",
"laravel/pint": "^1.21", "laravel/pint": "^1.21",
"pestphp/pest": "^4.7", "pestphp/pest": "^4.7",
"larapack/dd": "dev-master" "larapack/dd": "dev-master",
"phpstan/phpstan": "2.2.x-dev"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
Generated
+68 -2
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "36458db92bb67aee0a12a15f588349b4", "content-hash": "56b40d3f17cc40f1b62c3f71a3601af7",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@@ -6518,6 +6518,71 @@
}, },
"time": "2026-05-25T15:58:15+00:00" "time": "2026-05-25T15:58:15+00:00"
}, },
{
"name": "phpstan/phpstan",
"version": "2.2.x-dev",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5c0bbadd35255710f7d2402cd6213a0bc34cfa34",
"reference": "5c0bbadd35255710f7d2402cd6213a0bc34cfa34",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"default-branch": true,
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ondřej Mirtes"
},
{
"name": "Markus Staab"
},
{
"name": "Vincent Langlet"
}
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues",
"security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
}
],
"time": "2026-06-05T16:36:10+00:00"
},
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "12.5.x-dev", "version": "12.5.x-dev",
@@ -8189,7 +8254,8 @@
"aliases": [], "aliases": [],
"minimum-stability": "dev", "minimum-stability": "dev",
"stability-flags": { "stability-flags": {
"larapack/dd": 20 "larapack/dd": 20,
"phpstan/phpstan": 20
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
+42
View File
@@ -0,0 +1,42 @@
<?php
/**
* PHPStan bootstrap — stubs for Laravel global helpers that are always
* available at runtime in a Laravel application but are not present in the
* standalone illuminate/* packages this package depends on.
*/
if (! function_exists('app')) {
function app(string $abstract = null, array $parameters = []): mixed
{
return null;
}
}
if (! function_exists('config')) {
function config(string|null $key = null, mixed $default = null): mixed
{
return null;
}
}
if (! function_exists('config_path')) {
function config_path(string $path = ''): string
{
return '';
}
}
if (! function_exists('app_path')) {
function app_path(string $path = ''): string
{
return '';
}
}
if (! function_exists('session')) {
function session(string|array|null $key = null, mixed $default = null): mixed
{
return null;
}
}
+14
View File
@@ -0,0 +1,14 @@
parameters:
level: 5
phpVersion: 80300
paths:
- src
bootstrapFiles:
- phpstan-bootstrap.php
ignoreErrors:
# HasCart is a trait for external use in Livewire components — not used within this package.
- identifier: trait.unused
# PHPStan 2.x bundles PHP 8.4 stubs where PHP_ROUND_HALF_UP/DOWN map to 1/2 instead of 0/1.
# The code is correct at runtime on PHP 8.3. Remove once PHPStan stubs are corrected.
- message: '#Parameter \#3 \$mode of function round expects#'
path: src/Helpers/Helpers.php
+1 -5
View File
@@ -342,10 +342,6 @@ public function condition(array|CartCondition $condition): self
return $this; return $this;
} }
if (! $condition instanceof CartCondition) {
throw new InvalidConditionException('Argument 1 must be an instance of \'Wearepixel\Cart\CartCondition\'');
}
$conditions = $this->getConditions(); $conditions = $this->getConditions();
if ($condition->getOrder() === 0) { if ($condition->getOrder() === 0) {
@@ -735,6 +731,6 @@ protected function fireEvent(string $name, ?string $type = null, mixed $data = n
$eventData[$type] = $data; $eventData[$type] = $data;
} }
return $this->events->dispatch('LaravelCart.' . $name, array_values([$eventData, $this]), true); return $this->events->dispatch('LaravelCart.' . $name, [$eventData, $this], true);
} }
} }
+2 -6
View File
@@ -105,13 +105,11 @@ public function setValue($value): void
} }
/** /**
* Set the order to apply this condition. If no argument order is applied we return 0 as * Set the order to apply this condition.
* indicator that no assignment has been made
* *
* @param int $order * @param int $order
* @return int
*/ */
public function setOrder($order = 1) public function setOrder($order = 1): void
{ {
$this->args['order'] = $order; $this->args['order'] = $order;
} }
@@ -139,8 +137,6 @@ public function applyCondition($totalOrSubTotalOrPrice)
/** /**
* get the calculated value of this condition supplied by the subtotal|price * get the calculated value of this condition supplied by the subtotal|price
*
* @return mixed
*/ */
public function getCalculatedValue($totalOrSubTotalOrPrice = null): int|float public function getCalculatedValue($totalOrSubTotalOrPrice = null): int|float
{ {
+11 -3
View File
@@ -5,6 +5,14 @@
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Wearepixel\Cart\Helpers\Helpers; use Wearepixel\Cart\Helpers\Helpers;
/**
* @property mixed $id
* @property string $name
* @property float|int $price
* @property int|float $quantity
* @property array<string, mixed> $attributes
* @property mixed $conditions
*/
class ItemCollection extends Collection class ItemCollection extends Collection
{ {
/** /**
@@ -46,7 +54,7 @@ public function __get($name)
/** /**
* return the associated model of an item * return the associated model of an item
* *
* @return bool * @return mixed
*/ */
protected function getAssociatedModel() protected function getAssociatedModel()
{ {
@@ -97,7 +105,7 @@ public function getConditions()
// check if we're already an array of CartConditions // check if we're already an array of CartConditions
foreach ($conditions as $key => $condition) { foreach ($conditions as $key => $condition) {
if ($condition instanceof CartCondition) { if (is_object($condition) && $condition instanceof CartCondition) {
return $conditions; return $conditions;
} }
} }
@@ -105,7 +113,7 @@ public function getConditions()
$hasSubArray = false; $hasSubArray = false;
foreach ($conditions as $key => $condition) { foreach ($conditions as $key => $condition) {
if (is_array($condition) && ! $condition instanceof CartCondition) { if (is_array($condition)) {
$hasSubArray = true; $hasSubArray = true;
$conditionsArray[] = new CartCondition($condition); $conditionsArray[] = new CartCondition($condition);
} elseif ($condition instanceof CartCondition) { } elseif ($condition instanceof CartCondition) {
+3
View File
@@ -7,6 +7,9 @@
use Illuminate\Translation\FileLoader; use Illuminate\Translation\FileLoader;
use Illuminate\Translation\Translator; use Illuminate\Translation\Translator;
/**
* @method static \Illuminate\Validation\Validator make(array $data, array $rules, array $messages = [], array $attributes = [])
*/
abstract class Validator abstract class Validator
{ {
protected static $factory; protected static $factory;