Giter VIP home page Giter VIP logo

phpstan-wordpress's Introduction

Important

Hello everyone! This is Viktor who runs this PHPStan extension. I am planning to stop contributing to the WordPress ecosystem because it is extremely difficult and no one asks me to join his team as I am a thinker, a devops person, a tool maker (not a builder).

Please support my work to avoid abandoning this package.

Sponsor

Thank you!

WordPress extensions for PHPStan

Packagist stats Packagist Tweet Build Status PHPStan

Static analysis for the WordPress ecosystem.

Installation

Add this package to your project.

composer require --dev szepeviktor/phpstan-wordpress

Make PHPStan find it automatically using phpstan/extension-installer.

composer require --dev phpstan/extension-installer

Or manually include it in your phpstan.neon.

includes:
    - vendor/szepeviktor/phpstan-wordpress/extension.neon

Configuration

Needs no extra configuration. 😃 Simply configure PHPStan - for example - this way.

parameters:
    level: 5
    paths:
        - plugin.php
        - inc/

Please read PHPStan Config Reference.

💡 Use Composer autoloader or a custom autoloader!

Usage

Just start the analysis: vendor/bin/phpstan analyze then fix an error and GOTO 10!

You find further information in the examples directory e.g. examples/phpstan.neon.dist

Usage in WooCommerce webshops

Please see WooCommerce Stubs

What this extension does

  • Makes it possible to run PHPStan on WordPress plugins and themes
  • Loads php-stubs/wordpress-stubs package
  • Provides dynamic return type extensions for many core functions
  • Defines some core constants
  • Handles special functions and classes e.g. is_wp_error()
  • Validates the optional docblock that precedes a call to apply_filters() and treats the type of its first @param as certain

Usage of an apply_filters() docblock

WordPress core - and the wider WordPress ecosystem - uses PHPDoc docblocks in a non-standard manner to document the parameters passed to apply_filters(). Example:

/**
 * Filters the page title when creating an HTML drop-down list of pages.
 *
 * @param string  $title Page title.
 * @param WP_Post $page  Page data object.
 */
$title = apply_filters( 'list_pages', $title, $page );

This extension understands these docblocks when they're present in your code and uses them to instruct PHPStan to treat the return type of the filter as certain, according to the first @param tag. In the example above this means PHPStan treats the type of $title as string.

To make the best use of this feature, ensure that the type of the first @param tag in each of these such docblocks is accurate and correct.

Make your code testable

  • Write clean OOP code: 1 class per file, no other code in class files outside class Name { ... }
  • Structure your code: uniform class names (WPCS or PSR-4), keep classes in a separate directory inc/
  • Add proper PHPDoc blocks to classes, properties, methods, functions, and calls to apply_filters()
  • Choose your main plugin file parts.
  • Avoid using core constants, use core functions
  • Avoid bad parts of PHP
    • functions: eval, extract, compact, list
    • type juggling: $a = '15'; if ($a) ...
  • If you need robust code try avoiding all kinds of type juggling (e.g. if needs a boolean), see Variable handling functions
  • If you are not bound by PHP 5 consider following Neutron Standard
  • Do not enable exit_error in WP_CLI::launch or WP_CLI::runcommand to keep your code testable

Dirty corner (FAQ)

WordPress uses conditional function and class definition for override purposes. Use sed command to exclude function stubs when they are previously defined.

sed -i -e 's#function is_gd_image#function __is_gd_image#' vendor/php-stubs/wordpress-stubs/wordpress-stubs.php

phpstan-wordpress's People

Contributors

bart-jaskulski avatar chouby avatar dingo-d avatar herndlm avatar hug0-drelon avatar iandelmar avatar johnbillion avatar lipemat avatar merlindiavova avatar mundschenk-at avatar ondrejmirtes avatar swissspidy avatar szepeviktor avatar westonruter avatar zebulanstanphill avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

phpstan-wordpress's Issues

File phar://phpstan.phar/conf/bleedingEdge.neone is missing or not readable

I was following Tom McFarlin's directions for PHPStan found here:
https://tommcfarlin.com/better-wordpress-code-4/

This worked. But he referenced your extensions for PHPStan so I tried adding them following your directions:
https://packagist.org/packages/szepeviktor/phpstan-wordpress

Here is my composer.json file (.txt appended to get Github to allow upload):
composer.json.txt

And here is my phpstan.neon.dist file (same w/txt appended)
phpstan.neon.dist.txt

The folder structure for the plugin is:
wp-content\plugins\test
\src
\vendor
composer.json
composer.lock
phpstan.neon.dist
sample-plugin.php

When I run composer update everything seems to run fine. Then I try:

vendor/bin/phpstan analyse src

And get back an error:

Note: Using configuration file /var/www/phpdev.davemackey.net/wp-content/plugins/test/phpstan.neon.dist. File 'phar://phpstan.phar/conf/bleedingEdge.neon' is missing or is not readable.

Any suggestions are appreciated. :)

Alert when add_filter is used with a function with type hinting

So after a discussion on #core-conding-standards it is clear that is important to suggest to the developers when a function is used in a filter to not use type hinting in the function definition.
This because there are other developers that doesn't follow the standards and this can create fatal errors because a filter like the_excerpt return null instead of string.

