Skip to content

Refactor: Resolve Router via Symfony DI container#39

Open
Sameer6305 wants to merge 52 commits intomedcore-ua:mainfrom
Sameer6305:refactor/move-core-to-container
Open

Refactor: Resolve Router via Symfony DI container#39
Sameer6305 wants to merge 52 commits intomedcore-ua:mainfrom
Sameer6305:refactor/move-core-to-container

Conversation

@Sameer6305
Copy link
Copy Markdown
Contributor

Summary

This PR continues the Symfony DependencyInjection integration by ensuring that the Router is properly resolved through the DI container instead of relying on incorrect or outdated FQCN references.

What Was Done

Verified correct namespace for Router:
App\Core\Http\Router

Confirmed PSR-4 mapping:
"App\": "src/"

Ensured class_exists('\App\Core\Http\Router') returns true

Updated config/services.php to register the correct FQCN

Verified container resolution using:

$container->get(App\Core\Http\Router::class);

Removed stale references to incorrect FQCN (App\Core\Router)

Confirmed no syntax errors in updated files

Verification Performed

php composer.phar dump-autoload executed successfully

class_exists() validation returned bool(true)

Container successfully resolves Router

php -l syntax checks passed

Application boots via:

php -S localhost:8000 -t public

No runtime errors observed.

Scope

This PR:

Does NOT introduce architectural changes

Does NOT modify module system

Does NOT refactor singleton patterns

Only ensures namespace and DI alignment

Impact

Improves correctness of DI resolution

Prevents incorrect class references

Keeps Symfony DI integration branch consistent

ChernegaSergiy and others added 19 commits January 31, 2026 22:05
- Add Router as public service with service_container injection
- Add ModuleManager as public service (no-arg constructor)
- Add PermissionRegistry and PolicyRegistry as public services
- All registrations placed in Core services section of config/services.php
- Replace manual Router instantiation with container->get()
- Replace manual ModuleManager instantiation with container->get()
- Replace manual PermissionRegistry instantiation with container->get()
- Replace manual PolicyRegistry instantiation with container->get()
- No business logic changes; ModuleLoader remains manual
- Add composer.phar and composer-setup.php to .gitignore
- All services registered with correct FQCN (App\Core\Http\Router)
- Container resolves Router, ModuleManager, PermissionRegistry, PolicyRegistry
Copilot AI review requested due to automatic review settings March 3, 2026 12:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR advances the Symfony DependencyInjection integration by introducing a compiled container, wiring core runtime services (Router, registries, dispatcher, etc.) through it, and refactoring many controllers/services to support dependency injection (while keeping backwards-compatible fallbacks).

Changes:

  • Added a Symfony DI container factory and a new config/services.php service registration map.
  • Updated public/index.php to bootstrap the DI container and fetch Router/registries/module manager from it.
  • Refactored many controllers/services/repositories to accept optional dependencies instead of constructing everything internally.

Reviewed changes

