Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduction of phpstan #57

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff304a2
switch from psalm to phpstan.
Chrico Jan 14, 2025
da38ad5
switch from syde/phpcs to inpsyde/php-coding-standards to support PHP…
Chrico Jan 14, 2025
d638ed2
remove psalm.xml.
Chrico Jan 14, 2025
faed10a
Package // revert changes of da60d75 which introduced array_values() …
Chrico Jan 17, 2025
7687fce
composer.json // add "swissspidy/phpstan-no-private" and "phpstan/php…
Chrico Jan 17, 2025
cb1c7f9
Update .github/workflows/php-qa.yml
Chrico Jan 24, 2025
1d3608f
LibraryProperties // revert changes done for new syde/phpcs coding st…
Chrico Jan 24, 2025
77567a7
Merge remote-tracking branch 'origin/feature/phpstan' into feature/ph…
Chrico Jan 24, 2025
fe7709e
php-qa.yml // replace paths: '**psalm.xml' with '**phpstan.neon.xml'.
Chrico Jan 24, 2025
0995b5e
composer.json // update version constraint for phpstan and fixed "scr…
Chrico Jan 24, 2025
25d32c2
replaced psalm-assert-if-true, psalm-assert-if-false, psalm-import-ty…
Chrico Jan 24, 2025
f5153c1
tests // add PHPStan to "tests"-folder.
Chrico Jan 24, 2025
3e52f99
PackageTest // remove nullable return and added typecast to ensure a …
Chrico Jan 24, 2025
f005470
phpcs
Chrico Jan 24, 2025
641b840
ServiceExtensions // enable phpcs rules again.
Chrico Jan 28, 2025
9fec908
Update .github/workflows/php-unit-tests.yml
Chrico Jan 28, 2025
6eecb46
Update tests/unit/Container/ReadOnlyContainerTest.php
Chrico Jan 28, 2025
81f58ff
Update tests/unit/PackageTest.php
Chrico Jan 28, 2025
d5064db
address PHPStan issues
tfrommen Jan 28, 2025
13d9411
include PHP 8.4 in CI
tfrommen Jan 28, 2025
aa4c629
only lint on PHP 8.4, for now
tfrommen Jan 28, 2025
5ceef1a
refactor
tfrommen Jan 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/php-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- '**workflows/php-qa.yml'
- '**.php'
- '**phpcs.xml.dist'
- '**psalm.xml'
- '**phpstan.neon.dist'
- '**composer.json'
workflow_dispatch:
inputs:
Expand All @@ -25,7 +25,7 @@ on:
options:
- 'Run all'
- 'Run PHPCS only'
- 'Run Psalm only'
- 'Run PHPStan only'
- 'Run lint only'

concurrency:
Expand All @@ -50,11 +50,10 @@ jobs:
PHP_VERSION: '8.3'

