-
Notifications
You must be signed in to change notification settings - Fork 223
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #7125: PHPStan: Ensure no hooks are present inside the ORM log…
…ic (#7152)
- Loading branch information
Showing
6 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\Tests\phpstan\Rules; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\FuncCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\ReflectionProvider; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
class NoHooksInORM implements Rule | ||
{ | ||
private $reflectionProvider; | ||
|
||
public function __construct(ReflectionProvider $reflectionProvider) | ||
{ | ||
$this->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 []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\Tests\phpstan\tests\Rules; | ||
|
||
use PHPStan\Testing\RuleTestCase; | ||
use WP_Rocket\Tests\phpstan\Rules\NoHooksInORM; | ||
use PHPStan\Reflection\ReflectionProvider; | ||
|
||
class NoHooksInORMTest extends RuleTestCase | ||
{ | ||
protected function getRule(): \PHPStan\Rules\Rule | ||
{ | ||
$reflectionProvider = $this->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'], []); | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
tests/phpstan/tests/data/NoHooksInORMTest/orm-class-with-hooks.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace WP_Rocket\Tests\phpstan\tests\Rules; | ||
|
||
use WP_Rocket\Engine\Common\PerformanceHints\Database\Queries\AbstractQueries; | ||
use WP_Rocket\Engine\Common\PerformanceHints\Database\Queries\QueriesInterface; | ||
use WP_Rocket\Engine\Optimization\LazyRenderContent\Database\Schema\LazyRenderContent as LRCSchema; | ||
use WP_Rocket\Engine\Optimization\LazyRenderContent\Database\Rows\LazyRenderContent as LRCRow; | ||
|
||
class ORMWithHooks extends AbstractQueries implements QueriesInterface { | ||
/** | ||
* Name of the database table to query. | ||
* | ||
* @var string | ||
*/ | ||
protected $table_name = 'wpr_lazy_render_content'; | ||
|
||
/** | ||
* String used to alias the database table in MySQL statement. | ||
* | ||
* Keep this short, but descriptive. I.E. "tr" for term relationships. | ||
* | ||
* This is used to avoid collisions with JOINs. | ||
* | ||
* @var string | ||
*/ | ||
protected $table_alias = 'wpr_lrc'; | ||
|
||
/** | ||
* Name of class used to setup the database schema. | ||
* | ||
* @var string | ||
*/ | ||
protected $table_schema = LRCSchema::class; | ||
|
||
/** Item ******************************************************************/ | ||
|
||
/** | ||
* Name for a single item. | ||
* | ||
* Use underscores between words. I.E. "term_relationship" | ||
* | ||
* This is used to automatically generate action hooks. | ||
* | ||
* @var string | ||
*/ | ||
protected $item_name = 'lazy_render_content'; | ||
|
||
/** | ||
* Plural version for a group of items. | ||
* | ||
* Use underscores between words. I.E. "term_relationships" | ||
* | ||
* This is used to automatically generate action hooks. | ||
* | ||
* @var string | ||
*/ | ||
protected $item_name_plural = 'lazy_render_content'; | ||
|
||
/** | ||
* Name of class used to turn IDs into first-class objects. | ||
* | ||
* This is used when looping through return values to guarantee their shape. | ||
* | ||
* @var mixed | ||
*/ | ||
protected $item_shape = LRCRow::class; | ||
|
||
/** | ||
* Delete all rows which were not accessed in the last month. | ||
* | ||
* @return bool|int | ||
*/ | ||
public function delete_old_rows() { | ||
// Get the database interface. | ||
$db = $this->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 ); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
tests/phpstan/tests/data/NoHooksInORMTest/orm-class-without-hooks.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace WP_Rocket\Tests\phpstan\tests\Rules; | ||
|
||
use WP_Rocket\Engine\Common\PerformanceHints\Database\Queries\AbstractQueries; | ||
use WP_Rocket\Engine\Common\PerformanceHints\Database\Queries\QueriesInterface; | ||
use WP_Rocket\Engine\Optimization\LazyRenderContent\Database\Schema\LazyRenderContent as LRCSchema; | ||
use WP_Rocket\Engine\Optimization\LazyRenderContent\Database\Rows\LazyRenderContent as LRCRow; | ||
|
||
class ORMWithoutHooks extends AbstractQueries implements QueriesInterface { | ||
/** | ||
* Name of the database table to query. | ||
* | ||
* @var string | ||
*/ | ||
protected $table_name = 'wpr_lazy_render_content'; | ||
|
||
/** | ||
* String used to alias the database table in MySQL statement. | ||
* | ||
* Keep this short, but descriptive. I.E. "tr" for term relationships. | ||
* | ||
* This is used to avoid collisions with JOINs. | ||
* | ||
* @var string | ||
*/ | ||
protected $table_alias = 'wpr_lrc'; | ||
|
||
/** | ||
* Name of class used to setup the database schema. | ||
* | ||
* @var string | ||
*/ | ||
protected $table_schema = LRCSchema::class; | ||
|
||
/** Item ******************************************************************/ | ||
|
||
/** | ||
* Name for a single item. | ||
* | ||
* Use underscores between words. I.E. "term_relationship" | ||
* | ||
* This is used to automatically generate action hooks. | ||
* | ||
* @var string | ||
*/ | ||
protected $item_name = 'lazy_render_content'; | ||
|
||
/** | ||
* Plural version for a group of items. | ||
* | ||
* Use underscores between words. I.E. "term_relationships" | ||
* | ||
* This is used to automatically generate action hooks. | ||
* | ||
* @var string | ||
*/ | ||
protected $item_name_plural = 'lazy_render_content'; | ||
|
||
/** | ||
* Name of class used to turn IDs into first-class objects. | ||
* | ||
* This is used when looping through return values to guarantee their shape. | ||
* | ||
* @var mixed | ||
*/ | ||
protected $item_shape = LRCRow::class; | ||
|
||
/** | ||
* Delete all rows which were not accessed in the last month. | ||
* | ||
* @return bool|int | ||
*/ | ||
public function delete_old_rows() { | ||
// Get the database interface. | ||
$db = $this->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 ); | ||
} | ||
} |