Copilot reviewed 43 out of 45 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/Module/User/UserController.php Inject SettingsRepository instead of instantiating inline.
src/Module/User/OAuthController.php Kept manual construction; comment added for “DI compatibility”.
src/Module/User/MfaService.php Allow injecting PDO/QR generator/issuer name.
src/Module/User/MfaController.php Allow injecting MFA dependencies (settings/role repos).
src/Module/User/AuthController.php Allow injecting MFA/settings/oauth controller; use composed oauth controller for redirect.
src/Module/Schedule/SchedulePolicy.php Optional DI-friendly repository injection.
src/Module/Schedule/ScheduleController.php Optional DI-friendly repository injection.
src/Module/Room/RoomController.php Optional DI-friendly repository injection.
src/Module/Prescription/PrescriptionPolicy.php Optional DI-friendly repository injection.
src/Module/Prescription/PrescriptionController.php Optional DI-friendly repository injection.
src/Module/Patient/Repository/PatientRepository.php Allow injecting PDO/audit logger; use audit logger dependency when available.
src/Module/Patient/PatientPolicy.php Optional DI-friendly repository injection.
src/Module/Patient/PatientController.php Optional DI-friendly injections for insurance-related dependencies.
src/Module/Notification/NotificationController.php Optional DI-friendly repository injection.
src/Module/News/NewsController.php Optional DI-friendly repository injection.
src/Module/MedicalRecord/MedicalRecordPolicy.php Optional DI-friendly repository injection.
src/Module/MedicalRecord/MedicalRecordController.php Optional DI-friendly dependency injection for repos/services.
src/Module/LabOrder/Service/LabImportService.php Optional DI-friendly repository injection.
src/Module/LabOrder/LabOrderPolicy.php Optional DI-friendly repository injection.
src/Module/LabOrder/LabOrderController.php Optional DI-friendly dependency injection.
src/Module/Kpi/KpiController.php Optional DI-friendly repository injection.
src/Module/Inventory/InventoryController.php Optional DI-friendly repository injection.
src/Module/Insurance/InsuranceController.php Optional DI-friendly service injection (keeps fallback wiring).
src/Module/Hrm/HrmController.php Optional DI-friendly repository injection.
src/Module/Department/DepartmentController.php Optional DI-friendly repository injection.
src/Module/Dashboard/Service/KpiCalculatorService.php Optional DI-friendly repository injection.
src/Module/Dashboard/Service/DashboardService.php Optional DI-friendly repository injection.
src/Module/Dashboard/DashboardController.php Optional DI-friendly service injection.
src/Module/Billing/ContractController.php Optional DI-friendly repository injection.
src/Module/Billing/BillingController.php Optional DI-friendly injection; separates insurance repos/service wiring.
src/Module/Appointment/AppointmentPolicy.php Optional DI-friendly repository injection.
src/Module/Appointment/AppointmentController.php Optional DI-friendly injection; supports injecting composed SchedulingService.
src/Module/Admin/AdminController.php Optional DI-friendly injection; reuse injected MFA service.
src/Infrastructure/DI/ContainerFactory.php New container builder/loader that compiles config/services.php.
src/Database/Database.php Wrap PDO creation with clearer RuntimeException on connection failure.
src/Core/Service/NotificationService.php Optional DI-friendly PDO injection.
src/Core/Service/AuditLogger.php Optional DI-friendly PDO injection.
src/Core/Service/AttachmentService.php Optional DI-friendly PDO and uploadDir injection.
src/Core/Http/View.php Add setTranslationService() to inject translation service and set initial locale.
src/Core/Http/Router.php Resolve controllers via container when available; adds try/catch around controller creation.
public/index.php Bootstrap DI container and resolve Router/registries/module manager from it; inject TranslationService into View.
config/services.php New service registrations for container-resolved controllers/services/repositories.
composer.lock Locks new Symfony DI dependency and transitive packages.
composer.json Adds symfony/dependency-injection requirement.
.gitignore Ignores local composer phar/setup artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +251 to +255
->setArguments([
new Reference(\App\Module\Admin\Repository\AuthConfigRepository::class),
new Reference(\App\Module\User\Repository\UserRepository::class),
new Reference(\App\Module\User\Repository\UserOAuthIdentityRepository::class),
])->setPublic(true);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OAuthController is registered with 3 constructor arguments, but OAuthController::__construct() currently takes no parameters. This will cause container instantiation to fail when the /oauth/callback route resolves the controller via the container. Either update OAuthController to accept these dependencies (and assign them), or remove the arguments from the service definition and let it construct its own dependencies (prefer the former).

Suggested change
->setArguments([
new Reference(\App\Module\Admin\Repository\AuthConfigRepository::class),
new Reference(\App\Module\User\Repository\UserRepository::class),
new Reference(\App\Module\User\Repository\UserOAuthIdentityRepository::class),
])->setPublic(true);
->setPublic(true);

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +107
$container->register(\App\Module\Patient\PatientController::class)
->setArguments([
new Reference(\App\Module\Patient\Repository\PatientRepository::class),
new Reference(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class),
new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class),
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PatientController is registered with only 6 arguments, but PatientController::__construct() now declares 8 parameters (it also accepts ClaimRepository and InvoiceRepository). If the controller is resolved from the container, this will raise an ArgumentCountError at runtime. Add the missing ClaimRepository and InvoiceRepository references (or remove the explicit argument wiring and rely on defaults/updated DI).

Copilot uses AI. Check for mistakes.

// Appointment module
$container->register(\App\Module\Appointment\Repository\AppointmentRepository::class)->setPublic(true);
$container->register(\App\Module\Patient\Repository\PatientRepository::class)->setPublic(true);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-registers PatientRepository without constructor arguments, overriding the earlier definition that injects PDO and AuditLogger. As a result, PatientRepository will be built without the audit logger (and potentially without the intended PDO service). Remove this duplicate registration, or keep a single registration that consistently wires PDO and AuditLogger.

Suggested change
$container->register(\App\Module\Patient\Repository\PatientRepository::class)->setPublic(true);

Copilot uses AI. Check for mistakes.
])->setPublic(true);