static-code-analysis:
if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run Psalm only')) }}
if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run PHPStan only')) }}
uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-php.yml@main
strategy:
matrix:
php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ]
with:
PHP_VERSION: ${{ matrix.php }}
PSALM_ARGS: --output-format=github --no-suggestions --no-cache --no-diff --find-unused-psalm-suppress
16 changes: 9 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
},
"require-dev": {
"brain/monkey": "^2.6.1",
"inpsyde/php-coding-standards": "^2@dev",
"inpsyde/wp-stubs-versions": "dev-latest",
"inpsyde/php-coding-standards": "^2.0.0",
"roots/wordpress-no-content": "@dev",
"mikey179/vfsstream": "^v1.6.11",
"phpunit/phpunit": "^9.6.19",
"vimeo/psalm": "^5.24.0"
"phpstan/phpstan": "^2.1.1",
"szepeviktor/phpstan-wordpress": "^2",
Copy link
Contributor

@gmazzap gmazzap Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this thing. This is designed to make PHPStan pass more easily, not to make the code safer. Take this example:

function filterTitle(string $title): string
{
    /**
     * @param string $title Page title.
     */
    return apply_filters('list_pages', $title);
} 

Using that plugin, this passes as-is. Nice! Nice?

What if you have code that does:

add_filter('list_pages', '__return_zero');

Now, the filterTitle() function will return an integer, with string declared as the return type, resulting in a fatal error that the static analysis did not catch.

One might say that the culprit is the add_filter that returned an integer where a string was expected. And that is true but was static analysis able to tell whoever wrote that add_filter they were doing something wrong? No, not at all.

In short, the behavior above is clearly a logic mistake that a static analysis could catch, but using that plugin, it is hiding from us.

Without using that plugin, PHPStan would report an error on filterTitle, telling that it returns mixed where a string is expected. That might be annoying but it is the absolute truth. apply_filters makes our function return mixed, and if we use a plugin to "mock" it returns a string, then we are not working to make our code better, we are just making the tool happy hiding a whole set of errors.

The goal is not to have the CI green; the goal is to catch possible errors. Otherwise, for our unit tests, we could use a tool like https://github.com/hugues-m/phpunit-vw to ensure we never have failing tests, right?

If you don't have that plugin, you have two possibilities. Either you do something like this:

function filterTitle(string $title): string
{
    $filtered = apply_filters('list_pages', $title);

    return is_string($filtered) ? $filtered : $title;
} 

Or you do:

function filterTitle(string $title): string
{
    $filtered = apply_filters('list_pages', $title);
    assert(is_string($filtered));

    return $filtered;
} 

In the first case, you are aiming at stability and defensiveness. Which is great for public APIs.

In the second case, you are making it explicit that you're trusting the filter consumers. This has the same net effect of using the PHPStan plugin (if someone returns an integer from the filter, it breaks) but:

  1. it is explicit
  2. forces the developer to think about it and decide to opt for this or for the defensive path

Having a tool that hides all of that from you, might look nice at first because the checks pass more easily, but that is not the goal, right?

"swissspidy/phpstan-no-private": "^v1.0.0",
"phpstan/phpstan-deprecation-rules": "^2.0.1"
},
"extra": {
"branch-alias": {
Expand All @@ -53,13 +55,13 @@
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"cs": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs",
"psalm": "@php ./vendor/vimeo/psalm/psalm --no-suggestions --report-show-info=false --find-unused-psalm-suppress --no-diff --no-cache --no-file-cache --output-format=compact",
"phpcs": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs",
"phpstan": "@php ./vendor/bin/phpstan analyse --memory-limit=1G",
"tests": "@php ./vendor/phpunit/phpunit/phpunit --no-coverage",
"tests:coverage": "@php ./vendor/phpunit/phpunit/phpunit",
"qa": [
"@cs",
"@psalm",
"@phpcs",
"@phpstan",
"@tests"
]
},
Expand Down
13 changes: 13 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
includes:
- vendor/szepeviktor/phpstan-wordpress/extension.neon
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
- vendor/swissspidy/phpstan-no-private/rules.neon
parameters:
level: 8
paths:
- src/
treatPhpDocTypesAsCertain: false
ignoreErrors:
- '#Trait Inpsyde\\Modularity\\Module\\ModuleClassNameIdTrait is used zero times and is not analysed.#'
# TODO: Check how we can point ABSPATH constant in phpstan to vendor/roots/wordpress-no-content/
- '#Path in require_once\(\) \".*\" is not a file or it does not exist.#'
32 changes: 0 additions & 32 deletions psalm.xml

This file was deleted.

2 changes: 1 addition & 1 deletion src/Container/ReadOnlyContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ReadOnlyContainer implements ContainerInterface
/**
* @param array<string, Service> $services
* @param array<string, bool> $factoryIds
* @param ServiceExtensions|array $extensions
* @param ServiceExtensions|array<string, ExtendingService> $extensions
* @param ContainerInterface[] $containers
*/
public function __construct(
Expand Down
27 changes: 19 additions & 8 deletions src/Container/ServiceExtensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

/**
* @param string $type
*
* @return string
*/
final public static function typeId(string $type): string
Expand All @@ -30,18 +31,22 @@
/**
* @param string $extensionId
* @param ExtendingService $extender
*
* @return static
*/
public function add(string $extensionId, callable $extender): ServiceExtensions
{
isset($this->extensions[$extensionId]) or $this->extensions[$extensionId] = [];
if (!isset($this->extensions[$extensionId])) {
$this->extensions[$extensionId] = [];
}
$this->extensions[$extensionId][] = $extender;

return $this;
}

/**
* @param string $extensionId
*
* @return bool
*/
public function has(string $extensionId): bool
Expand All @@ -53,6 +58,7 @@
* @param mixed $service
* @param string $id
* @param Container $container
*
* @return mixed
*/
final public function resolve($service, string $id, Container $container)
Expand All @@ -68,6 +74,7 @@
* @param string $id
* @param mixed $service
* @param Container $container
*
* @return mixed
*/
protected function resolveById(string $id, $service, Container $container)
Expand All @@ -83,20 +90,19 @@
* @param string $className
* @param object $service
* @param Container $container
* @param array $extendedClasses
* @param string[] $extendedClasses
*
* @return mixed
*
* phpcs:disable Generic.Metrics.CyclomaticComplexity
* phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration
* phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
* phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType
*/
protected function resolveByType(
string $className,
object $service,
Container $container,
array $extendedClasses = []
) {
// phpcs:enable Generic.Metrics.CyclomaticComplexity
// phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration

$extendedClasses[] = $className;

Expand All @@ -111,7 +117,9 @@

// 2nd group of extensions: targeting parent classes
$parents = class_parents($service, false);
($parents === false) and $parents = [];
if ($parents === false) {
$parents = [];

Check warning on line 121 in src/Container/ServiceExtensions.php

View check run for this annotation

Codecov / codecov/patch

src/Container/ServiceExtensions.php#L121

Added line #L121 was not covered by tests
}
foreach ($parents as $parentName) {
$byParent = $this->extensions[self::typeId($parentName)] ?? null;
if (($byParent !== null) && ($byParent !== [])) {
Expand All @@ -121,7 +129,9 @@

// 3rd group of extensions: targeting implemented interfaces
$interfaces = class_implements($service, false);
($interfaces === false) and $interfaces = [];
if ($interfaces === false) {
$interfaces = [];

Check warning on line 133 in src/Container/ServiceExtensions.php

View check run for this annotation

Codecov / codecov/patch

src/Container/ServiceExtensions.php#L133

Added line #L133 was not covered by tests
}
foreach ($interfaces as $interfaceName) {
$byInterface = $this->extensions[self::typeId($interfaceName)] ?? null;
if (($byInterface !== null) && ($byInterface !== [])) {
Expand Down Expand Up @@ -163,6 +173,7 @@
* @param object $service
* @param Container $container
* @param list<ExtendingService> $extenders
*
* @return list{mixed, int}
*/
private function extendByType(
Expand Down
18 changes: 11 additions & 7 deletions src/Package.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

use Inpsyde\Modularity\Container\ContainerConfigurator;
use Inpsyde\Modularity\Container\PackageProxyContainer;
use Inpsyde\Modularity\Module\ExtendingModule;
use Inpsyde\Modularity\Module\ExecutableModule;
use Inpsyde\Modularity\Module\ExtendingModule;
use Inpsyde\Modularity\Module\FactoryModule;
use Inpsyde\Modularity\Module\Module;
use Inpsyde\Modularity\Module\ServiceModule;
Expand All @@ -17,6 +17,8 @@
/**
* @psalm-import-type Service from \Inpsyde\Modularity\Module\ServiceModule
* @psalm-import-type ExtendingService from \Inpsyde\Modularity\Module\ExtendingModule
*
* phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
*/
class Package
{
Expand Down Expand Up @@ -205,12 +207,12 @@ class Package
*/
public static function new(Properties $properties, ContainerInterface ...$containers): Package
{
return new self($properties, ...array_values($containers));
return new self($properties, ...$containers);
}

/**
* @param Properties $properties
* @param list<ContainerInterface> $containers
* @param ContainerInterface ...$containers
*/
private function __construct(Properties $properties, ContainerInterface ...$containers)
{
Expand Down Expand Up @@ -511,7 +513,7 @@ private function addModuleServices(Module $module, string $status): bool
{
/** @var null|array<string, Service|ExtendingService> $services */
$services = null;
/** @var null|callable(string, Service|ExtendingService) $addCallback */
/** @var null|callable(string, Service|ExtendingService): void $addCallback */
$addCallback = null;
switch ($status) {
case self::MODULE_REGISTERED:
Expand Down Expand Up @@ -552,7 +554,7 @@ private function doExecute(): void
$success = $executable->run($this->container());
$this->moduleProgress(
$executable->id(),
$success ? self::MODULE_EXECUTED : self::MODULE_EXECUTION_FAILED
$success ? self::MODULE_EXECUTED : self::MODULE_EXECUTION_FAILED,
);
}
}
Expand All @@ -569,7 +571,9 @@ private function moduleProgress(
?array $serviceIds = null
): void {

isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = [];
if (!isset($this->moduleStatus[$status])) {
$this->moduleStatus[$status] = [];
}
$this->moduleStatus[$status][] = $moduleId;

if (($serviceIds === null) || ($serviceIds === []) || !$this->properties->isDebug()) {
Expand Down Expand Up @@ -740,7 +744,7 @@ private function progress(int $status): void
* @param string $packageName
* @param string $reason
* @param bool $throw
* @return ($throw is true ? never: false)
* @return bool
*/
private function handleConnectionFailure(string $packageName, string $reason, bool $throw): bool
{
Expand Down
20 changes: 16 additions & 4 deletions src/Properties/BaseProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Inpsyde\Modularity\Properties;

/**
* phpcs:disable PHPCompatibility.FunctionDeclarations.RemovedImplicitlyNullableParam.Deprecated
*/
class BaseProperties implements Properties
{
protected ?bool $isDebug = null;
Expand Down Expand Up @@ -40,11 +43,14 @@ protected function __construct(

/**
* @param string $name
*
* @return lowercase-string
*/
protected function sanitizeBaseName(string $name): string
{
substr_count($name, '/') and $name = dirname($name);
if (substr_count($name, '/')) {
$name = dirname($name);
}

return strtolower(pathinfo($name, PATHINFO_FILENAME));
}
Expand Down Expand Up @@ -144,7 +150,9 @@ public function requiresWp(): ?string
{
$value = $this->get(self::PROP_REQUIRES_WP);

return (($value !== '') && is_string($value)) ? $value : null;
return (($value !== '') && is_string($value))
? $value
: null;
}

/**
Expand All @@ -154,11 +162,13 @@ public function requiresPhp(): ?string
{
$value = $this->get(self::PROP_REQUIRES_PHP);

return (($value !== '') && is_string($value)) ? $value : null;
return (($value !== '') && is_string($value))
? $value
: null;
}

/**
* @return array
* @return string[]
*/
public function tags(): array
{
Expand All @@ -168,6 +178,7 @@ public function tags(): array
/**
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get(string $key, $default = null)
Expand All @@ -177,6 +188,7 @@ public function get(string $key, $default = null)

/**
* @param string $key
*
* @return bool
*/
public function has(string $key): bool
Expand Down
Loading
Loading