So ideally there should be a rule that check if add_filter is used, get the second parameter and find that function to see if is using type hinting. After this add an alert in console with information about it.

I tried but I cannot find a phpstan documentation about how to do extensions or example to achieve this.

Deprecation Warnings of missing return types in WP stubs

When running analyze I get the following deprecation warnings. I'm using PHPStan using docker.

Deprecated: Return type of Requests_Utility_FilteredIterator::unserialize($serialized) should either be compatible with ArrayIterator::unserialize(string $data): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 21346
Deprecated: Return type of Requests_Utility_FilteredIterator::__unserialize($serialized) should either be compatible with ArrayIterator::__unserialize(array $data): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 21354
Deprecated: Return type of Requests_Utility_FilteredIterator::current() should either be compatible with ArrayIterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 21340
Deprecated: Return type of Requests_Cookie_Jar::offsetExists($key) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 18582
Deprecated: Return type of Requests_Cookie_Jar::offsetGet($key) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 18591
Deprecated: Return type of Requests_Cookie_Jar::offsetSet($key, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 18602
Deprecated: Return type of Requests_Cookie_Jar::offsetUnset($key) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 18610
Deprecated: Return type of Requests_Cookie_Jar::getIterator() should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 18618
Deprecated: Return type of Requests_Utility_CaseInsensitiveDictionary::offsetExists($key) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 20573
Deprecated: Return type of Requests_Utility_CaseInsensitiveDictionary::offsetGet($key) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 20582
Deprecated: Return type of Requests_Utility_CaseInsensitiveDictionary::offsetSet($key, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 20593
Deprecated: Return type of Requests_Utility_CaseInsensitiveDictionary::offsetUnset($key) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 20601
Deprecated: Return type of Requests_Utility_CaseInsensitiveDictionary::getIterator() should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 20609
Deprecated: Return type of WP_Block_List::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31174
Deprecated: Return type of WP_Block_List::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31196
Deprecated: Return type of WP_Block_List::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31186
Deprecated: Return type of WP_Block_List::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31206
Deprecated: Return type of WP_Block_List::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31162
Deprecated: Return type of WP_Block_List::offsetExists($index) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31114
Deprecated: Return type of WP_Block_List::offsetGet($index) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31127
Deprecated: Return type of WP_Block_List::offsetSet($index, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31140
Deprecated: Return type of WP_Block_List::offsetUnset($index) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31152
Deprecated: Return type of WP_Block_List::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 31218
Deprecated: Return type of WP_Hook::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39085
Deprecated: Return type of WP_Hook::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39097
Deprecated: Return type of WP_Hook::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39109
Deprecated: Return type of WP_Hook::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39121
Deprecated: Return type of WP_Hook::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39131
Deprecated: Return type of WP_Hook::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39035
Deprecated: Return type of WP_Hook::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39048
Deprecated: Return type of WP_Hook::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39061
Deprecated: Return type of WP_Hook::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 39073
Deprecated: Return type of WP_Theme::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 50086
Deprecated: Return type of WP_Theme::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 50104
Deprecated: Return type of WP_Theme::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 50065
Deprecated: Return type of WP_Theme::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 50075
Deprecated: Return type of WP_REST_Request::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 59361
Deprecated: Return type of WP_REST_Request::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 59372
Deprecated: Return type of WP_REST_Request::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 59383
Deprecated: Return type of WP_REST_Request::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /composer/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 59393

phpstan.neon

parameters:
	level: 0
	paths:
		- plugin.php
includes:
    - /composer/vendor/szepeviktor/phpstan-wordpress/extension.neon

Add functional tests

Would it be so simple that we add tiny files with WordPress code and run PHPStan on it?

vendor/bin/phpstan analyse -c extension.neon -l max tests/

Call to function is_wp_error() with int<0, max> will always evaluate to false.

In phpstan-wordpress v1.1 (not previous versions) with PHPStan level 6, when running something along the lines of:

$id = wp_insert_post([
    'post_title' => "Some Title",
]);
if (is_wp_error($id)) {
    ...
}

I get the errors:

Call to function is_wp_error() with int<0, max> will always evaluate to false.
is_wp_error(int<0, max>) will always evaluate to false.

This function can return an instance of WP_Error though [1][2][3][4][5][6], so the code is correct.

Here's my phpstan.neon

includes:
    # Include this extension
    - vendor/szepeviktor/phpstan-wordpress/extension.neon
parameters:
    level: 6
    inferPrivatePropertyTypeFromConstructor: true
    paths:
        - MyFolder

Unhook WP_Embed::autoembed filter

@jenkoian asked a question to remove this core hook.

// Attempts to embed all URLs in a post
add_filter( 'the_content', array( $this, 'autoembed' ), 8 );

by adding remove_filter( 'the_content', [ $GLOBALS['wp_embed'], 'autoembed' ], 8 );

$wpdb->last_error type should be un-narrowed after running $wpdb->query()

Consider the following code:

/**
 * @template T
 * @param callable(): T $callback
 * @return T
 */
function doDbTransaction(callable $callback) {
	global $wpdb;

	try {
		$wpdb->query('START TRANSACTION');
		if ($wpdb->last_error !== '') {
			throw new \Exception(
				sprintf(
					__('Starting db transaction failed. WPDB error: %s'),
					$wpdb->last_error
				)
			);
		}

		$returnValue = $callback();

		$wpdb->query('COMMIT');
		if ($wpdb->last_error !== '') {
			throw new \Exception(
				sprintf(
					__('Committing db transaction failed. WPDB error: %s'),
					$wpdb->last_error
				)
			);
		}

		return $returnValue;
	} catch (\Throwable $e) {
		$wpdb->query('ROLLBACK');
		throw $e;
	}
}

When using phpstan/phpstan-strict-rules, the second if statement causes an error to be thrown by PHPStan's analysis:

Strict comparison using !== between '' and '' will always evaluate to false.

The type was narrowed by the first if statement, but since $wpdb->query() can change the value of $wpdb-last_error, the second if statement is completely valid. The type of $wpdb->last_error should be un-narrowed after every $wpdb->query() call.

Performance issues

I noticed that this extension increases both CPU and memory resources quite a bit. Sorry, I did not have time to profile it, but the "problems" seem to be there since 1.0.0 at least already.

I have quite a small plugin that uses the latest phpstan, phpstan-webmozart-assert extension and phpstan-wordpress extension.

Test case running vendor/bin/phpstan --debug --verbose analyse
With this extension: ~17s using ~310M memory
Without this extension:~4s using ~110M memory

Could be something really simple that we need to improve here, who knows. I can profile it soon hopefully. I see slowness in one specific file, so I'll need to inspect that one in particular.

Cannot redeclare wp_set_password()

Hello we have wordpress installed by composer. The composer installs it to two separate folders (wp where are all core files, wp-content etc.) And when installing something else(i.e. plugins etc.) they are installed to separate folder wp-content in root. Wordpress works good. But when trying to run phpStan there is an error
Fatal error: Cannot redeclare wp_set_password() (previously declared in C:\wamp64\www\uw-kuppro\wp-content\vendor\roots\wp-password-bcrypt\wp-password-bcrypt.php:86) in C:\wamp64\www\uw-kuppro\wp-content\vendor\php-stubs\wordpress-s tubs\wordpress-stubs.php on line 109004 Script wp-content/vendor/bin/phpstan analyse --debug handling the phpstan event returned with error code 255 make: *** [Makefile:2: pre-commit] Error 255
I presume that it is because of the folder structure? Or problem is somewhere else? On another project(only plugin for WP) your phpStan rules works great. Thanks for your help.
Michal Dolezal

Coverage calculation problem

Contributions are welcome!

Code Coverage Report:
  2022-06-01 21:26:20

 Summary:
  Classes: 13.64% (3/22)
  Methods: 53.23% (33/62)
  Lines:   32.20% (133/413)

SzepeViktor\PHPStan\WordPress\ApplyFiltersDynamicFunctionReturnTypeExtension
  Methods:  66.67% ( 2/ 3)   Lines:  91.67% ( 11/ 12)
SzepeViktor\PHPStan\WordPress\CurrentTimeDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   7.69% (  1/ 13)
SzepeViktor\PHPStan\WordPress\EchoParameterDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   5.88% (  1/ 17)
SzepeViktor\PHPStan\WordPress\GetCommentDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   6.25% (  1/ 16)
SzepeViktor\PHPStan\WordPress\GetListTableDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   8.33% (  1/ 12)
SzepeViktor\PHPStan\WordPress\GetObjectTaxonomiesDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   7.14% (  1/ 14)
SzepeViktor\PHPStan\WordPress\GetPostDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   6.25% (  1/ 16)
SzepeViktor\PHPStan\WordPress\GetPostsDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   4.35% (  1/ 23)
SzepeViktor\PHPStan\WordPress\GetTaxonomiesDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   6.67% (  1/ 15)
SzepeViktor\PHPStan\WordPress\GetTermsDynamicFunctionReturnTypeExtension
  Methods:  10.00% ( 1/10)   Lines:   1.59% (  1/ 63)
SzepeViktor\PHPStan\WordPress\HasFilterDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   7.14% (  1/ 14)
SzepeViktor\PHPStan\WordPress\HookDocBlock
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 13/ 13)
SzepeViktor\PHPStan\WordPress\HookDocsParamException
  Methods:  ( 0/ 0)   Lines:  (  0/  0)
SzepeViktor\PHPStan\WordPress\HookDocsRule
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 57/ 57)
SzepeViktor\PHPStan\WordPress\HookDocsVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 11/ 11)
SzepeViktor\PHPStan\WordPress\IsWpErrorFunctionTypeSpecifyingExtension
  Methods:  33.33% ( 1/ 3)   Lines:  37.50% (  3/  8)
