Files
alrahma_sunday_school_api/tools/generate-ci-controller-mapping.php
T
2026-04-23 00:04:35 -04:00

265 lines
10 KiB
PHP

#!/usr/bin/env php
<?php
/**
* Regenerate tools/ci-controller-to-laravel-mapping.csv by comparing
* CodeIgniter app/Controllers with Laravel app/Http/Controllers.
*
* Usage:
* php tools/generate-ci-controller-mapping.php
* php tools/generate-ci-controller-mapping.php --ci-path=/path/to/app_codeigniter/app/Controllers
* php tools/generate-ci-controller-mapping.php --output=/path/to/out.csv
*/
declare(strict_types=1);
$laravelRoot = dirname(__DIR__);
$defaultCiRoot = dirname($laravelRoot) . DIRECTORY_SEPARATOR . 'app_codeigniter' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Controllers';
$defaultOutput = $laravelRoot . DIRECTORY_SEPARATOR . 'tools' . DIRECTORY_SEPARATOR . 'ci-controller-to-laravel-mapping.csv';
$options = getopt('', ['ci-path:', 'output:', 'help']);
if (isset($options['help'])) {
fwrite(STDOUT, <<<TXT
Generate CI Laravel controller mapping CSV.
Options:
--ci-path=PATH CodeIgniter Controllers directory (default: sibling app_codeigniter/.../Controllers)
--output=PATH Output CSV path (default: tools/ci-controller-to-laravel-mapping.csv)
--help Show this message
TXT);
exit(0);
}
$ciRoot = isset($options['ci-path']) ? $options['ci-path'] : $defaultCiRoot;
$outputPath = isset($options['output']) ? $options['output'] : $defaultOutput;
if (! is_dir($ciRoot)) {
fwrite(STDERR, "CI controllers directory not found: {$ciRoot}\n");
exit(1);
}
$lvControllersDir = $laravelRoot . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Http' . DIRECTORY_SEPARATOR . 'Controllers';
if (! is_dir($lvControllersDir)) {
fwrite(STDERR, "Laravel controllers directory not found: {$lvControllersDir}\n");
exit(1);
}
/** @var array<string, array{paths: string[], notes: string}> */
$explicit = [
'AdminProgressController' => ['paths' => ['Api/ClassProgress/ClassProgressController.php'], 'notes' => ''],
'ParentProgressController' => ['paths' => ['Api/ClassProgress/ClassProgressController.php'], 'notes' => ''],
'ParentReportCardController' => ['paths' => ['Api/Reports/ReportCardsController.php'], 'notes' => ''],
'ApiDocsController' => ['paths' => ['Api/Documentation/ApiDocsAdminController.php'], 'notes' => ''],
'DocsController' => [
'paths' => ['Api/Documentation/DocsCatalogController.php', 'Api/Documentation/ApiDocsAdminController.php'],
'notes' => 'Two CI files (root + View); docs split between catalog + admin',
],
'ConfigurationController' => ['paths' => ['Api/Settings/ConfigurationAdminController.php'], 'notes' => ''],
'ClassPrepController' => ['paths' => ['Api/ClassPreparation/ClassPreparationController.php'], 'notes' => ''],
'BadgesController' => ['paths' => ['Api/Badges/BadgeController.php'], 'notes' => ''],
'NotificationsController' => ['paths' => ['Api/Notifications/NotificationController.php'], 'notes' => ''],
'FlagController' => ['paths' => ['Api/Incidents/IncidentController.php'], 'notes' => ''],
'SendSMSController' => ['paths' => ['Api/Messaging/MessagesController.php'], 'notes' => ''],
'TestDBController' => [
'paths' => ['Api/System/DatabaseHealthController.php', 'Api/System/HealthController.php'],
'notes' => '',
],
'ErrorController' => ['paths' => ['Api/System/AccessDeniedController.php'], 'notes' => ''],
'ScorePredictor' => ['paths' => ['Api/Scores/ScorePredictorController.php'], 'notes' => ''],
'CompetitionWinnersController' => ['paths' => ['Api/Public/PublicWinnersController.php'], 'notes' => ''],
'WinnersController' => ['paths' => ['Api/Public/PublicWinnersController.php'], 'notes' => ''],
'AssignmentController' => ['paths' => ['Api/Assignment/AssignmentApiController.php'], 'notes' => ''],
'Home' => ['paths' => [], 'notes' => 'CI welcome_message only; Laravel SPA + API'],
'BaseController' => ['paths' => [], 'notes' => 'CI framework base'],
'PrintablesBaseController' => ['paths' => [], 'notes' => 'Abstract printable base (CI)'],
'InitializeRolesPermissions' => ['paths' => [], 'notes' => 'Use Laravel seeds/Artisan; not an HTTP controller'],
'PrintRequests' => [
'paths' => ['Api/PrintRequests/PrintRequestsController.php'],
'notes' => 'HTTP JSON API + PrintRequestsPortalService',
],
'RFIDController' => [
'paths' => [
'Api/BadgeScan/BadgeScanController.php',
'Api/Students/StudentController.php',
],
'notes' => 'Scan + logs: BadgeScanController; assign student badge: StudentController.updateBadgeScan',
],
'AdministratorController' => [
'paths' => [
'Api/Administrator/AdministratorAbsenceController.php',
'Api/Administrator/AdministratorDashboardController.php',
'Api/Administrator/AdministratorEnrollmentController.php',
'Api/Administrator/AdministratorNotificationController.php',
'Api/Administrator/AdministratorTeacherSubmissionController.php',
'Api/Administrator/EmergencyContactController.php',
'Api/Administrator/TeacherClassAssignmentController.php',
],
'notes' => 'Large CI controller split across Laravel admin APIs',
],
'AttendanceController' => [
'paths' => [
'Api/Attendance/AdminAttendanceApiController.php',
'Api/Attendance/StaffAttendanceApiController.php',
'Api/Attendance/TeacherAttendanceApiController.php',
],
'notes' => 'Attendance UI split by role in Laravel API',
],
];
/** @return array<string, string[]> basename => list of relative paths under Http/Controllers */
function indexLaravelControllers(string $controllersDir): array
{
$map = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($controllersDir, FilesystemIterator::SKIP_DOTS)
);
$prefixLen = strlen($controllersDir) + 1;
foreach ($iterator as $fileInfo) {
/** @var SplFileInfo $fileInfo */
if (! $fileInfo->isFile() || strcasecmp($fileInfo->getExtension(), 'php') !== 0) {
continue;
}
if ($fileInfo->getFilename() === 'Controller.php') {
continue;
}
$full = $fileInfo->getPathname();
$rel = str_replace('\\', '/', substr($full, $prefixLen));
$base = $fileInfo->getBasename('.php');
if (! isset($map[$base])) {
$map[$base] = [];
}
$map[$base][] = $rel;
}
return $map;
}
/** @param string[] $paths */
function joinRepoPaths(array $paths): string
{
$prefixed = array_map(
static fn (string $p): string => 'app/Http/Controllers/'.$p,
array_filter($paths)
);
return implode('|', $prefixed);
}
function resolvePaths(string $basename, array $explicit, array $lvFiles): array
{
if (isset($explicit[$basename])) {
return $explicit[$basename];
}
if (isset($lvFiles[$basename])) {
return ['paths' => $lvFiles[$basename], 'notes' => ''];
}
return ['paths' => [], 'notes' => 'NOT FOUND IN Laravel app/Http/Controllers'];
}
function migrationStatus(string $basename, array $resolved, array $explicit, array $lvFiles): string
{
if (isset($explicit[$basename])) {
$e = $explicit[$basename];
if ($basename === 'AdministratorController') {
return 'mapped_split';
}
if ($basename === 'AttendanceController') {
return 'mapped_split';
}
if ($basename === 'RFIDController') {
return 'mapped_split';
}
if ($basename === 'Home') {
return 'stub_replaced';
}
if (in_array($basename, ['BaseController', 'PrintablesBaseController'], true)) {
return 'framework_base';
}
if ($basename === 'InitializeRolesPermissions') {
return 'setup_script';
}
if ($e['paths'] === []) {
return 'no_laravel_controller';
}
if ($basename === 'DocsController') {
return 'mapped_renamed';
}
$renamed = [
'AdminProgressController', 'ParentProgressController', 'ParentReportCardController', 'ApiDocsController',
'ConfigurationController', 'ClassPrepController', 'BadgesController', 'NotificationsController',
'FlagController', 'SendSMSController', 'TestDBController', 'ErrorController', 'ScorePredictor',
'CompetitionWinnersController', 'WinnersController', 'AssignmentController', 'PrintRequests',
];
if (in_array($basename, $renamed, true)) {
return 'mapped_renamed';
}
return 'mapped_explicit';
}
if (isset($lvFiles[$basename])) {
return count($lvFiles[$basename]) > 1 ? 'ambiguous_duplicate_in_laravel' : 'mapped_exact';
}
return 'unmapped';
}
$lvFiles = indexLaravelControllers($lvControllersDir);
$ciFiles = [];
$ciIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($ciRoot, FilesystemIterator::SKIP_DOTS)
);
$ciPrefixLen = strlen($ciRoot) + 1;
foreach ($ciIterator as $fileInfo) {
if (! $fileInfo->isFile() || strcasecmp($fileInfo->getExtension(), 'php') !== 0) {
continue;
}
$full = $fileInfo->getPathname();
$rel = str_replace('\\', '/', substr($full, $ciPrefixLen));
$ciFiles[$full] = $rel;
}
ksort($ciFiles);
$rows = [];
foreach ($ciFiles as $full => $relPath) {
$basename = basename($full, '.php');
$resolved = resolvePaths($basename, $explicit, $lvFiles);
$pathStr = joinRepoPaths($resolved['paths']);
$notes = $resolved['notes'];
$status = migrationStatus($basename, $resolved, $explicit, $lvFiles);
if ($status === 'unmapped') {
$pathStr = '';
if ($notes === '') {
$notes = 'NOT FOUND IN Laravel app/Http/Controllers';
}
}
$rows[] = [$relPath, $basename, $pathStr, $status, $notes];
}
$dir = dirname($outputPath);
if (! is_dir($dir)) {
mkdir($dir, 0775, true);
}
$fh = fopen($outputPath, 'wb');
if ($fh === false) {
fwrite(STDERR, "Cannot write: {$outputPath}\n");
exit(1);
}
$headers = ['ci_relative_path', 'ci_basename', 'laravel_controller_paths_from_repo_root', 'migration_status', 'notes'];
fputcsv($fh, $headers);
foreach ($rows as $row) {
fputcsv($fh, $row);
}
fclose($fh);
fwrite(STDOUT, "Wrote ".count($rows)." rows to {$outputPath}\n");