From b917e45c8c489278a8b2ae808ffee9dc80f3ba39 Mon Sep 17 00:00:00 2001 From: wpdev Date: Sat, 26 Sep 2026 07:45:05 +0800 Subject: [PATCH] chore(2meet-data-optimizer): make the hardening branch pass PHPStan and PHPCS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CI PHPStan (level 6, baseline for pre-existing debt) failed on 35 errors in code added on hardening/2.0: missing iterable value types (PHPDoc only), a redundant method_exists() in uninstall.php, and the new WP-CLI commands registered as array('Class', 'instanceMethod') — now array(new Class(), ...) - Local runs had been masked by a PHPStan tmpDir shared with the act CI runner (cached container pointing at a removed phar); use a private tmpDir - Verify: phpstan (private tmpDir) no errors, phpcs clean on changed files, phpunit Unit 658 OK; dev22 (running this branch) CLI post-status-sync / bridge-restore-native dry runs OK --- cli/class-tmdo-cli-post.php | 14 +++++++------- cli/class-tmdo-cli-v2.php | 7 +++++-- includes/diagnostic/class-tmdo-site-health.php | 6 +++--- includes/engine/class-tmdo-entity-registry.php | 16 +++++++++++----- includes/engine/class-tmdo-hook-bus.php | 1 + .../migration/class-tmdo-native-restorer.php | 18 +++++++++--------- .../class-tmdo-post-group-reconciler.php | 14 +++++++------- .../query/class-tmdo-post-query-router.php | 4 ++-- uninstall.php | 2 +- 9 files changed, 46 insertions(+), 36 deletions(-) diff --git a/cli/class-tmdo-cli-post.php b/cli/class-tmdo-cli-post.php index 75206fe..ac9382f 100644 --- a/cli/class-tmdo-cli-post.php +++ b/cli/class-tmdo-cli-post.php @@ -282,8 +282,8 @@ class TMDO_CLI_Post { * wp tmdo post-reconcile-groups --dry-run * wp tmdo post-reconcile-groups * - * @param array $args Positional arguments (unused). - * @param array $assoc_args Named arguments. + * @param array $args Positional arguments (unused). + * @param array $assoc_args Named arguments. */ public function post_reconcile_groups( $args, $assoc_args ): void { $dry_run = isset( $assoc_args['dry-run'] ); @@ -327,8 +327,8 @@ class TMDO_CLI_Post { * wp tmdo post-status-sync --dry-run * wp tmdo post-status-sync * - * @param array $args Positional arguments (unused). - * @param array $assoc_args Named arguments. + * @param array $args Positional arguments (unused). + * @param array $assoc_args Named arguments. */ public function post_status_sync( $args, $assoc_args ): void { $dry_run = isset( $assoc_args['dry-run'] ); @@ -925,6 +925,6 @@ WP_CLI::add_command( 'wpdo post-benchmark', array( 'TMDO_CLI_Post', 'post_benchm WP_CLI::add_command( 'tmdo post-benchmark', array( 'TMDO_CLI_Post', 'post_benchmark' ) ); WP_CLI::add_command( 'wpdo post-shadow-report', array( 'TMDO_CLI_Post', 'post_shadow_report' ) ); WP_CLI::add_command( 'tmdo post-shadow-report', array( 'TMDO_CLI_Post', 'post_shadow_report' ) ); -WP_CLI::add_command( 'wpdo post-reconcile-groups', array( 'TMDO_CLI_Post', 'post_reconcile_groups' ) ); -WP_CLI::add_command( 'tmdo post-reconcile-groups', array( 'TMDO_CLI_Post', 'post_reconcile_groups' ) ); -WP_CLI::add_command( 'tmdo post-status-sync', array( 'TMDO_CLI_Post', 'post_status_sync' ) ); +WP_CLI::add_command( 'wpdo post-reconcile-groups', array( new TMDO_CLI_Post(), 'post_reconcile_groups' ) ); +WP_CLI::add_command( 'tmdo post-reconcile-groups', array( new TMDO_CLI_Post(), 'post_reconcile_groups' ) ); +WP_CLI::add_command( 'tmdo post-status-sync', array( new TMDO_CLI_Post(), 'post_status_sync' ) ); diff --git a/cli/class-tmdo-cli-v2.php b/cli/class-tmdo-cli-v2.php index 7a374cc..b3d76f2 100644 --- a/cli/class-tmdo-cli-v2.php +++ b/cli/class-tmdo-cli-v2.php @@ -183,6 +183,9 @@ class TMDO_CLI_V2 { * wp tmdo bridge-restore-native user --dry-run * wp tmdo bridge-restore-native user --demote * wp tmdo bridge-restore-native user --group=sso --retire + * + * @param array $args Positional arguments. + * @param array $assoc_args Named arguments. */ public function bridge_restore_native( $args, $assoc_args ): void { $entity = (string) ( $args[0] ?? '' ); @@ -846,8 +849,8 @@ WP_CLI::add_command( 'wpdo bridge-status', array( 'TMDO_CLI_V2', 'bridge_status' WP_CLI::add_command( 'tmdo bridge-status', array( 'TMDO_CLI_V2', 'bridge_status' ) ); WP_CLI::add_command( 'wpdo bridge-set', array( 'TMDO_CLI_V2', 'bridge_set' ) ); WP_CLI::add_command( 'tmdo bridge-set', array( 'TMDO_CLI_V2', 'bridge_set' ) ); -WP_CLI::add_command( 'wpdo bridge-restore-native', array( 'TMDO_CLI_V2', 'bridge_restore_native' ) ); -WP_CLI::add_command( 'tmdo bridge-restore-native', array( 'TMDO_CLI_V2', 'bridge_restore_native' ) ); +WP_CLI::add_command( 'wpdo bridge-restore-native', array( new TMDO_CLI_V2(), 'bridge_restore_native' ) ); +WP_CLI::add_command( 'tmdo bridge-restore-native', array( new TMDO_CLI_V2(), 'bridge_restore_native' ) ); WP_CLI::add_command( 'wpdo mode-audit', array( 'TMDO_CLI_V2', 'mode_audit' ) ); WP_CLI::add_command( 'tmdo mode-audit', array( 'TMDO_CLI_V2', 'mode_audit' ) ); WP_CLI::add_command( 'wpdo mode-set', array( 'TMDO_CLI_V2', 'mode_set' ) ); diff --git a/includes/diagnostic/class-tmdo-site-health.php b/includes/diagnostic/class-tmdo-site-health.php index 053d784..87cfc32 100644 --- a/includes/diagnostic/class-tmdo-site-health.php +++ b/includes/diagnostic/class-tmdo-site-health.php @@ -442,7 +442,7 @@ class TMDO_Site_Health { * Not cached: the collision list is in-memory state rebuilt on every * request, so reading it is free and a transient would only go stale. * - * @return array Site Health result. + * @return array Site Health result. */ public static function check_entity_group_collisions(): array { $collisions = class_exists( 'TMDO_Entity_Registry' ) ? TMDO_Entity_Registry::get_collisions() : array(); @@ -482,7 +482,7 @@ class TMDO_Site_Health { * * Not cached for the same reason as check_entity_group_collisions(). * - * @return array Site Health result. + * @return array Site Health result. */ public static function check_orphaned_entity_groups(): array { $restored = class_exists( 'TMDO_Entity_Registry' ) ? TMDO_Entity_Registry::get_restored_groups() : array(); @@ -521,7 +521,7 @@ class TMDO_Site_Health { * * Not cached: two class_exists() probes. * - * @return array Site Health result. + * @return array Site Health result. */ public static function check_missing_integration_addons(): array { $missing = class_exists( 'TMDO_Compatibility' ) ? TMDO_Compatibility::missing_integration_addons() : array(); diff --git a/includes/engine/class-tmdo-entity-registry.php b/includes/engine/class-tmdo-entity-registry.php index 51f258d..6e4cdfe 100644 --- a/includes/engine/class-tmdo-entity-registry.php +++ b/includes/engine/class-tmdo-entity-registry.php @@ -31,10 +31,10 @@ final class TMDO_Entity_Registry { /** @var array 待建表的 Schema 清單 */ private static array $pending_schemas = array(); - /** @var array> 實體類型 → meta_key → 欄位定義(僅未限定 post_type 的群組)*/ + /** @var array>> 實體類型 → meta_key → 欄位定義(僅未限定 post_type 的群組)*/ private static array $unscoped_index = array(); - /** @var array>> 實體類型 → post_type → meta_key → 欄位定義 */ + /** @var array>>> 實體類型 → post_type → meta_key → 欄位定義 */ private static array $scoped_index = array(); /** @var array> 實體類型 → 群組名 → 限定的 post_type */ @@ -43,10 +43,10 @@ final class TMDO_Entity_Registry { /** @var array>> 實體類型 → 群組名 → 索引名 → 欄位 key */ private static array $indexes = array(); - /** @var array "entity/group" → 同名群組以不同定義重複註冊的紀錄 */ + /** @var array> "entity/group" → 同名群組以不同定義重複註冊的紀錄 */ private static array $collisions = array(); - /** @var array "entity/group" → 從 wpdo_registry_meta 還原的孤兒群組 */ + /** @var array> "entity/group" → 從 wpdo_registry_meta 還原的孤兒群組 */ private static array $restored = array(); /** 合法的欄位型別 */ @@ -119,7 +119,7 @@ final class TMDO_Entity_Registry { * - unique (bool) 唯一索引 * - options (array) enum 選項 * - label (string) 顯示名稱 - * @param array $args 選填:post_types (string[]) — 群組只套用於這些 post_type。 + * @param array $args 選填:post_types (string[]) — 群組只套用於這些 post_type。 * 多個群組共用 meta_key 時,依 post 本身的類型決定歸屬; * 不屬於任何宣告 post_type 的 post 不受管理(走原生 meta)。 * 不列入欄位定義,所以不影響 schema hash 與衝突比對。 @@ -183,6 +183,8 @@ final class TMDO_Entity_Registry { /** * 驗證並正規化欄位定義(略過缺 key/type 或型別不合法者)。 + * @param array> $fields + * @return array> */ private static function normalize_fields( string $entity_type, string $group_name, array $fields ): array { $normalized = array(); @@ -229,6 +231,7 @@ final class TMDO_Entity_Registry { * * @param string[] $native_keys 原生 meta 仍有資料、不可還原的 key。 * @return bool 有登錄任何欄位時為 true + * @param array> $fields */ public static function restore_group( string $entity_type, string $group_name, array $fields, array $native_keys = array() ): bool { if ( ! isset( self::$adapters[ $entity_type ] ) || isset( self::$groups[ $entity_type ][ $group_name ] ) ) { @@ -286,6 +289,7 @@ final class TMDO_Entity_Registry { * 供不帶 post_type 的查詢使用;另外依是否限定 post_type 分別建索引。 * * @param string[] $post_types 群組限定的 post_type;空陣列表示不限定。 + * @param array $field */ private static function index_field( string $entity_type, array $field, array $post_types ): void { self::$field_index[ $entity_type ][ $field['key'] ] = $field; @@ -312,6 +316,8 @@ final class TMDO_Entity_Registry { * 記錄同名群組衝突(每個 entity/group 每請求一筆),並寫入 warning。 * * 註冊每個請求都會跑,warning 以 transient 節流為每天一次,避免灌爆 wpdo_errors。 + * @param array> $existing + * @param array> $incoming */ private static function record_collision( string $entity_type, string $group_name, array $existing, array $incoming ): void { $id = $entity_type . '/' . $group_name; diff --git a/includes/engine/class-tmdo-hook-bus.php b/includes/engine/class-tmdo-hook-bus.php index 91744f6..505822b 100644 --- a/includes/engine/class-tmdo-hook-bus.php +++ b/includes/engine/class-tmdo-hook-bus.php @@ -648,6 +648,7 @@ final class TMDO_Hook_Bus { * 欄位定義查詢;post meta 依該 post 本身的 post_type 解析(見 TMDO_Entity_Registry::get_field())。 * * 先做不帶 post_type 的查詢:大多數 key 不受管理,可省下 get_post_type()。 + * @return array|null */ private static function field_for( string $type, int $object_id, string $meta_key ): ?array { $field_def = TMDO_Entity_Registry::get_field( $type, $meta_key ); diff --git a/includes/migration/class-tmdo-native-restorer.php b/includes/migration/class-tmdo-native-restorer.php index cf97d42..0fa3cf8 100644 --- a/includes/migration/class-tmdo-native-restorer.php +++ b/includes/migration/class-tmdo-native-restorer.php @@ -218,11 +218,11 @@ final class TMDO_Native_Restorer { /** * Flat rows matching $where; post rows carry their post type as __post_type. * - * @param string $entity_type Entity type. - * @param string $table Flat table. - * @param string $id_col Entity id column. - * @param string $where WHERE clause with placeholders. - * @param array $params Placeholder values. + * @param string $entity_type Entity type. + * @param string $table Flat table. + * @param string $id_col Entity id column. + * @param string $where WHERE clause with placeholders. + * @param array $params Placeholder values. * @return array> */ private static function fetch( string $entity_type, string $table, string $id_col, string $where, array $params ): array { @@ -237,10 +237,10 @@ final class TMDO_Native_Restorer { /** * Replace the native meta of the keys this group owns for each row. * - * @param string $entity_type Entity type. - * @param string $group Group the rows come from. - * @param TMDO_Entity_Adapter_Interface $adapter Entity adapter. - * @param array $rows Flat rows. + * @param string $entity_type Entity type. + * @param string $group Group the rows come from. + * @param TMDO_Entity_Adapter_Interface $adapter Entity adapter. + * @param array> $rows Flat rows. * @return int Native values written. */ private static function write_rows( string $entity_type, string $group, TMDO_Entity_Adapter_Interface $adapter, array $rows ): int { diff --git a/includes/migration/class-tmdo-post-group-reconciler.php b/includes/migration/class-tmdo-post-group-reconciler.php index 4f4655b..32dbadc 100644 --- a/includes/migration/class-tmdo-post-group-reconciler.php +++ b/includes/migration/class-tmdo-post-group-reconciler.php @@ -72,7 +72,7 @@ final class TMDO_Post_Group_Reconciler { * * @param bool $dry_run Count only; write nothing. * @param int $batch Rows per batch. - * @return array{moves: array, enabled: bool} + * @return array{moves: array>, enabled: bool} */ public static function run( bool $dry_run = false, int $batch = 500 ): array { global $wpdb; @@ -165,9 +165,9 @@ final class TMDO_Post_Group_Reconciler { /** * FROM/WHERE for non-NULL legacy values of the move's post types. * - * @param array $move One entry of plan(). - * @param string $id_col Entity id column of the flat tables. - * @return array{0:string, 1:array} + * @param array $move One entry of plan(). + * @param string $id_col Entity id column of the flat tables. + * @return array{0:string, 1:array} */ private static function source_clause( array $move, string $id_col ): array { global $wpdb; @@ -184,9 +184,9 @@ final class TMDO_Post_Group_Reconciler { /** * Upsert one column of the target group rows and drop their cached copies. * - * @param array $move One entry of plan(). - * @param string $id_col Entity id column of the flat tables. - * @param array $rows Source rows: list of { id, v }. + * @param array $move One entry of plan(). + * @param string $id_col Entity id column of the flat tables. + * @param array> $rows Source rows: list of { id, v }. * @return int Rows written. */ private static function copy_rows( array $move, string $id_col, array $rows ): int { diff --git a/includes/query/class-tmdo-post-query-router.php b/includes/query/class-tmdo-post-query-router.php index 1ba4611..c2f1074 100644 --- a/includes/query/class-tmdo-post-query-router.php +++ b/includes/query/class-tmdo-post-query-router.php @@ -353,8 +353,8 @@ class TMDO_Post_Query_Router { /** * The status / date copy route for a routed query, or [] when it does not apply. * - * @param \WP_Query $query Current WP_Query. - * @param array> $routed Routed clauses keyed by post type. + * @param \WP_Query $query Current WP_Query. + * @param array>> $routed Routed clauses keyed by post type. * @return array{post_type:string, group:string, statuses:string[], order:string}|array{} */ private static function copy_route( \WP_Query $query, array $routed ): array { diff --git a/uninstall.php b/uninstall.php index 8f7e54f..b8747dc 100644 --- a/uninstall.php +++ b/uninstall.php @@ -77,7 +77,7 @@ $wpdo_for_each_site = static function ( callable $callback ) use ( $wpdo_site_id }; // ── Refuse while any site holds data that exists only in wpdo_* tables ─── -if ( ! $bootstrap_ok || ! method_exists( 'TMDO_Installer', 'uninstall_blockers' ) ) { +if ( ! $bootstrap_ok ) { wp_die( esc_html( '2meet Data Optimizer:無法載入資料安全檢查,為避免資料遺失,已停止解除安裝。' ) ); }