SzepeViktor\PHPStan\WordPress\IsWpErrorRule
  Methods:  66.67% ( 2/ 3)   Lines:  86.96% ( 20/ 23)
SzepeViktor\PHPStan\WordPress\MySQL2DateDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   8.33% (  1/ 12)
SzepeViktor\PHPStan\WordPress\RedirectCanonicalDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:  50.00% (  1/  2)
SzepeViktor\PHPStan\WordPress\ShortcodeAttsDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   6.67% (  1/ 15)
SzepeViktor\PHPStan\WordPress\StringOrArrayDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:   6.67% (  1/ 15)
SzepeViktor\PHPStan\WordPress\TermExistsDynamicFunctionReturnTypeExtension
  Methods:  50.00% ( 1/ 2)   Lines:  11.54% (  3/ 26)
SzepeViktor\PHPStan\WordPress\WPErrorParameterDynamicFunctionReturnTypeExtension
  Methods:  33.33% ( 1/ 3)   Lines:   6.25% (  1/ 16)

Add type checking for hook callbacks in `add_action` and `add_filter`

I'm planning on working on some rules that check the use of add_action() and add_filter() when the action or filter is from WordPress core. The wp-hooks library facilitates this, and the wp-hooks-generator library facilitates this for hooks in third party plugins and themes (although I'm only concentrating on the WordPress core hooks for now).

