Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<FuncCall>
|
||||
*/
|
||||
final class ForbiddenFunctionsRule implements Rule
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $forbiddenFunctions = [
|
||||
'file_get_contents' => 'Use FileSystem abstraction instead',
|
||||
'file_put_contents' => 'Use FileSystem abstraction instead',
|
||||
'mkdir' => 'Use Directory abstraction instead',
|
||||
'rmdir' => 'Use Directory abstraction instead',
|
||||
'curl_exec' => 'Use HttpClient abstraction instead',
|
||||
'mail' => 'Use Mailer abstraction instead',
|
||||
'header' => 'Use HttpResponse abstraction instead',
|
||||
'setcookie' => 'Use Cookie abstraction instead',
|
||||
'session_start' => 'Use SessionManager abstraction instead',
|
||||
'die' => 'Use proper exception handling instead',
|
||||
'exit' => 'Use proper exception handling instead',
|
||||
'var_dump' => 'Use logger or debug() function instead',
|
||||
'print_r' => 'Use logger or debug() function instead',
|
||||
];
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return FuncCall::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof FuncCall) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! $node->name instanceof Node\Name) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$functionName = $node->name->toString();
|
||||
|
||||
if (! isset($this->forbiddenFunctions[$functionName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Function %s() is forbidden: %s',
|
||||
$functionName,
|
||||
$this->forbiddenFunctions[$functionName]
|
||||
))->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<Return_>
|
||||
*/
|
||||
final class PreferValueObjectsRule implements Rule
|
||||
{
|
||||
/** @var array<string> */
|
||||
private array $suspiciousReturnPatterns = [
|
||||
'metrics',
|
||||
'stats',
|
||||
'report',
|
||||
'summary',
|
||||
'analysis',
|
||||
'data',
|
||||
];
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return Return_::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof Return_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($node->expr === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check if we're in a method that should return Value Objects
|
||||
$function = $scope->getFunction();
|
||||
if ($function === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$methodName = $function->getName();
|
||||
$className = $scope->getClassReflection()?->getName() ?? '';
|
||||
|
||||
// Skip if not in Application layer (allow primitive returns in Framework)
|
||||
if (! str_starts_with($className, 'App\\Application\\')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check if method name suggests it should return a Value Object
|
||||
$shouldUseValueObject = false;
|
||||
foreach ($this->suspiciousReturnPatterns as $pattern) {
|
||||
if (str_contains(strtolower($methodName), $pattern)) {
|
||||
$shouldUseValueObject = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $shouldUseValueObject) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Method %s::%s() returns an array but should consider using a Value Object for better type safety and domain modeling',
|
||||
$scope->getClassReflection()?->getDisplayName() ?? 'Unknown',
|
||||
$methodName
|
||||
))->tip('Create a dedicated Value Object class instead of returning raw arrays')->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
38
src/Framework/Quality/PHPStan/Rules/TestRule.php
Normal file
38
src/Framework/Quality/PHPStan/Rules/TestRule.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<Return_>
|
||||
*/
|
||||
final class TestRule implements Rule
|
||||
{
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return Return_::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof Return_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// This will trigger on every return statement - just for testing
|
||||
if ($scope->getClassReflection()?->getName() === 'App\\Application\\Analytics\\Service\\AnalyticsReportService') {
|
||||
return [
|
||||
RuleErrorBuilder::message('TEST RULE: Found return statement in AnalyticsReportService')->build(),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<FuncCall>
|
||||
*/
|
||||
final class UseClockAbstractionRule implements Rule
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $forbiddenTimeFunctions = [
|
||||
'time' => 'Use Clock::time() instead for testable time handling',
|
||||
'microtime' => 'Use Clock::microtime() instead for testable time handling',
|
||||
'date' => 'Use Clock::now()->format() or DateTimeFormatter instead',
|
||||
'gmdate' => 'Use Clock::now()->setTimezone() and format() instead',
|
||||
'strtotime' => 'Use Clock::fromString() instead for better error handling',
|
||||
'mktime' => 'Use Clock::fromString() with proper format instead',
|
||||
'gmmktime' => 'Use Clock::fromString() with UTC timezone instead',
|
||||
];
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return FuncCall::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof FuncCall) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! $node->name instanceof Node\Name) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$functionName = $node->name->toString();
|
||||
|
||||
// Skip if we're already in Framework DateTime layer (allow direct usage there)
|
||||
$currentClass = $scope->getClassReflection()?->getName() ?? '';
|
||||
if (str_starts_with($currentClass, 'App\\Framework\\DateTime\\')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! isset($this->forbiddenTimeFunctions[$functionName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Function %s() is forbidden: %s',
|
||||
$functionName,
|
||||
$this->forbiddenTimeFunctions[$functionName]
|
||||
))
|
||||
->tip('Inject Clock interface via dependency injection for testable time handling')
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<New_>
|
||||
*/
|
||||
final class UseDateTimeAbstractionRule implements Rule
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $forbiddenDateTimeClasses = [
|
||||
'DateTime' => 'Use App\\Framework\\DateTime\\DateTime instead for consistent timezone handling',
|
||||
'DateTimeImmutable' => 'Use Clock::now() or Clock::fromString() instead for testable time handling',
|
||||
'DateInterval' => 'Use App\\Framework\\DateTime\\DateTime::createInterval() instead for consistent error handling',
|
||||
'DatePeriod' => 'Use App\\Framework\\DateTime\\DateRange instead for better domain modeling',
|
||||
];
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return New_::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof New_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! $node->class instanceof Node\Name) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$className = $node->class->toString();
|
||||
|
||||
// Skip if we're already in Framework DateTime layer (allow direct usage there)
|
||||
$currentClass = $scope->getClassReflection()?->getName() ?? '';
|
||||
if (str_starts_with($currentClass, 'App\\Framework\\DateTime\\')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! isset($this->forbiddenDateTimeClasses[$className])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Direct instantiation of %s is forbidden: %s',
|
||||
$className,
|
||||
$this->forbiddenDateTimeClasses[$className]
|
||||
))
|
||||
->tip('Use Clock interface or Framework DateTime abstractions via dependency injection')
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<New_>
|
||||
*/
|
||||
final class UseFrameworkAbstractionsRule implements Rule
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $forbiddenClasses = [
|
||||
'DateTime' => 'Use App\\Framework\\DateTime\\DateTime instead',
|
||||
'DateTimeImmutable' => 'Use App\\Framework\\DateTime\\DateTime instead',
|
||||
'PDO' => 'Use App\\Framework\\Database\\ConnectionInterface instead',
|
||||
'mysqli' => 'Use App\\Framework\\Database\\ConnectionInterface instead',
|
||||
'Redis' => 'Use App\\Framework\\Cache\\CacheDriver instead',
|
||||
'GuzzleHttp\\Client' => 'Use App\\Framework\\HttpClient\\HttpClient instead',
|
||||
'Predis\\Client' => 'Use App\\Framework\\Cache\\CacheDriver instead',
|
||||
];
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return New_::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof New_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! $node->class instanceof Node\Name) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$className = $node->class->toString();
|
||||
|
||||
// Skip if we're already in Framework layer (allow direct usage there)
|
||||
$currentClass = $scope->getClassReflection()?->getName() ?? '';
|
||||
if (str_starts_with($currentClass, 'App\\Framework\\')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! isset($this->forbiddenClasses[$className])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Direct instantiation of %s is forbidden: %s',
|
||||
$className,
|
||||
$this->forbiddenClasses[$className]
|
||||
))->tip('Use dependency injection to get the framework abstraction')->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Quality\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @implements Rule<FuncCall>
|
||||
*/
|
||||
final class UseTimerAbstractionRule implements Rule
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $forbiddenSleepFunctions = [
|
||||
'sleep' => 'Use Timer::sleep(Duration) instead for testable sleep operations',
|
||||
'usleep' => 'Use Timer::sleep(Duration) instead for testable sleep operations',
|
||||
'time_sleep_until' => 'Use Timer::sleep(Duration) with Clock abstraction instead',
|
||||
'time_nanosleep' => 'Use Timer::sleep(Duration) instead',
|
||||
];
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return FuncCall::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope): array
|
||||
{
|
||||
if (! $node instanceof FuncCall) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! $node->name instanceof Node\Name) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$functionName = $node->name->toString();
|
||||
|
||||
// Skip if we're already in Framework DateTime layer (allow direct usage there)
|
||||
$currentClass = $scope->getClassReflection()?->getName() ?? '';
|
||||
if (str_starts_with($currentClass, 'App\\Framework\\DateTime\\')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! isset($this->forbiddenSleepFunctions[$functionName])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RuleErrorBuilder::message(sprintf(
|
||||
'Function %s() is forbidden: %s',
|
||||
$functionName,
|
||||
$this->forbiddenSleepFunctions[$functionName]
|
||||
))
|
||||
->tip('Inject Timer interface via dependency injection for testable timing operations. Use Duration value objects for type-safe timing.')
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user