WordPress extensions for PHPStan ⛏️

Overview

WordPress extensions for PHPStan

Build Status Packagist Packagist stats 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 futher information in the example directory e.g. example/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
Comments
  • Performance issues

    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.

    opened by herndlm 23
  • Cannot redeclare wp_set_password() when using roots/bedrock

    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

    opened by mklepaczewski 22
  • Add type checking for hook callbacks in `add_action` and `add_filter`

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

    This implements part of #106.

    I'm learning more about PHPStan internals as I work on this, so it's going to take some time 😄 . Once everything's working and covered by tests then I'll refactor it.

    Applicable to all hooks:

    • [X] The number used in the $accepted_args parameter should match the number of parameters that the callback accepts
    • [x] The callback for add_filter must return a value
    • [x] The callback for add_action must return void

    ~~Applicable when the hook signature is known (eg. a WordPress core hook):~~

    • [ ] ~~The signature of the callback function should be checked against the parameters that get passed to the hook~~
    • [ ] ~~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~~

    I've decided to implement the WordPress core hook handling in a separate PR because it relies on the wp-hooks API package that I'm writing and it's not ready yet.

    opened by johnbillion 20
  • Internal error: Internal error: Multiple variants - use selectFromArgs() instead.

    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.

    opened by arnaudpfu 13
  • Add tests

    Add tests

    This adds a testing framework and tests for the mysql2date() dynamic return type extension as a starting point.

    To run the tests:

    composer test:phpunit
    

    Fixes #54

    opened by johnbillion 13
  • Any good solution for WP array shapes not being recognized by PHPStan?

    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.
    }
    
    opened by mundschenk-at 12
  • $wpdb->last_error type should be un-narrowed after running $wpdb->query()

    $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.

    opened by ZebulanStanphill 12
  • Treat assertNotWPError() and assertWPError() as type-narrowing functions

    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.
    
    opened by johnbillion 12
  • How to deal with mixed types from WordPress core?

    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(),
    );
    
    opened by claytoncollie 12
  • Use relative path in config file

    Use relative path in config file

    By dropping `%rootDir%` variable, including extension isn't strictly tied to path of PHPStan executable.
    

    I would like to request a change in config file for this extension.

    In my case, I would like to use PHPStan through phar file located in tools directory (default for installation from phive). Actually, it's possible, if I don't use phpstan/extensions-installer and rely on explicit include in my config file. (This also applies to globally installed phpstan executable).

    Relying on relative path searches for wordpress-stubs package from current working directory instead of executable location, which is desired in aforementioned cases.

    opened by bart-jaskulski 11
  • Does not work with globally installed phpstan

    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.
    
    opened by flip111 11
  • How long should PHPStan take?

    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?

    opened by jdamner 2
  • Add dynamic return type extension for `WP_Term_Query()`

    Add dynamic return type extension for `WP_Term_Query()`

    Same as GetTermsDynamicFunctionReturnTypeExtension, but just for WP_Term_Query()

    See also #48 for WP_Query

    Logic is as follows:

    https://github.com/WordPress/wordpress-develop/blob/bdc2084b30fd842fe862f656aafd1a7c46c53317/src/wp-includes/class-wp-term-query.php#L314-L339

    opened by swissspidy 0
  • Add type checking for hook callbacks in `add_action` and `add_filter`

    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)
    opened by johnbillion 8
  • Add a return type extension for `WP_Query->query()` and `WP_Query->get_posts()`

    Add a return type extension for `WP_Query->query()` and `WP_Query->get_posts()`

    Return type extension for WP_Query->query() and WP_Query->get_posts().

    As get_posts() ultimately calls WP_Query->query() and WP_Query->get_posts(), they all share the same return type pattern. Not sure if you want to combine them somehow.

    opened by johnbillion 0
  • have_posts() is an impure function!

    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()
        {
        }
    }
    
    opened by szepeviktor 9
Releases(v1.1.6)
Owner
Viktor Szépe
I make your #web application's life happy 📡 Consulting and 24/7 Operations 🏡 I'm on GitHub to blow off the steam by doing high quality work.
Viktor Szépe
An extension for PHPStan for adding analysis for PHP Language Extensions.

PHPStan PHP Language Extensions (currently in BETA) This is an extension for PHPStan for adding analysis for PHP Language Extensions. Language feature