I'll open a PR once I have something working.


When I call add_action( 'foo', $callback ) or add_filter( 'foo', $callback ):

  • The signature of the callback function should be checked against the parameters that get passed to the hook
  • The number used in the $accepted_args parameter should match the number of parameters that the callback accepts
  • A filter should not be used as an action, nor vice versa
  • The callback for add_filter must return a value with a type which is compatible with the type passed to it
  • The callback for add_action must return void

Notes:

  • It's fine for the callback function to accept fewer parameters than are passed to the hook (but see point above about $accepted_args)
  • There is no requirement to have a docblock on the callback because it's the job of PHPStan to figure out parameter types and raise errors due to implicit mixed, etc
  • We could also look up the literal hook name, eg plugin_action_links_{$plugin_file} and see if we get a match for dynamic hook names (stretch goal)

Add dynamic return type for generic functions

There are several generic functions in WordPress that return a value of the same type as passed in. Here's my list so far:

  • addslashes_gpc()
  • map_deep()
  • rawurlencode_deep()
  • sanitize_category()
  • sanitize_post()
  • sanitize_term()
  • stripslashes_deep()
  • urldecode_deep()
  • urlencode_deep()
  • wp_slash()
  • wp_unslash()

We should be able to come up with a dynamic return type extension which makes the function behave like a generic and return the same type as passed in.

Detect wp_die exit arg

wp_die is listed in earlyTerminatingFunctionCalls, which is fine by default.

However, when passing [ 'exit' => false ] as the third param, it won't terminate early.

Is detection for this possible using a custom extension?

How to deal with mixed types from WordPress core?

When I use functions from WordPress, PHPstan is flagging them as having incorrect mixed types when used in printf or sprintf functions.

First example is: esc_url( get_permalink() ) which says Parameter #1 $url of function esc_url expects string, string|false given. because get_permalink return string or integer according to the docs.

Second example is: get_the_term_list( get_the_ID(), 'category', '', ', ', '' ) which says Parameter #1 $post_id of function get_the_term_list expects int, int|false given. because get_the_ID() returns an integer or false according to the docs.

Am I doing something wrong when using these functions or do I need to mark these lines as OK to ignore?

Here is my entire block of code.

$output .= sprintf(
	'<li><h3 class="entry-title"><a href="%s">%s</a></h3>%s<span class="entry-sep">&middot;</span>%s<span class="entry-sep">&middot;</span>%s</li>',
	esc_url( get_permalink() ),
	esc_html( get_the_title() ),
	get_the_date(),
	get_the_term_list( get_the_ID(), 'category', '', ', ', '' ),
	poa_get_the_time_to_read(),
);

Question: how to configure phpstan-wordpress to use Neutron Standard?

Hi,

I'm a phpcs user, using Neutron standard rules.

I've just started using phpstan and phpstan-wordress too. I'm quite new to these tools, I just copied-pasted your neon config files but I can't figure out how to make use of Neutron rules.

Could you kindly help me?

Thanks!

have_posts() is an impure function!

stubFiles:
    - tests/phpstan/wordpress-override.stub
<?php

/**
 * Whether current WordPress query has results to loop over.
 *
 * @since 1.5.0
 *
 * @global WP_Query $wp_query Global WP_Query instance.
 *
 * @return bool
 * @phpstan-impure
 */
function have_posts()
{
}

class WP_Query
{
    /**
     * Determines whether there are more posts available in the loop.
     *
     * Calls the {@see 'loop_end'} action when the loop is complete.
     *
     * @since 1.5.0
     *
     * @return bool True if posts are available, false if end of loop.
     * @phpstan-impure
     */
    public function have_posts()
    {
    }
}

phpstan/phpstan#8822

Cannot redeclare wp_set_password() when using roots/bedrock

It seems that there are few conflicts when using https://roots.io/bedrock/ with roots/wp-password-bcrypt which declares its own variants of some funtions:

  • wp_set_password
  • wp_generate_password
  • wp_check_password
  • wp_hash_password

Running vendor/bin/phpstan analyze throws:

$ vendor/bin/phpstan analyze
Note: Using configuration file /var/www/example.com/phpstan.neon.
Fatal error: Cannot redeclare wp_hash_password() (previously declared in /var/www/example.com/vendor/roots/wp-password-bcrypt/wp-password-bcrypt.php:52) in /var/www/example.com/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 105420

Steps to reproduce