// Room
$container->register(\App\Module\Room\RoomRepository::class)->setPublic(true);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Service definition references \App\Module\Room\RoomRepository::class, but the repository class is actually in \App\Module\Room\Repository\RoomRepository (and that is what other service definitions reference). This incorrect FQCN can break container compilation or service resolution; remove it or change it to the correct class.

Suggested change
$container->register(\App\Module\Room\RoomRepository::class)->setPublic(true);
$container->register(\App\Module\Room\Repository\RoomRepository::class)->setPublic(true);

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +25
$container->compile();

Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContainerBuilder::compile() is executed on every request (called from public/index.php). Symfony container compilation is relatively expensive and typically meant to be done once and cached (dumped PHP container) for production. Consider adding an environment guard (compile only in debug) and/or dumping/loading a cached compiled container from disk to avoid per-request compilation overhead.

Suggested change
$container->compile();
$debugValue = getenv('APP_DEBUG');
if ($debugValue === false && isset($_ENV['APP_DEBUG'])) {
$debugValue = $_ENV['APP_DEBUG'];
}
$isDebug = false;
if ($debugValue !== false && $debugValue !== null) {
$bool = filter_var($debugValue, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE);
$isDebug = ($bool === null) ? (bool) $debugValue : $bool;
}
if ($isDebug) {
$container->compile();
}

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +55
} catch (\Throwable $e) {
$detail = $e->getMessage() . "\n\n" . $e->getTraceAsString();
$content = View::render('errors/error.html.twig', ['message' => 'Internal Server Error', 'detail' => $detail]);
$response = new Response($content, 500);
$response->headers->set('Content-Type', 'text/html; charset=utf-8');
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Router::dispatch catch block builds a detailed error string using $e->getMessage() and $e->getTraceAsString() and passes it as the detail variable into View::render('errors/error.html.twig', ...). The errors/error.html.twig template unconditionally renders detail when it is non-empty, which means any failure during controller resolution (e.g. DI misconfiguration, database connection errors, or unexpected exceptions) will expose full stack traces and internal error messages to end users in all environments. Instead, capture and log the full exception details server-side and only render a generic error message to the client (and gate any verbose details behind a dedicated debug flag that is disabled in production).

Copilot uses AI. Check for mistakes.
- Fix HrmRepository FQCN: Department\Repository\HrmRepository -> Hrm\Repository\HrmRepository
- Fix RoomRepository FQCN: Room\RoomRepository -> Room\Repository\RoomRepository
- Fix View::render() -> View::renderToString() (void return used as value)
- Fix PSR-12 line length violation (multi-line condition formatting)
- Fix line ending violations (CRLF -> LF)
- Add missing use imports (EventDispatcherService, EntityChangedEvent) in 5 repositories
- Fix View::render() (void) to View::renderToString() in Router.php
- Narrow TranslationService::translator type from interface to concrete Translator
- Fix Assert\Type constructor to use named arguments instead of array
- Add ServiceRepository::delete() method for service deletion
- Update EntityChangedEvent to match 5-param pattern used by all 16 callers
- Update AuditLogger listener to use new EntityChangedEvent properties
- Add missing claimRepository/invoiceRepository properties to PatientController
- Add AuthGuard import to AuthController
- Remove always-true redundant inner if-check in AppointmentController
- Remove always-true right side of && in PrescriptionPolicy
- Suppress property.onlyWritten for DI properties intended for future use

PHPStan: 64 errors -> 0 errors
PHPCS: 0 errors (142 pre-existing line-length warnings remain)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants