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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -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(),
];
}
}

View File

@@ -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(),
];
}
}

View 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 [];
}
}

View File

@@ -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(),
];
}
}

View File

@@ -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(),
];
}
}

View File

@@ -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(),
];
}
}

View File

@@ -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(),
];
}
}