# Install roots/bedrock
composer create-project roots/bedrock
cd bedrock
# Create some dummy plugin to test
mkdir -p web/app/plugins/test/
touch web/app/plugins/test/test.php
# Run phpstan
vendor/bin/phpstan analyze web/app/plugins/test/
[...]
Fatal error: Cannot redeclare wp_hash_password() (previously declared in /var/www/example.com/bedrock/vendor/roots/wp-password-bcrypt/wp-password-bcrypt.php:52) in /var/www/example.com/bedrock/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php on line 105420

Workaround

As bad as this is - I just comment out these four functions mentioned above from vendor/php-stubs/wordpress-stubs/wordpress-stubs.php

Internal error: Internal error: Multiple variants - use selectFromArgs() instead.

I am using PHPStan via the last version of szepeviktor/phpstan-wordpress.

I get this error when I want to analyse my code.

-- -------------------------------------------------------------------------------------------------------------------------
     Error                                                                                                                          
 -- -------------------------------------------------------------------------------------------------------------------------
     Internal error: Internal error: Multiple variants - use selectFromArgs() instead. in file /Users/arnaud/Local                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/core/AjaxSetup.php                                          
                                                                                                                                    
     Post the following stack trace to https://github.com/phpstan/phpstan/issues/new?template=Bug_report.md:                        
     #0 /Users/arnaud/Local                                                                                                         
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/szepeviktor/phpstan-wordpress/src/HookCallbackRule.  
     php(83): PHPStan\Reflection\ParametersAcceptorSelector::selectSingle(Array)                                                    
     #1 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnaly  
     ser.php(106): SzepeViktor\PHPStan\WordPress\HookCallbackRule->processNode(Object(PhpParser\Node\Expr\FuncCall),                
     Object(PHPStan\Analyser\MutatingScope))                                                                                        
     #2 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Node/ClassStatemen  
     tsGatherer.php(98): PHPStan\Analyser\FileAnalyser->PHPStan\Analyser\{closure}(Object(PhpParser\Node\Expr\FuncCall),            
     Object(PHPStan\Analyser\MutatingScope))                                                                                        
     #3 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(503): PHPStan\Node\ClassStatementsGatherer->__invoke(Object(PhpParser\Node\Expr\FuncCall),                        
     Object(PHPStan\Analyser\MutatingScope))                                                                                        
     #4 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(2460): PHPStan\Analyser\NodeScopeResolver::PHPStan\Analyser\{closure}(Object(PhpParser\Node\Expr\FuncCall),       
     Object(PHPStan\Analyser\MutatingScope))                                                                                        
     #5 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(1462): PHPStan\Analyser\NodeScopeResolver->callNodeCallbackWithExpression(Object(Closure),                        
     Object(PhpParser\Node\Expr\FuncCall), Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\ExpressionContext))      
     #6 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(555): PHPStan\Analyser\NodeScopeResolver->processExprNode(Object(PhpParser\Node\Expr\FuncCall),                   
     Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\ExpressionContext))                           
     #7 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(357): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Expression),                 
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #8 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(734): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Foreach_), Array,           
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #9 phar:///Users/arnaud/Local                                                                                                  
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(357): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Foreach_),                   
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #10 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(518): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\ClassMethod), Array,        
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #11 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(357): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\ClassMethod),                
     Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Node\ClassStatementsGatherer))                                          
     #12 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(596): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Class_), Array,             
     Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Node\ClassStatementsGatherer))                                          
     #13 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(357): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Class_),                     
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #14 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(568): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Namespace_), Array,         
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #15 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScope  
     Resolver.php(327): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Namespace_),                 
     Object(PHPStan\Analyser\MutatingScope), Object(Closure))                                                                       
     #16 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnaly  
     ser.php(175): PHPStan\Analyser\NodeScopeResolver->processNodes(Array, Object(PHPStan\Analyser\MutatingScope),                  
     Object(Closure))                                                                                                               
     #17 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerComm  
     and.php(147): PHPStan\Analyser\FileAnalyser->analyseFile('/Users/arnaud/L...', Array, Object(PHPStan\Rules\LazyRegistry),      
     Object(PHPStan\Collectors\Registry), NULL)                                                                                     
     #18 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evene  
     ment/src/Evenement/EventEmitterTrait.php(97): PHPStan\Command\WorkerCommand->PHPStan\Command\{closure}(Array)                  
     #19 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/clue/ndjson-rea  
     ct/src/Decoder.php(110): _PHPStan_582a9cb8b\Evenement\EventEmitter->emit('data', Array)                                        
     #20 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evene  
     ment/src/Evenement/EventEmitterTrait.php(97): _PHPStan_582a9cb8b\Clue\React\NDJson\Decoder->handleData(Array)                  
     #21 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/sr  
     c/Util.php(62): _PHPStan_582a9cb8b\Evenement\EventEmitter->emit('data', Array)                                                 
     #22 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evene  
     ment/src/Evenement/EventEmitterTrait.php(97):                                                                                  
     _PHPStan_582a9cb8b\React\Stream\Util::_PHPStan_582a9cb8b\React\Stream\{closure}('{"action":"anal...')                          
     #23 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/sr  
     c/DuplexResourceStream.php(154): _PHPStan_582a9cb8b\Evenement\EventEmitter->emit('data', Array)                                
     #24 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loo  
     p/src/StreamSelectLoop.php(201): _PHPStan_582a9cb8b\React\Stream\DuplexResourceStream->handleData(Resource id #3450)           
     #25 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loo  
     p/src/StreamSelectLoop.php(173): _PHPStan_582a9cb8b\React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)              
     #26 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerComm  
     and.php(107): _PHPStan_582a9cb8b\React\EventLoop\StreamSelectLoop->run()                                                       
     #27 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console  
     /Command/Command.php(259):                                                                                                     
     PHPStan\Command\WorkerCommand->execute(Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Input\ArgvInput),                   
     Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Output\ConsoleOutput))                                                     
     #28 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console  
     /Application.php(868):                                                                                                         
     _PHPStan_582a9cb8b\Symfony\Component\Console\Command\Command->run(Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Input\A  
     rgvInput), Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Output\ConsoleOutput))                                          
     #29 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console  
     /Application.php(259):                                                                                                         
     _PHPStan_582a9cb8b\Symfony\Component\Console\Application->doRunCommand(Object(PHPStan\Command\WorkerCommand),                  
     Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Input\ArgvInput),                                                          
     Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Output\ConsoleOutput))                                                     
     #30 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console  
     /Application.php(157):                                                                                                         
     _PHPStan_582a9cb8b\Symfony\Component\Console\Application->doRun(Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Input\Arg  
     vInput), Object(_PHPStan_582a9cb8b\Symfony\Component\Console\Output\ConsoleOutput))                                            
     #31 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(124):       
     _PHPStan_582a9cb8b\Symfony\Component\Console\Application->run()                                                                
     #32 phar:///Users/arnaud/Local                                                                                                 
     Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(125):       
     _PHPStan_582a9cb8b\{closure}()                                                                                                 
     #33 /Users/arnaud/Local Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/phpstan/phpstan/phpstan(7):  
     require('phar:///Users/a...')                                                                                                  
     #34 /Users/arnaud/Local Sites/other-builder-version/app/public/wp-content/themes/creatorem/vendor/bin/phpstan(115):            
     include('/Users/arnaud/L...')                                                                                                  
     #35 {main}                                                                                                                     
     Child process error (exit code 1):                                                                                             
 -- -------------------------------------------------------------------------------------------------------------------------

