Make WordPress Core

Changeset 64435


Ignore:
Timestamp:
10/16/2026 11:46:27 PM (25 hours ago)
Author:
westonruter
Message:

Upgrade/Install: Add PHPStan types to the automatic updater.

The update offers and results handled by WP_Automatic_Updater are now typed by update type, with a type alias for each kind of offer and result and a conditional return type for WP_Automatic_Updater::update(). A conditional parameter type for the offer would be preferable, but PHPStan only narrows the outermost condition of a nested conditional parameter type and resolves the others to never, so the offer is instead narrowed with inline tags in each branch that reads it, alongside comments to replace them once nested conditions are supported.

Typing the results showed that several documented return types never matched the code. The null|WP_Error documented for update() since r32655 omitted both the false returned when an update is declined and the result passed through from the upgrader. Because Plugin_Upgrader, Theme_Upgrader and Language_Pack_Upgrader redeclare $result without a default, their upgrade() methods and the plugin and theme install() methods return null when the operation fails before the package is installed, which their documentation has omitted since r30758. The $update parameter of Language_Pack_Upgrader::upgrade() is also corrected to the update offer object that its callers pass.

Variables that PHPStan reported as possibly undefined are initialized, and a redundant $is_debug assignment from r58139 is removed. This resolves 7 baselined errors across the argument.type, return.type and variable.undefined identifiers.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/14361.
Follow-up to r11005, r25566, r30758, r32655, r58139, r63019, r63022, r63024.

See #65817.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-language-pack-upgrader.php

    r61455 r64435  
    2323         *
    2424         * @since 3.7.0
    25          * @var array|WP_Error $result
     25         * @var array|WP_Error|null $result
     26         * @phpstan-var non-empty-array<string, mixed>|WP_Error|null
    2627         * @see WP_Upgrader::$result
    2728         */
    … …  
    128129         * @since 3.7.0
    129130         *
    130          * @param string|false $update Optional. Whether an update offer is available. Default false.
     131         * @param object|false $update Optional. The language pack update offer, or false to upgrade all
     132         *                             available language packs. Default false.
    131133         * @param array        $args   Optional. Other optional arguments, see
    132134         *                             Language_Pack_Upgrader::bulk_upgrade(). Default empty array.
    133          * @return array|bool|WP_Error The result of the upgrade, or a WP_Error object instead.
     135         * @return array|bool|WP_Error|null The result of the upgrade, or a WP_Error object instead. Null if the
     136         *                                  upgrade failed before the package was installed, for example when
     137         *                                  the package could not be downloaded.
    134138         */
    135139        public function upgrade( $update = false, $args = array() ) {
  • trunk/src/wp-admin/includes/class-plugin-upgrader.php

    r63788 r64435  
    2525         *
    2626         * @since 2.8.0
    27          * @var array|WP_Error $result
     27         * @var array|WP_Error|null $result
     28         * @phpstan-var non-empty-array<string, mixed>|WP_Error|null
    2829         *
    2930         * @see WP_Upgrader::$result
    … …  
    114115         *                                    Default true.
    115116         * }
    116          * @return bool|WP_Error True if the installation was successful, false or a WP_Error otherwise.
     117         * @return bool|WP_Error|null True if the installation was successful, false or a WP_Error otherwise.
     118         *                            Null if the installation failed before the package was installed, for
     119         *                            example when the package could not be downloaded.
    117120         */
    118121        public function install( $package, $args = array() ) {
    … …  
    186189         *                                    Default true.
    187190         * }
    188          * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
     191         * @return bool|WP_Error|null True if the upgrade was successful, false or a WP_Error object otherwise.
     192         *                            Null if the upgrade failed before the package was installed, for example
     193         *                            when the package could not be downloaded.
    189194         */
    190195        public function upgrade( $plugin, $args = array() ) {
  • trunk/src/wp-admin/includes/class-theme-upgrader.php

    r63788 r64435  
    2525         *
    2626         * @since 2.8.0
    27          * @var array|WP_Error $result
     27         * @var array|WP_Error|null $result
     28         * @phpstan-var non-empty-array<string, mixed>|WP_Error|null
    2829         * @see WP_Upgrader::$result
    2930         */
    … …  
    121122         * @param array $child_result
    122123         * @return bool
     124         *
     125         * @phpstan-param non-empty-array<string, mixed> $child_result
    123126         */
    124127        public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
    … …  
    226229         * }
    227230         *
    228          * @return bool|WP_Error True if the installation was successful, false or a WP_Error object otherwise.
     231         * @return bool|WP_Error|null True if the installation was successful, false or a WP_Error object otherwise.
     232         *                            Null if the installation failed before the package was installed, for
     233         *                            example when the package could not be downloaded.
    229234         */
    230235        public function install( $package, $args = array() ) {
    … …  
    291296         *                                    Default true.
    292297         * }
    293          * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
     298         * @return bool|WP_Error|null True if the upgrade was successful, false or a WP_Error object otherwise.
     299         *                            Null if the upgrade failed before the package was installed, for example
     300         *                            when the package could not be downloaded.
    294301         */
    295302        public function upgrade( $theme, $args = array() ) {
  • trunk/src/wp-admin/includes/class-wp-automatic-updater.php

    r63494 r64435  
    1313 * @since 3.7.0
    1414 * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
     15 *
     16 * @phpstan-type Core_Update_Offer object{
     17 *     current: string,
     18 *     version: string,
     19 *     php_version: string,
     20 *     mysql_version: string,
     21 *     new_files?: bool|string,
     22 *     notify_email?: bool|string,
     23 *     autoupdate?: bool,
     24 *     disable_autoupdate?: bool,
     25 * }&stdClass
     26 * @phpstan-type Plugin_Update_Offer object{
     27 *     slug: string,
     28 *     plugin: string,
     29 *     new_version: string,
     30 *     current_version?: string|false,
     31 *     requires_php?: string|false|null,
     32 *     autoupdate?: bool,
     33 *     disable_autoupdate?: bool,
     34 * }&stdClass
     35 * @phpstan-type Theme_Update_Offer object{
     36 *     theme: string,
     37 *     new_version: string,
     38 *     current_version?: string|false,
     39 *     requires_php?: string|false|null,
     40 *     autoupdate?: bool,
     41 *     disable_autoupdate?: bool,
     42 * }&stdClass
     43 * @phpstan-type Translation_Update_Offer object{
     44 *     language: string,
     45 *     autoupdate?: bool,
     46 *     disable_autoupdate?: bool,
     47 * }&stdClass
     48 * @phpstan-type Core_Update_Result object{
     49 *     item: Core_Update_Offer,
     50 *     result: string|WP_Error,
     51 *     name: string,
     52 *     messages: string[],
     53 * }&stdClass
     54 * @phpstan-type Plugin_Update_Result object{
     55 *     item: Plugin_Update_Offer,
     56 *     result: true|WP_Error|null,
     57 *     name: string,
     58 *     messages: string[],
     59 * }&stdClass
     60 * @phpstan-type Theme_Update_Result object{
     61 *     item: Theme_Update_Offer,
     62 *     result: true|WP_Error|null,
     63 *     name: string|false,
     64 *     messages: string[],
     65 * }&stdClass
     66 * @phpstan-type Translation_Update_Result object{
     67 *     item: Translation_Update_Offer,
     68 *     result: array<mixed>|true|WP_Error|null,
     69 *     name: string,
     70 *     messages: string[],
     71 * }&stdClass
    1572 */
    1673#[AllowDynamicProperties]
    … …  
    2077         * Tracks update results during processing.
    2178         *
    22          * @var array
     79         * @var array<string, object[]>
     80         * @phpstan-var array{
     81         *     core?: list<Core_Update_Result>,
     82         *     plugin?: list<Plugin_Update_Result>,
     83         *     theme?: list<Theme_Update_Result>,
     84         *     translation?: list<Translation_Update_Result>,
     85         * }
    2386         */
    2487        protected $update_results = array();
    … …  
    192255         *                        access and status should be checked.
    193256         * @return bool True if the item should be updated, false otherwise.
     257         *
     258         * @phpstan-param 'core'|'theme'|'plugin'|'translation' $type
     259         * @phpstan-param (Core_Update_Offer|Plugin_Update_Offer|Theme_Update_Offer|Translation_Update_Offer) $item
    194260         */
    195261        public function should_update( $type, $item, $context ) {
    … …  
    217283                }
    218284
    219                 // Next up, is this an item we can update?
     285                /*
     286                 * Next up, is this an item we can update?
     287                 *
     288                 * The inline @phpstan-var tags narrow $item by $type. This should be replaced with a conditional
     289                 * parameter type once PHPStan supports narrowing nested conditional parameter types.
     290                 */
    220291                if ( 'core' === $type ) {
     292                        /** @phpstan-var Core_Update_Offer $item */
    221293                        $update = Core_Upgrader::should_update_to_version( $item->current );
    222294                } elseif ( 'plugin' === $type || 'theme' === $type ) {
     295                        /** @phpstan-var (Plugin_Update_Offer|Theme_Update_Offer) $item */
    223296                        $update = ! empty( $item->autoupdate );
    224297
    … …  
    279352                        global $wpdb;
    280353
     354                        /** @phpstan-var Core_Update_Offer $item */
    281355                        $php_compat = version_compare( PHP_VERSION, $item->php_version, '>=' );
    282356                        if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) ) {
    … …  
    293367                // If updating a plugin or theme, ensure the minimum PHP version requirements are satisfied.
    294368                if ( in_array( $type, array( 'plugin', 'theme' ), true ) ) {
     369                        /** @phpstan-var (Plugin_Update_Offer|Theme_Update_Offer) $item */
    295370                        if ( ! empty( $item->requires_php ) && version_compare( PHP_VERSION, $item->requires_php, '<' ) ) {
    296371                                return false;
    … …  
    358433         * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
    359434         * @param object $item The update offer.
    360          * @return null|WP_Error
     435         * @return WP_Error|bool|string|array|null False if the item should not be updated, otherwise the result
     436         *                                         from the upgrader: the new WordPress version for core, true for
     437         *                                         a successful plugin or theme update, the installation result for
     438         *                                         a translation, or a WP_Error on failure. Null for a plugin, theme,
     439         *                                         or translation if the update failed before the package was
     440         *                                         installed.
     441         *
     442         * @phpstan-param 'core'|'theme'|'plugin'|'translation' $type
     443         * @phpstan-param (Core_Update_Offer|Plugin_Update_Offer|Theme_Update_Offer|Translation_Update_Offer) $item
     444         * @phpstan-return (
     445         *     $type is 'core' ? string|false|WP_Error : (
     446         *         $type is 'translation' ? array<mixed>|bool|WP_Error|null : bool|WP_Error|null
     447         *     )
     448         * )
    361449         */
    362450        public function update( $type, $item ) {
    363451                $skin = new Automatic_Upgrader_Skin();
    364452
     453                /*
     454                 * The inline @phpstan-var tags narrow $item by $type. This should be replaced with a conditional
     455                 * parameter type once PHPStan supports narrowing nested conditional parameter types.
     456                 */
     457                $upgrader = null;
     458                $context  = null;
    365459                switch ( $type ) {
    366460                        case 'core':
     461                                /** @phpstan-var Core_Update_Offer $item */
    367462                                // The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
    368463                                add_filter( 'update_feedback', array( $skin, 'feedback' ) );
    … …  
    371466                                break;
    372467                        case 'plugin':
     468                                /** @phpstan-var Plugin_Update_Offer $item */
    373469                                $upgrader = new Plugin_Upgrader( $skin );
    374470                                $context  = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR.
    375471                                break;
    376472                        case 'theme':
     473                                /** @phpstan-var Theme_Update_Offer $item */
    377474                                $upgrader = new Theme_Upgrader( $skin );
    378475                                $context  = get_theme_root( $item->theme );
    379476                                break;
    380477                        case 'translation':
     478                                /** @phpstan-var Translation_Update_Offer $item */
    381479                                $upgrader = new Language_Pack_Upgrader( $skin );
    382480                                $context  = WP_CONTENT_DIR; // WP_LANG_DIR;
    … …  
    401499                do_action( 'pre_auto_update', $type, $item, $context );
    402500
     501                $item_name     = null;
    403502                $upgrader_item = $item;
    404503                switch ( $type ) {
    405504                        case 'core':
     505                                /** @phpstan-var Core_Update_Offer $item */
    406506                                /* translators: %s: WordPress version. */
    407507                                $skin->feedback( __( 'Updating to WordPress %s' ), $item->version );
    … …  
    410510                                break;
    411511                        case 'theme':
     512                                /** @phpstan-var Theme_Update_Offer $item */
    412513                                $upgrader_item = $item->theme;
    413514                                $theme         = wp_get_theme( $upgrader_item );
    … …  
    422523                                break;
    423524                        case 'plugin':
     525                                /** @phpstan-var Plugin_Update_Offer $item */
    424526                                $upgrader_item = $item->plugin;
    425527                                $plugin_data   = get_plugin_data( $context . '/' . $upgrader_item );
    … …  
    434536                                break;
    435537                        case 'translation':
     538                                /** @phpstan-var Translation_Update_Offer $item */
     539                                /** @var Language_Pack_Upgrader $upgrader */
    436540                                $language_item_name = $upgrader->get_name_for_update( $item );
    437541                                /* translators: %s: Project name (plugin, theme, or WordPress). */
    … …  
    447551                }
    448552
    449                 $is_debug = WP_DEBUG && WP_DEBUG_LOG;
     553                $was_active = null;
     554                $is_debug   = WP_DEBUG && WP_DEBUG_LOG;
    450555                if ( 'plugin' === $type ) {
     556                        /** @phpstan-var Plugin_Update_Offer $item */
     557                        /** @var string $upgrader_item */
    451558                        $was_active = is_plugin_active( $upgrader_item );
    452559                        if ( $is_debug ) {
    … …  
    456563
    457564                if ( 'theme' === $type && $is_debug ) {
     565                        /** @phpstan-var Theme_Update_Offer $item */
    458566                        error_log( '    Upgrading theme ' . var_export( $item->theme, true ) . '...' );
    459567                }
    … …  
    531639                }
    532640
    533                 $is_debug = WP_DEBUG && WP_DEBUG_LOG;
    534 
    535641                if ( 'theme' === $type && $is_debug ) {
     642                        /** @phpstan-var Theme_Update_Offer $item */
    536643                        error_log( '    Theme ' . var_export( $item->theme, true ) . ' has been upgraded.' );
    537644                }
    538645
    539646                if ( 'plugin' === $type ) {
     647                        /** @phpstan-var Plugin_Update_Offer $item */
     648                        /** @var string $upgrader_item */
    540649                        if ( $is_debug ) {
    541650                                error_log( '    Plugin ' . var_export( $item->slug, true ) . ' has been upgraded.' );
  • trunk/src/wp-admin/includes/class-wp-upgrader.php

    r62532 r64435  
    7474         * successfully. It will then be an array, unless a WP_Error is returned by the
    7575         * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to
    76          * it.
     76         * it. Until then, it is an empty array, or null in subclasses which redeclare it.
    7777         *
    7878         * @since 2.8.0
    7979         *
    80          * @var array|WP_Error $result {
     80         * @var array|WP_Error|null $result {
    8181         *     @type string $source             The full path to the source the files were installed from.
    8282         *     @type string $source_files       List of all the files in the source directory.
  • trunk/tests/phpstan/baselines/argument.type.neon

    r64311 r64435  
    100100                        path: ../../../src/wp-admin/includes/class-custom-image-header.php
    101101                -
    102                         message: '#^Parameter \#1 \$language_updates of method Language_Pack_Upgrader\:\:bulk_upgrade\(\) expects array\<object\>, list\<string\>\|string\|false given\.$#'
    103                         identifier: argument.type
    104                         count: 1
    105                         path: ../../../src/wp-admin/includes/class-language-pack-upgrader.php
    106                 -
    107102                        message: '#^Parameter \#1 \$str of function md5 expects string, int given\.$#'
    108103                        identifier: argument.type
    … …  
    305300                        path: ../../../src/wp-admin/includes/template.php
    306301                -
    307                         message: '#^Parameter \#1 \$update of method Language_Pack_Upgrader\:\:upgrade\(\) expects string\|false, object\{language\: string, version\: string, updated\: string, english_name\: string, native_name\: string, package\: string, iso\: array\<string\>, strings\: array\}&stdClass given\.$#'
    308                         identifier: argument.type
    309                         count: 1
    310                         path: ../../../src/wp-admin/includes/translation-install.php
    311                 -
    312302                        message: '#^Parameter \#3 \$overwrite of method WP_Filesystem_Base\:\:copy\(\) expects bool, int given\.$#'
    313303                        identifier: argument.type
  • trunk/tests/phpstan/baselines/return.type.neon

    r63789 r64435  
    1919parameters:
    2020        ignoreErrors:
    21                 -
    22                         message: '#^Method WP_Automatic_Updater\:\:update\(\) should return WP_Error\|null but returns false\.$#'
    23                         identifier: return.type
    24                         count: 2
    25                         path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php
    2621                -
    2722                        message: '#^Function convert_to_screen\(\) should return WP_Screen but returns object\{id\: string, base\: string\}&stdClass\.$#'
  • trunk/tests/phpstan/baselines/variable.undefined.neon

    r64402 r64435  
    160160                        path: ../../../src/wp-admin/includes/class-core-upgrader.php
    161161                -
    162                         message: '#^Variable \$context might not be defined\.$#'
    163                         identifier: variable.undefined
    164                         count: 3
    165                         path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php
    166                 -
    167                         message: '#^Variable \$item_name might not be defined\.$#'
    168                         identifier: variable.undefined
    169                         count: 1
    170                         path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php
    171                 -
    172162                        message: '#^Variable \$subject might not be defined\.$#'
    173                         identifier: variable.undefined
    174                         count: 1
    175                         path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php
    176                 -
    177                         message: '#^Variable \$upgrader might not be defined\.$#'
    178                         identifier: variable.undefined
    179                         count: 8
    180                         path: ../../../src/wp-admin/includes/class-wp-automatic-updater.php
    181                 -
    182                         message: '#^Variable \$was_active might not be defined\.$#'
    183163                        identifier: variable.undefined
    184164                        count: 1
Note: See TracChangeset for help on using the changeset viewer.