Dave Liddament 9 Nov 30, 2022
PHPStan extension to support #[Readonly] constructor properties

icanhazstring/phpstan-readonly-property Support #[Readonly] promoted constructor properties for PHPStan. This library is used to have a full transitio

Andreas Frömer 4 Apr 5, 2022
Analyzes PHPStan baseline files and creates aggregated error trend-reports

Analyzes phpstan baseline files Analyzes PHPStan baseline files and creates aggregated error trend-reports.

Markus Staab 22 Dec 23, 2022
Magento specific extension for phpstan

bitexpert/phpstan-magento This package provides some additional features for PHPStan to make it work for Magento 2 projects. Installation The preferre

bitExpert AG 92 Dec 7, 2022
Sandbox project for the PHPStan workshop

Sandbox project for a PHPStan workshop Installation Requirements Docker Engine Docker Compose Git Bash Getting started Clone this repository (git clon

Matthias Noback 4 Oct 17, 2022
A PHPStan package that supports working with Extbase

PHPStan for Extbase This package provides a couple of stubs and services to make your life easier when working with PHPStan and Extbase. Examples clas

Alexander Schnitzler 7 Dec 10, 2021
Custom PHPStan rules

phpstan-rules Provides additional rules for phpstan/phpstan. Installation Run $ composer require --dev alister/phpstan-rules Usage All of the rules pr

Alister Bulman 1 Nov 4, 2021
The main scope of this extension is to help phpstan to detect the type of object after the Assert\Assertion validation.

PHPStan beberlei/assert extension PHPStan beberlei/assert Description The main scope of this extension is to help phpstan to detect the type of object

PHPStan 33 Jan 2, 2023
PHPStan extension for webmozart/assert

PHPStan webmozart/assert extension PHPStan webmozart/assert Description The main scope of this extension is to help phpstan to detect the type of obje

PHPStan 139 Dec 22, 2022
Various PHPStan rules we found useful in ShipMonk.

ShipMonk PHPStan rules Various rules we found useful in ShipMonk. You may found some of them opinionated, so we recommend picking only those fitting y

ShipMonk R&D 31 Dec 22, 2022
PHPStan extension for sealed classes and interfaces.

Sealed classes with PHPStan This extension adds support for sealed classes and interfaces to PHPStan. Installation To use this extension, require it v

Jiří Pudil 14 Nov 28, 2022
Extension for PHPStan to allow analysis of Drupal code.

phpstan-drupal Extension for PHPStan to allow analysis of Drupal code. Sponsors Would you like to sponsor? Usage When you are using phpstan/extension-

Matt Glaman 154 Jan 2, 2023
Repository containing all the PHPStan rules from the book "Recipes for Decoupling"

PHPStan rules from the book "Recipes for Decoupling" by Matthias Noback In the book "Recipes for Decoupling" we discuss how to decouple from web and C

Matthias Noback 19 Sep 21, 2022
Perform static analysis of Drupal PHP code with phpstan-drupal.

Perform static analysis of Drupal PHP code with PHPStan and PHPStan-Drupal on Drupal using PHP 8. For example: docker run --rm \ -v $(pwd)/example01

Dcycle 0 Dec 10, 2021
Helper to automatically load various Kirby extensions in a plugin

Autoloader for Kirby Helper to automatically load various Kirby extensions in a plugin Commerical Usage This package is free but if you use it in a co

Bruno Meilick 13 Nov 9, 2022
Zephir is a compiled high level language aimed to the creation of C-extensions for PHP.

Zephir - is a high level programming language that eases the creation and maintainability of extensions for PHP. Zephir extensions are exported to C c

Zephir Language 3.2k Jan 2, 2023
GitHub action to set up PHP with extensions, php.ini configuration, coverage drivers, and various tools.

GitHub action to set up PHP with extensions, php.ini configuration, coverage drivers, and various tools.

Shivam Mathur 2.4k Jan 6, 2023
Custom code snippets and examples for SkyVerge-built WooCommerce extensions

SkyVerge WooCommerce Plugins Welcome to the wc-plugins-snippets repository! This repository stores code snippets related to SkyVerge WooCommerce plugi

SkyVerge 255 Nov 16, 2022
Library to build PHP extensions with C++

PHP-CPP The PHP-CPP library is a C++ library for developing PHP extensions. It offers a collection of well documented and easy-to-use classes that can

Copernica 1.3k Dec 24, 2022