I found that this issue comes from this section of code
This code trigger the issue:

class Setup {
	const ACTIONS = array(
		'save_page_builder_data' => 'update_page_builder_data',
	);

	public static function init(): void {
		foreach ( self::ACTIONS as $action => $method ) {
			add_action( 'wp_ajax_' . $action, array( self::class, $method ) );
			add_action( 'wp_ajax_nopriv_' . $action, array( self::class, $method ) );
		}
	}

	public static function update_page_builder_data(): void {
                // code here ...
	}
}

And this code works:

class Setup {
	public static function init(): void {
		add_action( 'wp_ajax_save_page_builder_data', array( self::class, 'update_page_builder_data' ) );
		add_action( 'wp_ajax_nopriv_save_page_builder_data', array( self::class, 'update_page_builder_data' ) );
	}

	public static function update_page_builder_data(): void {
                // code here ...
	}
}

How should I do to be able to use the first syntax ? I think that I need to tell to PHPStan than the values in the ACTIONS array corresponds to the name of method.

Do I need stubs for parent theme?

I am building a Genesis child theme and phpstan is saying it can't find certain functions that come from the parent theme. Do I need something to pull those in? I am not sure I even know the words to describe what I might need. Stubs?

37     Function genesis_get_nav_menu not found.                                                     
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols  

After reading the link mentioned above, maybe I need to include the path to the parent theme in on .neon config.

Maybe either of these applies to my situation?

https://phpstan.org/user-guide/discovering-symbols#third-party-code-outside-of-composer-dependencies

https://phpstan.org/user-guide/discovering-symbols#custom-autoloader

PHPStan does not run because it can't open wp-config.php

From this line https://github.com/wp-plugins/schreikasten/blob/master/feed.php#L3

I get an error

PHP Fatal error:  require_once(): Failed opening required '../../../wp-config.php' (include_path='.:/usr/share/php') in /home/flip111/php/schreikasten/feed.php on line 3

Maybe this is "normal" for phpstan. But vimeo/psalm i can just load vendor/php-stubs/wordpress-stubs/wordpress-stubs.php as stubs file and the analysis runs without problems. This is likely due to a design decision of phpstan itself, but i wanted to report it here first because this package is setting up the configuration for phpstan.

Early return not recognized in the case of `\WP_CLI::error`

Hi!

I have a method

public function getComposer(array $args = []): array
{
	if (!isset($args['config_path'])) {
		if (function_exists('\add_action')) {
			$composerPath = $this->getProjectRootPath() . '/composer.json';
		} else {
			$composerPath = $this->getProjectRootPath(true) . '/composer.json';
		}
	} else {
		$composerPath = $args['config_path'];
	}

	$composerFile = file_get_contents($composerPath);

	if ($composerFile === false) {
		\WP_CLI::error("The composer on {$composerPath} path seems to be missing.");
	}

	return json_decode($composerFile, true);
}

And I'm getting an error

Parameter #1 $json of function json_decode expects string,
string|false given.

But above the json_decode there is a check

