." to prevent * collisions between modules and with core session state. */ final class ModuleSessionKeyPrefixContractTest extends AbstractModuleStructureContractTestCase { /** * Pattern matches direct $_SESSION access with a string key that does NOT * start with "module.". */ private const SESSION_ACCESS_PATTERN = '/\$_SESSION\s*\[\s*[\'"](?!module\.)/'; public function testModulePhpFilesUseSessionPrefix(): void { $modulesDir = realpath(__DIR__ . '/../../modules'); self::assertNotFalse($modulesDir); $violations = []; foreach (self::$modules as $module) { $moduleId = $module['id']; foreach (['lib', 'pages'] as $subdir) { $dir = $module['path'] . '/' . $subdir; if (!is_dir($dir)) { continue; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { /** @var \SplFileInfo $file */ $ext = $file->getExtension(); if ($ext !== 'php' && $ext !== 'phtml') { continue; } $content = file_get_contents($file->getPathname()); if ($content === false) { continue; } if (preg_match(self::SESSION_ACCESS_PATTERN, $content)) { $relativePath = 'modules/' . $moduleId . '/' . $subdir . '/' . str_replace($dir . '/', '', $file->getPathname()); $violations[] = $relativePath . " (module '{$moduleId}') — session keys must start with 'module.{$moduleId}.'"; } } } } self::assertSame( [], $violations, "Module session keys must be prefixed with 'module..':\n" . implode("\n", $violations) ); } }