*/ private const FORBIDDEN_PATTERNS = [ '/\$_SESSION\b/' => '$_SESSION', '/\$_GET\b/' => '$_GET', '/\$_POST\b/' => '$_POST', '/\$_COOKIE\b/' => '$_COOKIE', '/\$_REQUEST\b/' => '$_REQUEST', '/\bheader\s*\(/' => 'header()', '/\bhttp_response_code\s*\(/' => 'http_response_code()', '/\bsetcookie\s*\(/' => 'setcookie()', '/\bsession_start\s*\(/' => 'session_start()', ]; public function testCoreRepositoriesDoNotAccessHttpSuperglobals(): void { $this->assertRepositoryIsolation('lib/Repository'); } public function testModuleRepositoriesDoNotAccessHttpSuperglobals(): void { $root = $this->projectRootPath(); $modulesDir = $root . '/modules'; if (!is_dir($modulesDir)) { self::markTestSkipped('modules/ directory missing.'); } foreach (scandir($modulesDir) ?: [] as $entry) { if ($entry === '.' || $entry === '..' || !is_dir($modulesDir . '/' . $entry)) { continue; } $libRel = 'modules/' . $entry . '/lib'; if (!is_dir($root . '/' . $libRel)) { continue; } // Scan only Repository directories within module lib/ $repoDir = $libRel . '/Repository'; if (is_dir($root . '/' . $repoDir)) { $this->assertRepositoryIsolation($repoDir); } // Also scan files named *Repository.php anywhere in module lib/ $this->assertRepositoryFilesIsolated($libRel); } } private function assertRepositoryIsolation(string $relativeDirectory): void { $violations = []; foreach (self::FORBIDDEN_PATTERNS as $pattern => $label) { foreach ($this->findPatternMatchesInPhpFiles($relativeDirectory, $pattern) as $file) { $violations[] = "{$file} uses {$label}"; } } self::assertSame( [], $violations, "Repository layer must not access HTTP state. Violations:\n" . implode("\n", $violations) ); } private function assertRepositoryFilesIsolated(string $relativeDirectory): void { $root = $this->projectRootPath(); $basePath = $root . '/' . $relativeDirectory; $violations = []; $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($basePath, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { /** @var \SplFileInfo $file */ if ($file->getExtension() !== 'php') { continue; } if (!str_ends_with($file->getFilename(), 'Repository.php')) { continue; } if (str_ends_with($file->getFilename(), 'RepositoryInterface.php')) { continue; } $content = file_get_contents($file->getPathname()); if ($content === false) { continue; } $relativePath = str_replace($root . '/', '', $file->getPathname()); foreach (self::FORBIDDEN_PATTERNS as $pattern => $label) { if (preg_match($pattern, $content)) { $violations[] = "{$relativePath} uses {$label}"; } } } self::assertSame( [], $violations, "Repository files must not access HTTP state. Violations:\n" . implode("\n", $violations) ); } }