if ($composerFile === false) {
	\WP_CLI::error("The composer on {$composerPath} path seems to be missing.");
}

And \WP_CLI::error will exit() out, and terminate script execution.

Add return type for query string `$args` in `GetTermsDynamicFunctionReturnTypeExtension()`

Currently, the extension does not handle query strings in the $args parameter.

if (!($argument instanceof ConstantArrayType)) {
// Without constant array argument return default return type
return self::defaultType();
}

Supported functions that explicitly accept query strings:

  • get_tags()
  • get_terms()
  • wp_get_object_terms()

Supported functions that implicitly accept query strings by calling wp_parse_args() or a function that does:

  • wp_get_post_categories()
  • wp_get_post_tags()
  • wp_get_post_terms()

Constant FS_CHMOD_FILE not found.

When running the scan over a filesystem operation I got the

Constant FS_CHMOD_FILE not found.
💡 Learn more at https://phpstan.org/user-guide/discovering-symbols

The code in question looks like this:

$wp_filesystem->put_contents(
	$attachment,
	$pdfContents,
	FS_CHMOD_FILE // predefined mode settings for WP files.
);

The docs recommend adding a constants.php file that would be bootstraped during the scan. But that kinda seems like overkill IMO.

Could this be added to wordpress-stubs and then let phpstan read WP globals from there?

error string unformatted

this seemed to lack the usual pretty feedback:

Ignored error pattern /^Parameter #2 $callable of static method WP_CLI::add_command() expects callable(): mixed, \S+ given.$/ was not matched in reported errors.

https://circleci.com/gh/WP2Static/wp2static/493

elementor/wp2static@c244af2

https://github.com/WP2Static/wp2static/blob/c244af202a301c1ce9b6795c249b854f0b5c07fd/wp2static.php#L92

and / or

https://github.com/WP2Static/wp2static/blob/c244af202a301c1ce9b6795c249b854f0b5c07fd/wp2static.php#L93

Treat assertNotWPError() and assertWPError() as type-narrowing functions

In the WordPress core test suite the following assertion methods are available:

  • $this->assertNotWPError()
  • $this->assertWPError()

It would be great if this extension treated these methods as type-narrowing functions, so the following code would not throw an error in PHPStan:

$terms = wp_get_post_terms( $post_id, $taxonomy );
$this->assertNotWPError( $terms );
$this->assertCount( 0, $terms );

Currently this triggers the following error because $terms is assumed to be array|WP_Error on the last line.

[phpstan] Parameter #2 $haystack of method PHPUnit\Framework\Assert::assertCount() expects Countable|iterable, array|WP_Error given.

EchoKey extension: Properly handle `getConstantStrings()` and `getConstantArrays()`

I only skimmed through it but saw first element access of array and string arrays. Jfyi in most cases the generic approach is to handle all of them and union the results then 😊

Originally posted by @herndlm in #146 (comment)

Loop through all elements in the list of constant strings/array:

if (count($type->getConstantStrings()) === 0) {
    return []; // no constant strings
}

foreach ($type->getConstantStrings() as $constantString) {
    // do something with each value
}

Incorrect return type for `esc_sql()`, `wp_slash()` and `wp_unslash()`

StringOrArrayDynamicFunctionReturnTypeExtension narrows the return type to strings. While this is true for esc_sql(), wp_slash() and wp_unslash() do not change the type of the values.

wp_slash([true]); // gives [true]
wp_unslash(1); // gives 1
esc_sql([true]); // gives ['1']
esc_sql(1); // gives '1'

For Array[] also the return type for esc_sql() is incorrect.

esc_sql([[1]]); // gives array<int,array<int,string>>
esc_sql([['key' => 1]]); // gives array<int,array<string,string>>
esc_sql(['key' => [1]]); // gives array<string,array<int,string>>
esc_sql(['key1' => ['key2' => 1]]); // gives array<string,array<string,string>>
// the extension gives either array<int,string> or array<string,string>

Currently the extension also lacks a maybe array case, ie string|array<int|string,mixed>, and a maybe string array key case, ie array<int|string,mixed>.

Compatibility of WP_Nav_Menu_Item with WP_Post

PHPStan throws

Parameter #2 $item (WP_Nav_Menu_Item) of method Class_Name::start_el() should be compatible with parameter $item (WP_Post) of method Class_Name::start_el()

when analyzing

class Class_Name extends Walker_Nav_Menu {
	/**
	* Starts the element output.
	*
	* @since WP 3.0.0
	* @since WP 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
	*
	* @see Walker_Nav_Menu::start_el()
	*
	* @param string           $output Used to append additional content (passed by reference).
	* @param WP_Nav_Menu_Item $item   Menu item data object.
	* @param int              $depth  Depth of menu item. Used for padding.
	* @param WP_Nav_Menu_Args $args   An object of wp_nav_menu() arguments.
	* @param int              $id     Current item ID.
	*/
	public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
		...
	}
}

The extension seems to not be working with phpstan/extension-installer

Here is my global composer.json:

{
  "config": {
      "sort-packages": true
  },
  "require": {
  },
  "require-dev": {
    "giacocorsiglia/stubs-generator": "^0.5.0",
    "phpstan/extension-installer": "^1.0",
    "phpstan/phpstan": "^0.12.50",
    "szepeviktor/phpstan-wordpress": "^0.6.6"
  }
}

