From dd33f7bebe6db79b5c4cff9609a2e9a1480173dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Robin?= Date: Thu, 28 Nov 2024 19:12:53 +0100 Subject: [PATCH] Closes #7125: PHPStan: Ensure no hooks are present inside the ORM logic (#7152) --- phpstan-baseline.neon | 15 +++ phpstan.neon.dist | 2 + tests/phpstan/Rules/NoHooksInORM.php | 55 ++++++++++ .../phpstan/tests/Rules/NoHooksInORMTest.php | 32 ++++++ .../NoHooksInORMTest/orm-class-with-hooks.php | 101 ++++++++++++++++++ .../orm-class-without-hooks.php | 95 ++++++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 tests/phpstan/Rules/NoHooksInORM.php create mode 100644 tests/phpstan/tests/Rules/NoHooksInORMTest.php create mode 100644 tests/phpstan/tests/data/NoHooksInORMTest/orm-class-with-hooks.php create mode 100644 tests/phpstan/tests/data/NoHooksInORMTest/orm-class-without-hooks.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 75d173329e..fcc843b472 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -215,6 +215,11 @@ parameters: count: 3 path: inc/Engine/Media/AboveTheFold/AJAX/Controller.php + - + message: "#^Hooks should not be used in ORM classes\\: WP_Rocket\\\\Engine\\\\Media\\\\AboveTheFold\\\\Database\\\\Queries\\\\AboveTheFold\\:\\:apply_filters$#" + count: 1 + path: inc/Engine/Media/AboveTheFold/Database/Queries/AboveTheFold.php + - message: "#^Usage of apply_filters\\(\\) is discouraged\\. Use wpm_apply_filters_typed\\(\\) instead\\.$#" count: 1 @@ -315,6 +320,11 @@ parameters: count: 1 path: inc/Engine/Optimization/LazyRenderContent/AJAX/Controller.php + - + message: "#^Hooks should not be used in ORM classes\\: WP_Rocket\\\\Engine\\\\Optimization\\\\LazyRenderContent\\\\Database\\\\Queries\\\\LazyRenderContent\\:\\:apply_filters$#" + count: 1 + path: inc/Engine/Optimization/LazyRenderContent/Database/Queries/LazyRenderContent.php + - message: "#^Usage of apply_filters\\(\\) is discouraged\\. Use wpm_apply_filters_typed\\(\\) instead\\.$#" count: 1 @@ -410,6 +420,11 @@ parameters: count: 5 path: inc/Engine/Preload/Cron/Subscriber.php + - + message: "#^Hooks should not be used in ORM classes\\: WP_Rocket\\\\Engine\\\\Preload\\\\Database\\\\Queries\\\\Cache\\:\\:apply_filters$#" + count: 4 + path: inc/Engine/Preload/Database/Queries/Cache.php + - message: "#^Usage of apply_filters\\(\\) is discouraged\\. Use wpm_apply_filters_typed\\(\\) instead\\.$#" count: 4 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index bfea57688d..7d2c7a5a48 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -58,3 +58,5 @@ rules: - WP_Rocket\Tests\phpstan\Rules\DiscourageUpdateOptionUsage - WP_Rocket\Tests\phpstan\Rules\DiscourageApplyFilters - WP_Rocket\Tests\phpstan\Rules\EnsureCallbackMethodsExistsInSubscribedEvents + - WP_Rocket\Tests\phpstan\Rules\NoHooksInORM + diff --git a/tests/phpstan/Rules/NoHooksInORM.php b/tests/phpstan/Rules/NoHooksInORM.php new file mode 100644 index 0000000000..513210164f --- /dev/null +++ b/tests/phpstan/Rules/NoHooksInORM.php @@ -0,0 +1,55 @@ +reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node instanceof FuncCall) { + return []; + } + + $functionName = $node->name; + if (!$functionName instanceof Node\Name) { + return []; + } + + $functionName = $functionName->toString(); + $hookFunctions = ['add_action', 'add_filter', 'do_action', 'apply_filters', 'wpm_apply_filters_typed', 'apply_filters_ref_array', 'wpm_apply_filters_typesafe']; + + if (in_array($functionName, $hookFunctions, true)) { + $classReflection = $scope->getClassReflection(); + if ($classReflection !== null) { + $className = $classReflection->getName(); + $queryClassReflection = $this->reflectionProvider->getClass('WP_Rocket\Dependencies\BerlinDB\Database\Query'); + if ($classReflection->isSubclassOf($queryClassReflection->getName())) { + return [ + RuleErrorBuilder::message(sprintf('Hooks should not be used in ORM classes: %s::%s', $className, $functionName))->identifier('noHooksInORM')->build(), + ]; + } + } + } + + return []; + } +} diff --git a/tests/phpstan/tests/Rules/NoHooksInORMTest.php b/tests/phpstan/tests/Rules/NoHooksInORMTest.php new file mode 100644 index 0000000000..7346a6030e --- /dev/null +++ b/tests/phpstan/tests/Rules/NoHooksInORMTest.php @@ -0,0 +1,32 @@ +createReflectionProvider(); + return new NoHooksInORM($reflectionProvider); + } + + public function testShouldReturnErrorBecauseOfHooks() + { + $this->analyse([__DIR__ . '/../data/NoHooksInORMTest/orm-class-with-hooks.php'], [ + [ + 'Hooks should not be used in ORM classes: WP_Rocket\Tests\phpstan\tests\Rules\ORMWithHooks::apply_filters', + 90, + ], + ]); + } + + + public function testShouldNotReturnError() + { + $this->analyse([__DIR__ . '/../data/NoHooksInORMTest/orm-class-without-hooks.php'], []); + } +} diff --git a/tests/phpstan/tests/data/NoHooksInORMTest/orm-class-with-hooks.php b/tests/phpstan/tests/data/NoHooksInORMTest/orm-class-with-hooks.php new file mode 100644 index 0000000000..57b917ecac --- /dev/null +++ b/tests/phpstan/tests/data/NoHooksInORMTest/orm-class-with-hooks.php @@ -0,0 +1,101 @@ +get_db(); + + // Bail if no database interface is available. + if ( ! $db ) { + return false; + } + + /** + * Filters the interval (in months) to determine when Below The Fold entry is considered 'old'. + * Old LRC entries are eligible for deletion. By default, LRC entry is considered old if it hasn't been accessed in the last month. + * + * @param int $delete_interval The interval in months after which LRC entry is considered old. Default is 1 month. + */ + $delete_interval = (int) apply_filters( 'rocket_lrc_cleanup_interval', 1 ); + + if ( $delete_interval <= 0 ) { + return false; + } + + $prefixed_table_name = $db->prefix . $this->table_name; + $query = "DELETE FROM `$prefixed_table_name` WHERE status = 'failed' OR `last_accessed` <= date_sub(now(), interval $delete_interval month)"; + + return $db->query( $query ); + } +} diff --git a/tests/phpstan/tests/data/NoHooksInORMTest/orm-class-without-hooks.php b/tests/phpstan/tests/data/NoHooksInORMTest/orm-class-without-hooks.php new file mode 100644 index 0000000000..2c76d7e986 --- /dev/null +++ b/tests/phpstan/tests/data/NoHooksInORMTest/orm-class-without-hooks.php @@ -0,0 +1,95 @@ +get_db(); + + // Bail if no database interface is available. + if ( ! $db ) { + return false; + } + + $delete_interval = 30; + + if ( $delete_interval <= 0 ) { + return false; + } + + $prefixed_table_name = $db->prefix . $this->table_name; + $query = "DELETE FROM `$prefixed_table_name` WHERE status = 'failed' OR `last_accessed` <= date_sub(now(), interval $delete_interval month)"; + + return $db->query( $query ); + } +}