From edcb52ecbcb5f9cb1cf8f5e1ff0a7d41a3ac42ae Mon Sep 17 00:00:00 2001 From: Juan de Paco Moreno Date: Mon, 1 Apr 2024 18:42:04 +0200 Subject: [PATCH] Add PHPStan analysis (#45) * [wpmlbridge-296] Bring PHPStan analysis. https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlbridge-296 --- .github/workflows/php.yml | 9 +- composer.json | 16 +- composer.lock | 297 +++++++++++++++++++++++++- phpstan.neon | 19 ++ src/Feature.php | 11 +- src/Manager/Indices.php | 8 +- src/Traits/CrudPropagation.php | 10 + tests/phpstan/bootstrap.php | 3 + tests/phpstan/stubs/elasticpress.stub | 244 +++++++++++++++++++++ tests/phpstan/stubs/wp-cli.stub | 39 ++++ 10 files changed, 641 insertions(+), 15 deletions(-) create mode 100644 phpstan.neon create mode 100644 tests/phpstan/bootstrap.php create mode 100644 tests/phpstan/stubs/elasticpress.stub create mode 100644 tests/phpstan/stubs/wp-cli.stub diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 19f0810..8ee906a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,5 +1,5 @@ -name: OTGS PHP UNIT -run-name: PHP Unit at ${{ github.ref_name }} +name: OTGS PHP TEST +run-name: PHP TEST at ${{ github.ref_name }} on: [push, pull_request] @@ -20,5 +20,8 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - - name: Run test suite + - name: Run unit test suite run: ./vendor/bin/phpunit + + - name: Run static test suite + run: ./vendor/bin/phpstan diff --git a/composer.json b/composer.json index 65a24a9..9574813 100644 --- a/composer.json +++ b/composer.json @@ -28,22 +28,26 @@ "config": { "sort-packages": true, "platform": { - "php": "5.6" + "php": "7.4" }, "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true } }, "minimum-stability": "dev", "prefer-stable": true, "require-dev": { - "roave/security-advisories": "dev-master", + "dealerdirect/phpcodesniffer-composer-installer": "*", "otgs/unit-tests-framework": "~1.2.0", - "sebastian/phpcpd": "^3.0", - "squizlabs/php_codesniffer": "~3", "phpcompatibility/php-compatibility": "*", "phpcompatibility/phpcompatibility-wp": "*", - "dealerdirect/phpcodesniffer-composer-installer": "*", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "roave/security-advisories": "dev-master", + "sebastian/phpcpd": "^3.0", + "squizlabs/php_codesniffer": "~3", + "szepeviktor/phpstan-wordpress": "^1.3", "wp-coding-standards/wpcs": "^0" }, "scripts": { diff --git a/composer.lock b/composer.lock index d359474..98512c3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "884a0f89ace02cceb864fc8c83eff141", + "content-hash": "972bde3bc31760cc7b675d2a11deca22", "packages": [], "packages-dev": [ { @@ -510,6 +510,53 @@ ], "time": "2019-08-29T07:13:54+00:00" }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "6105bdab2f26c0204fe90ecc53d5684754550e8f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6105bdab2f26c0204fe90ecc53d5684754550e8f", + "reference": "6105bdab2f26c0204fe90ecc53d5684754550e8f", + "shasum": "" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^4.13", + "php": "^7.4 || ~8.0.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpstan": "^1.10.49", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.11" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.4.3" + }, + "time": "2024-02-11T18:56:19+00:00" + }, { "name": "phpcompatibility/php-compatibility", "version": "9.3.4", @@ -879,6 +926,112 @@ ], "time": "2019-10-03T11:07:50+00:00" }, + { + "name": "phpstan/extension-installer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", + "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + }, + "time": "2023-05-24T08:59:17+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.10.66", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-03-28T16:17:31+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "4.0.8", @@ -2652,6 +2805,82 @@ ], "time": "2020-07-14T12:35:20+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, { "name": "symfony/yaml", "version": "v3.4.36", @@ -2711,6 +2940,68 @@ "homepage": "https://symfony.com", "time": "2019-10-24T15:33:53+00:00" }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v1.3.4", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "891d0767855a32c886a439efae090408cc1fa156" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/891d0767855a32c886a439efae090408cc1fa156", + "reference": "891d0767855a32c886a439efae090408cc1fa156", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", + "phpstan/phpstan": "^1.10.31", + "symfony/polyfill-php73": "^1.12.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^0.8" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.4" + }, + "time": "2024-03-21T16:32:59+00:00" + }, { "name": "theseer/fdomdocument", "version": "1.6.6", @@ -2881,7 +3172,7 @@ "platform": [], "platform-dev": [], "platform-overrides": { - "php": "5.6" + "php": "7.4" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..78d1e21 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,19 @@ +parameters: + level: 3 + featureToggles: + disableRuntimeReflectionProvider: true + bootstrapFiles: + - tests/phpstan/bootstrap.php + - tests/phpstan/stubs/elasticpress.stub + - tests/phpstan/stubs/wp-cli.stub + paths: + - src + dynamicConstantNames: + - EP_VERSION + - EP_DASHBOARD_SYNC + + checkFunctionNameCase: true + inferPrivatePropertyTypeFromConstructor: true + + parallel: + processTimeout: 300.0 diff --git a/src/Feature.php b/src/Feature.php index 3854f92..c67756d 100644 --- a/src/Feature.php +++ b/src/Feature.php @@ -31,11 +31,20 @@ class Feature extends \ElasticPress\Feature { /** @var Stats\Report */ private $statsReport; + /** @var string */ + public $slug; + + /** @var string */ + public $title; + + /** @var bool */ + public $requires_install_reindex = false; + /** * @param Field\Search $fieldSearch * @param Field\Sync $fieldSync * @param Sync\Dashboard $syncDashboard - * @param Sync\singular $syncSingular + * @param Sync\Singular $syncSingular * @param Sync\CLI $syncCli * @param FeatureSupport\Search $frontendSearch * @param FeatureSupport\RelatedPosts $frontendRelatedPosts diff --git a/src/Manager/Indices.php b/src/Manager/Indices.php index 475484e..099524e 100644 --- a/src/Manager/Indices.php +++ b/src/Manager/Indices.php @@ -183,10 +183,14 @@ public function generateMissingIndices() { * @param \WP_Site $blog */ public function createBlogIndices( $blog ) { - $syncManager = $this->indexables->get( \WPML\ElasticPress\Constants::INDEXABLE_SLUG_POST )->sync_manager; + $indexable = $this->indexables->get( \WPML\ElasticPress\Constants::INDEXABLE_SLUG_POST ); + if ( false === $indexable ) { + return; + } + foreach ( $this->activeLanguages as $language ) { $this->setCurrentIndexLanguage( $language ); - $syncManager->action_create_blog_index( $blog ); + $indexable->sync_manager->action_create_blog_index( $blog ); $this->clearCurrentIndexLanguage(); } } diff --git a/src/Traits/CrudPropagation.php b/src/Traits/CrudPropagation.php index 241f9db..9f7554c 100644 --- a/src/Traits/CrudPropagation.php +++ b/src/Traits/CrudPropagation.php @@ -170,15 +170,25 @@ private function setIds( $ids, $role = 'main' ) { */ private function syncIds( $ids ) { $postIndexable = $this->indexables->get( \WPML\ElasticPress\Constants::INDEXABLE_SLUG_POST ); + if ( false === $postIndexable ) { + return; + } + $this->indicesManager->generateIndexByIndexable( $postIndexable ); $postIndexable->bulk_index_dynamically( $ids ); } /** * @param array $ids + * + * @return array */ private function deleteIds( $ids ) { $postIndexable = $this->indexables->get( \WPML\ElasticPress\Constants::INDEXABLE_SLUG_POST ); + if ( false === $postIndexable ) { + return []; + } + $indexName = $postIndexable->get_index_name(); if ( ! $this->indicesManager->indexExists( $indexName ) ) { return []; diff --git a/tests/phpstan/bootstrap.php b/tests/phpstan/bootstrap.php new file mode 100644 index 0000000..8412a39 --- /dev/null +++ b/tests/phpstan/bootstrap.php @@ -0,0 +1,3 @@ + $assocArgs + */ + function sync( $args, $assoc_args ) {} + + /** + * @param mixed[] $args + * @param array $assocArgs + */ + function index( $args, $assoc_args ) {} + + } + + class Feature { + + public function __construct() {} + + /** + * @return boolean + */ + public function is_active() {} + + } + + class Features { + + /** + * @param string $slug + * @return Feature|false + */ + public function get_registered_feature( $slug ) {} + + /** + * @return self + */ + public static function factory() {} + + /** + * @param Feature $feature An instance of the Feature class + * @return boolean + */ + public function register_feature( Feature $feature ) {} + + } + + class Elasticsearch { + + /** + * @param string $index Index name. + * @return boolean + */ + public function index_exists( $index ) {} + + /** + * + * @param string $index Index name. + * @param array $mapping Mapping array. + * @param string $return_type Desired return type. Can be either 'bool' or 'raw' + * @return boolean|WP_Error + */ + public function put_mapping( $index, $mapping, $return_type = 'bool' ) {} + + /** + * @return boolean + */ + public function delete_all_indices() {} + + /** + * @return self + */ + public static function factory() {} + + /** + * @param bool $force Bust cache or not. + * @return string|bool + */ + public function get_elasticsearch_version( $force = false ) {} + + } + + class SyncManager { + + /** @var array */ + public $sync_queue = []; + + /** + * @param WP_Site $blog New site object. + */ + public function action_create_blog_index( $blog ) {} + + /** + * @param int $blog_id + */ + public function action_delete_blog_from_index( $blog_id ) {} + + /** + * @param int $post_id Post id. + */ + public function action_sync_on_update( $post_id ) {} + + } + + class Indexables { + + /** @var SyncManager */ + public $sync_manager; + + /** + * @param boolean $global If true or false, will only get Indexables with that global property. + * @param boolean $slug_only True returns an array of only string slugs. + * @param string $status Whether to return active indexables or all registered. + * @return array + */ + public function get_all( $global = null, $slug_only = false, $status = 'active' ) {} + + /** + * @param string $slug Indexable type slug. + * @return Indexable|false + */ + public function get( $slug ) {} + + /** + * @return self + */ + public static function factory() {} + + /** + * @param string $slug Indexable slug + * @return boolean + */ + public function is_active( string $slug ) : bool {} + + /** + * @param string $slug The indexable slug + */ + public function deactivate( string $slug ) {} + + /** + * @param string $slug The indexable slug + */ + public function activate( string $slug ) {} + + } + + class Indexable { + + /** @var SyncManager */ + public $sync_manager; + + /** + * @param int $blog_id `null` means current blog. + * @return string + */ + public function get_index_name( $blog_id = null ) {} + + /** + * @return array + */ + public function generate_mapping() {} + + /** + * @param array $object_ids Array of object IDs. + * @return array[WP_Error|array] The return of each request made. + */ + public function bulk_index_dynamically( $object_ids ) {} + + /** + * @param int $object_id Object to get. + * @return boolean|array + */ + public function get( $object_id ) {} + + /** + * @param int $object_id Object to delete. + * @param boolean $blocking Whether to issue blocking HTTP request or not. + * @return boolean + */ + public function delete( $object_id, $blocking = true ) {} + + } + + class IndexHelper { + + /** + * @return self + */ + public static function factory() {} + + /** + * @param array $args Arguments. + */ + public function full_index( $args ) {} + + } + +} + +namespace ElasticPress\StatusReport { + + class Report {} + + class Indices { + + /** + * @return array + */ + public function get_groups() {} + + /** + * @return string + */ + public function get_title() {} + + } + +} + +namespace ElasticPress\Utils { + + /** + * @param int $limit The maximum amount of sites retrieved, Use 0 to return all sites. + * @param bool $only_indexable Whether should be returned only indexable sites or not. + * @return array + */ + function get_sites( $limit = 0, $only_indexable = false ) {} + + /** + * @return array|boolean + */ + function get_indexing_status() {} + + /** + * @return string + */ + function get_capability() {} + +} diff --git a/tests/phpstan/stubs/wp-cli.stub b/tests/phpstan/stubs/wp-cli.stub new file mode 100644 index 0000000..1e39dc2 --- /dev/null +++ b/tests/phpstan/stubs/wp-cli.stub @@ -0,0 +1,39 @@ + $assoc_args + */ + public static function confirm( string $question, array $assoc_args = [] ): void {} + + public static function success( string $message ): void {} + + /** + * @param callable|object|string $callable + * @param array $args + */ + public static function add_command( string $name, mixed $callable, array $args = [] ): bool {} + + } + +} + +namespace WP_CLI\Utils { + + /** + * @param array $assoc_args + */ + function get_flag_value( array $assoc_args, string $flag, mixed $default = null ): mixed {} + +}