(I'm not using giacocorsiglia/stubs-generator at the moment, but it's installed in case I need to generate some stubs)

My inspection fails on a file where line №2 is if(is_admin() || defined('XMLRPC_REQUEST')):.

The error is Function is_admin not found..

In fact, this and many other files produce similar errors multiple times for basic functions such as is_admin, __, add_action, etc.

Any lead on what am I missing?

array_map callable not accepted

Writing a class for my plugin, I used this code to sanitize input:

$new_input['endpoints'] = array_map( 'esc_url_raw', $input['endpoints'], $protocols = array( 'https' ) );

where $input['endpoints'] is an array.

PHPStan seems not to see esc_url_raw as a callable function, and says:

Parameter #1 $callback of function array_map expects (callable(string, 'https'): mixed)|null, 'esc_url_raw' given.

I am not sure if this is related to stubs, or it is a syntax problem.

Problem with polyfills for compatibility with previous WordPress versions

As part of making my plugin Avatar Privacy PHP-8-compatible, I added a polyfill for the is_gd_image function introduced in WordPress 5.6.0. While my polyfill is guarded by function_exists, the stubs in php-stubs/wordpress-stubs are not, making phpstan analyze fail (due to Composer autoloading my functions before the stubs).

Any good solution for WP array shapes not being recognized by PHPStan?

Is there currently any good solution (other than disabling the warning) for PHPStan not recognizing WordPress Documentation Standards array shape definitions?

@param array {
    Some description.
   
   @type int    $foo Some parameter description.
   @type string $bar Some other parameter description.
}

add_menu_page expecting a callable but callable|callable-string should be fine too

Is this a problem we can fix? Looks like it's coming from the stubs / wordpress docs, but actually it is supposed to be used with callable strings, right? E.g. as in

add_menu_page('Docs', 'Docs', 'manage_options', 'docs', 'my-docs-function', 'dashicons-editor-help');

phpstan reports

Parameter #5 $function add_menu_page expects callable(): mixed, 'my-docs-function' given.

I guess many many more functions are having the same problems?

This is more like a discussion, @szepeviktor want to enable the discussions feature in this repo? ;)

Extend PHPStan scanning to cover `@param` tags of `apply_filters()` and `do_action()`

No idea if this is possible, I'm going to look into it and maybe ask on the PHPStan discussion forums.

When PHPStan encounters a call to apply_filters() or do_action() it should validate the types of the parameters passed to it according to the @param tags in the preceding docblock.

Example:

/**
 * Filters the date formatted based on the locale.
 *
 * @param string       $date      Formatted date string.
 * @param string       $format    Format to display the date.
 * @param int          $timestamp Unix timestamp.
 * @param DateTimeZone $timezone  Timezone.
 */
$date = apply_filters( 'wp_date', $date, $format, $timestamp, $timezone );

Here, PHPStan should validate that $date, $format, $timestamp, and $timezone are of the correct types and trigger an error if not. This helps to ensure that the documentation for filters and actions is accurate.

It might be possible to achieve this by treating calls to apply_filters() and do_action() in the same manner as a closure. As far as PHPStan is concerned, this code produces the same result:

/**
 * Filters the date formatted based on the locale.
 *
 * @param string       $date      Formatted date string.
 * @param string       $format    Format to display the date.
 * @param int          $timestamp Unix timestamp.
 * @param DateTimeZone $timezone  Timezone.
 */
$date = function( string $date, string $format, int $timestamp, DateTimeZone $timezone ) : string {
    return $date;
}

How long should PHPStan take?

A private project I work on takes around 8-10 minutes to run analysis for. It's a bespoke theme and a bunch of bespoke/private plugins so the code base is fairly large but still seems like an excessive time period considering other tools like phpcs finish in a few seconds at most. PHPUnit takes a similar amount of time, so is this just an issue of scale?

Does not work with globally installed phpstan

I have phpstan installed with composer global require phpstan/phpstan

My config file is this

includes:
  - phar://phpstan.phar/conf/bleedingEdge.neon
  - vendor/szepeviktor/phpstan-wordpress/extension.neon
parameters:
  level: max
  inferPrivatePropertyTypeFromConstructor: true
  excludes_analyse:
    - vendor/*
    - templates
  paths:
    - .
    - libs
  autoload_files:
    - cubepoints.php
    - feed.php
    - schreikasten.php
    - uninstall.php
  autoload_directories:
    - libs

Then i get this error

Autoload file /home/flip111/.config/composer/vendor/phpstan/phpstan/../../php-stubs/wordpress-stubs/wordpress-stubs.php does not exist.

False positives in `add_filter()` type checking

Re-sharing from #107 (comment)

The new rules check that callbacks added for add_filter actually return a value.

In my initial testing it doesn't always work, the rule only seems to check the return type declaration, but is ignoring any @return docblocks.

Example:

https://github.com/GoogleForCreators/web-stories-wp/blob/c3b5eec59b0bb79dc096260f5c9614cffc3b3778/includes/Admin/Admin.php#L75-L78

The top three lines are getting flagged because those methods don't have a return type (just in PHPDoc).
The last one has a PHP return type and is not flagged.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.