From b7875ebc243d15559c7254e57610bfaf1c318659 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:45:10 +0200 Subject: [PATCH 01/52] chore(composer): Add symfony/dependency-injection to composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 2efdbe7..b1df057 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "symfony/process": "^7.4", "symfony/http-foundation": "^7.4", "symfony/event-dispatcher": "^7.4", + "symfony/dependency-injection": "^7.0", "symfony/validator": "^7.4" }, "require-dev": { From 0601b8807309b6ee1eea9489119bdb263e226838 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:45:13 +0200 Subject: [PATCH 02/52] chore(composer): Update composer.lock after adding symfony/dependency-injection --- composer.lock | 167 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index ef14dce..1481b5c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8a0e3d3ecc783e9a6a4070f986ddc128", + "content-hash": "751bde90085dea1841ccd458c3c4438b", "packages": [ { "name": "cakephp/chronos", @@ -3034,6 +3034,90 @@ ], "time": "2025-07-15T17:58:03+00:00" }, + { + "name": "symfony/dependency-injection", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-27T16:16:02+00:00" + }, { "name": "symfony/event-dispatcher", "version": "v7.4.4", @@ -4403,6 +4487,87 @@ ], "time": "2026-01-27T08:59:58+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-11T10:15:23+00:00" + }, { "name": "symfony/yaml", "version": "v7.4.1", From 6273815e17616db657001231e8658c23abd71abc Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:46:55 +0200 Subject: [PATCH 03/52] refactor(audit): Allow injecting PDO into AuditLogger (backwards compatible) --- src/Core/Service/AuditLogger.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Service/AuditLogger.php b/src/Core/Service/AuditLogger.php index f9d66c0..b5e9d34 100644 --- a/src/Core/Service/AuditLogger.php +++ b/src/Core/Service/AuditLogger.php @@ -9,9 +9,9 @@ class AuditLogger { private PDO $pdo; - public function __construct() + public function __construct(?PDO $pdo = null) { - $this->pdo = Database::getInstance(); + $this->pdo = $pdo ?? Database::getInstance(); } public function log(string $entityType, int $entityId, string $action, ?string $oldValue = null, ?string $newValue = null, ?int $userId = null): bool From 454ecaaf0c20f5c622289a66fbdb255b8bac4d82 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:47:16 +0200 Subject: [PATCH 04/52] feat(di): Add ContainerFactory to build DI container and load service config --- src/Infrastructure/DI/ContainerFactory.php | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/Infrastructure/DI/ContainerFactory.php diff --git a/src/Infrastructure/DI/ContainerFactory.php b/src/Infrastructure/DI/ContainerFactory.php new file mode 100644 index 0000000..810c289 --- /dev/null +++ b/src/Infrastructure/DI/ContainerFactory.php @@ -0,0 +1,28 @@ +setParameter('project_dir', $projectDir); + + $servicesFile = $projectDir . '/config/services.php'; + if (file_exists($servicesFile)) { + $config = require $servicesFile; + if (is_callable($config)) { + $config($container); + } + } + + $container->compile(); + + return $container; + } +} From 23966b862b09a1c636ca2680fd21bb4f9fad0c1d Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:47:53 +0200 Subject: [PATCH 05/52] feat(di): Add basic service definitions (PDO, TranslationService, AuditLogger) --- config/services.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 config/services.php diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..f02d344 --- /dev/null +++ b/config/services.php @@ -0,0 +1,23 @@ +register('pdo', PDO::class) + ->setFactory([\App\Database\Database::class, 'getInstance']) + ->setPublic(true); + + // Core services + $container->register(\App\Core\Service\TranslationService::class) + ->setPublic(true); + + $container->register(\App\Core\Service\AuditLogger::class) + ->setArguments([new Reference('pdo')]) + ->setPublic(true); + + // Example: register repositories (kept simple for now) + $container->register(\App\Module\Patient\Repository\PatientRepository::class) + ->setPublic(true); +}; From 082a105f18379df807569e542d7fd94db366fc77 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:49:19 +0200 Subject: [PATCH 06/52] feat(di): Register EventDispatcher service in services.php --- config/services.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/services.php b/config/services.php index f02d344..4b9ce74 100644 --- a/config/services.php +++ b/config/services.php @@ -10,6 +10,10 @@ ->setPublic(true); // Core services + // Event dispatcher + $container->register(\Symfony\Component\EventDispatcher\EventDispatcher::class) + ->setPublic(true); + $container->register(\App\Core\Service\TranslationService::class) ->setPublic(true); From 4ab82a89ac2cae0798df350874e7c8e3403e2ede Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:49:44 +0200 Subject: [PATCH 07/52] refactor(view): Allow injecting TranslationService and preserve fallback behavior --- src/Core/Http/View.php | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Core/Http/View.php b/src/Core/Http/View.php index a37e87e..119e3eb 100644 --- a/src/Core/Http/View.php +++ b/src/Core/Http/View.php @@ -14,29 +14,33 @@ class View private static array $twigGlobals = []; private static ?TranslationService $translationService = null; + public static function setTranslationService(TranslationService $service): void + { + self::$translationService = $service; + // Ensure globals are loaded so locale detection can use system settings + self::loadTwigGlobals(); + + $preferredLocale = (self::$twigGlobals['system_locale'] ?? null) ?? self::detectBrowserLanguage() ?? 'uk'; + $rawAvailableLocales = array_keys(self::$translationService->getAvailableLocales()); + $finalLocale = 'uk'; + if (in_array($preferredLocale, $rawAvailableLocales)) { + $finalLocale = $preferredLocale; + } + self::$translationService->setLocale($finalLocale); + } + public static function getTranslationService(): TranslationService { if (self::$translationService === null) { self::$translationService = new TranslationService(); - - // Ensure globals are loaded to get system_locale + // If setTranslationService wasn't used, preserve old behavior self::loadTwigGlobals(); - - // Determine initial locale preference $preferredLocale = (self::$twigGlobals['system_locale'] ?? null) ?? self::detectBrowserLanguage() ?? 'uk'; - - // Get actual available locales from the TranslationService instance - // Note: Calling getAvailableLocales() here on the instance is safe - // as the TranslationService is already initialized above. $rawAvailableLocales = array_keys(self::$translationService->getAvailableLocales()); - - // Validate preferredLocale against truly available locales - $finalLocale = 'uk'; // Default fallback + $finalLocale = 'uk'; if (in_array($preferredLocale, $rawAvailableLocales)) { $finalLocale = $preferredLocale; } - // If preferredLocale is not available, $finalLocale remains 'uk' - self::$translationService->setLocale($finalLocale); } return self::$translationService; From a96847e9fd6c2823987c50ec127bdc61e8950ba9 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:50:05 +0200 Subject: [PATCH 08/52] feat(bootstrap): Use DI container for EventDispatcher and TranslationService --- public/index.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index aef91d1..1858f5d 100644 --- a/public/index.php +++ b/public/index.php @@ -17,6 +17,8 @@ require_once __DIR__ . '/../vendor/autoload.php'; +use App\Infrastructure\DI\ContainerFactory; + use App\Controller\InstallController; use App\Controller\PageController; use App\Core\Exception\ExitException; @@ -50,8 +52,17 @@ session_set_cookie_params($cookieParams); } } + session_start(); +$container = ContainerFactory::createContainer(); + +// Get EventDispatcher from container (falls back to manual if not available) +$eventDispatcher = null; +if ($container->has(\Symfony\Component\EventDispatcher\EventDispatcher::class)) { + $eventDispatcher = $container->get(\Symfony\Component\EventDispatcher\EventDispatcher::class); +} + $request = Request::createFromGlobals(); $router = new Router(); @@ -60,7 +71,9 @@ $policyRegistry = new \App\Core\Auth\PolicyRegistry(); $moduleManager = new ModuleManager(); -$eventDispatcher = new EventDispatcher(); +if ($eventDispatcher === null) { + $eventDispatcher = new EventDispatcher(); +} $moduleLoader = new ModuleLoader($moduleManager); $moduleLoader->loadAll(); @@ -70,6 +83,11 @@ $moduleManager->registerPolicies($policyRegistry); $moduleManager->registerEventListeners($eventDispatcher); EventDispatcherService::setDispatcher($eventDispatcher); + +// Inject translation service into View if present in container +if ($container->has(\App\Core\Service\TranslationService::class)) { + \App\Core\Http\View::setTranslationService($container->get(\App\Core\Service\TranslationService::class)); +} $moduleManager->registerRoutes($router); \App\Core\Auth\Gate::setPermissionRegistry($permissionRegistry); From c73cc85b4292feb833b126e99124257b51e9d50a Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:51:56 +0200 Subject: [PATCH 09/52] feat(router): Accept PSR container and resolve controllers from DI when available --- src/Core/Http/Router.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Core/Http/Router.php b/src/Core/Http/Router.php index f005805..fb879b0 100644 --- a/src/Core/Http/Router.php +++ b/src/Core/Http/Router.php @@ -2,6 +2,7 @@ namespace App\Core\Http; +use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use App\Core\Http\View; @@ -9,6 +10,12 @@ class Router { private array $routes = []; + private ?ContainerInterface $container; + + public function __construct(?ContainerInterface $container = null) + { + $this->container = $container; + } public function add(string $method, string $path, callable|array $handler): void { @@ -34,7 +41,11 @@ public function dispatch(Request $request): Response $handler = $route['handler']; if (is_array($handler) && is_string($handler[0])) { - $controller = new $handler[0](); + if ($this->container && $this->container->has($handler[0])) { + $controller = $this->container->get($handler[0]); + } else { + $controller = new $handler[0](); + } $callback = [$controller, $handler[1]]; } else { $callback = $handler; From 9dfd073088728e7ff3ebcb2a9b4615fe579c88a5 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:52:22 +0200 Subject: [PATCH 10/52] refactor(bootstrap): Pass DI container to Router for controller resolution --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index 1858f5d..2de611f 100644 --- a/public/index.php +++ b/public/index.php @@ -65,7 +65,7 @@ $request = Request::createFromGlobals(); -$router = new Router(); +$router = new Router($container); $permissionRegistry = new \App\Core\Auth\PermissionRegistry(); $policyRegistry = new \App\Core\Auth\PolicyRegistry(); From 0e52fdcdccab0d9d6d7e4fc72310282a264ae749 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:53:10 +0200 Subject: [PATCH 11/52] refactor(patient): Allow injecting PDO and AuditLogger; prefer injected logger when available --- .../Patient/Repository/PatientRepository.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Module/Patient/Repository/PatientRepository.php b/src/Module/Patient/Repository/PatientRepository.php index b84d157..6ce878c 100644 --- a/src/Module/Patient/Repository/PatientRepository.php +++ b/src/Module/Patient/Repository/PatientRepository.php @@ -3,6 +3,7 @@ namespace App\Module\Patient\Repository; use App\Core\Event\EventDispatcherService; +use App\Core\Service\AuditLogger as CoreAuditLogger; use App\Event\EntityChangedEvent; use App\Database\Database; use PDO; @@ -10,11 +11,13 @@ class PatientRepository implements PatientRepositoryInterface { private PDO $pdo; + private ?CoreAuditLogger $auditLogger = null; private ?string $lastError = null; - public function __construct() + public function __construct(?PDO $pdo = null, ?CoreAuditLogger $auditLogger = null) { - $this->pdo = Database::getInstance(); + $this->pdo = $pdo ?? Database::getInstance(); + $this->auditLogger = $auditLogger; } public function findAll(string $searchTerm = ''): array @@ -227,9 +230,8 @@ public function update(int $id, array $data): bool } if ($success && $oldStatus !== ($data['status'] ?? 'active')) { - $auditLogger = new AuditLogger(); - // Assuming current user ID is available, for now, null or placeholder - $auditLogger->log('patient', $id, 'status_change', $oldStatus, $data['status'] ?? 'active'); + $logger = $this->auditLogger ?? new CoreAuditLogger(); + $logger->log('patient', $id, 'status_change', $oldStatus, $data['status'] ?? 'active'); } if ($success) { @@ -259,8 +261,8 @@ public function updateStatus(int $id, string $status): bool $success = $stmt->execute([':status' => $status, ':id' => $id]); if ($success && $oldStatus !== $status) { - $auditLogger = new AuditLogger(); - $auditLogger->log('patient', $id, 'status_change', $oldStatus, $status); + $logger = $this->auditLogger ?? new CoreAuditLogger(); + $logger->log('patient', $id, 'status_change', $oldStatus, $status); EventDispatcherService::getDispatcher()->dispatch(new EntityChangedEvent('patient', $id, 'update', $oldPatient, ['status' => $status])); } return $success; From 3ddde17e4abc894a1b3618527a3a37d4a5f2a62f Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:53:43 +0200 Subject: [PATCH 12/52] feat(di): Wire PatientRepository and AuditLogger to use PDO; register controllers --- config/services.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/config/services.php b/config/services.php index 4b9ce74..7e3f29c 100644 --- a/config/services.php +++ b/config/services.php @@ -21,7 +21,14 @@ ->setArguments([new Reference('pdo')]) ->setPublic(true); - // Example: register repositories (kept simple for now) + // Repositories $container->register(\App\Module\Patient\Repository\PatientRepository::class) + ->setArguments([new Reference('pdo'), new Reference(\App\Core\Service\AuditLogger::class)]) + ->setPublic(true); + + // Controllers (allow resolving controllers from container) + $container->register(\App\Controller\PageController::class) + ->setPublic(true); + $container->register(\App\Controller\InstallController::class) ->setPublic(true); }; From 4ead5798f9691ce5cfe275312cc15caefe9a444e Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:55:35 +0200 Subject: [PATCH 13/52] refactor(medical): Inject dependencies into MedicalRecordController and register services --- config/services.php | 23 +++++++++++++++++ .../MedicalRecord/MedicalRecordController.php | 25 ++++++++++++------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/config/services.php b/config/services.php index 7e3f29c..c517167 100644 --- a/config/services.php +++ b/config/services.php @@ -31,4 +31,27 @@ ->setPublic(true); $container->register(\App\Controller\InstallController::class) ->setPublic(true); + + // MedicalRecord module services + $container->register(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class) + ->setPublic(true); + $container->register(\App\Module\Appointment\Repository\AppointmentRepository::class) + ->setPublic(true); + $container->register(\App\Module\LabOrder\Repository\LabOrderRepository::class) + ->setPublic(true); + $container->register(\App\Module\ClinicalReference\Repository\IcdCodeRepository::class) + ->setPublic(true); + $container->register(\App\Module\ClinicalReference\Repository\InterventionCodeRepository::class) + ->setPublic(true); + $container->register(\App\Module\MedicalRecord\MedicalRecordController::class) + ->setArguments([ + new Reference(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class), + new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class), + new Reference(\App\Module\LabOrder\Repository\LabOrderRepository::class), + new Reference(\App\Module\ClinicalReference\Repository\IcdCodeRepository::class), + new Reference(\App\Module\ClinicalReference\Repository\InterventionCodeRepository::class), + new Reference(\App\Core\Service\AttachmentService::class), + new Reference(\App\Core\Service\AuditLogger::class), + ]) + ->setPublic(true); }; diff --git a/src/Module/MedicalRecord/MedicalRecordController.php b/src/Module/MedicalRecord/MedicalRecordController.php index 9cae16a..1c5904b 100644 --- a/src/Module/MedicalRecord/MedicalRecordController.php +++ b/src/Module/MedicalRecord/MedicalRecordController.php @@ -24,15 +24,22 @@ class MedicalRecordController private AttachmentService $attachmentService; private AuditLogger $auditLogger; - public function __construct() - { - $this->medicalRecordRepository = new MedicalRecordRepository(); - $this->appointmentRepository = new AppointmentRepository(); - $this->labOrderRepository = new LabOrderRepository(); - $this->icdCodeRepository = new IcdCodeRepository(); - $this->interventionCodeRepository = new InterventionCodeRepository(); - $this->attachmentService = new AttachmentService(); - $this->auditLogger = new AuditLogger(); + public function __construct( + ?MedicalRecordRepository $medicalRecordRepository = null, + ?AppointmentRepository $appointmentRepository = null, + ?LabOrderRepository $labOrderRepository = null, + ?IcdCodeRepository $icdCodeRepository = null, + ?InterventionCodeRepository $interventionCodeRepository = null, + ?AttachmentService $attachmentService = null, + ?AuditLogger $auditLogger = null + ) { + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->labOrderRepository = $labOrderRepository ?? new LabOrderRepository(); + $this->icdCodeRepository = $icdCodeRepository ?? new IcdCodeRepository(); + $this->interventionCodeRepository = $interventionCodeRepository ?? new InterventionCodeRepository(); + $this->attachmentService = $attachmentService ?? new AttachmentService(); + $this->auditLogger = $auditLogger ?? new AuditLogger(); } public function create(): void From 1a6443b8cb1d5316ae9d833baf8650da9740087d Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 19:58:13 +0200 Subject: [PATCH 14/52] refactor(patient): Inject repositories and insurance service into PatientController; register services --- config/services.php | 29 ++++++++++++++++++++++++ src/Module/Patient/PatientController.php | 23 +++++++++++-------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/config/services.php b/config/services.php index c517167..1a1e2e0 100644 --- a/config/services.php +++ b/config/services.php @@ -54,4 +54,33 @@ new Reference(\App\Core\Service\AuditLogger::class), ]) ->setPublic(true); + + // Patient module + $container->register(\App\Module\Patient\Repository\PatientRepository::class) + ->setArguments([new Reference('pdo'), new Reference(\App\Core\Service\AuditLogger::class)]) + ->setPublic(true); + $container->register(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class)->setPublic(true); + $container->register(\App\Module\Appointment\Repository\AppointmentRepository::class)->setPublic(true); + $container->register(\App\Module\Insurance\Repository\InsuranceCompanyRepository::class)->setPublic(true); + $container->register(\App\Module\Insurance\Repository\PatientInsurancePolicyRepository::class)->setPublic(true); + $container->register(\App\Module\Insurance\Repository\ClaimRepository::class)->setPublic(true); + $container->register(\App\Module\Billing\Repository\InvoiceRepository::class)->setPublic(true); + $container->register(\App\Module\Insurance\Service\InsuranceService::class) + ->setArguments([ + new Reference(\App\Module\Insurance\Repository\InsuranceCompanyRepository::class), + new Reference(\App\Module\Insurance\Repository\PatientInsurancePolicyRepository::class), + new Reference(\App\Module\Insurance\Repository\ClaimRepository::class), + new Reference(\App\Module\Billing\Repository\InvoiceRepository::class), + ]) + ->setPublic(true); + $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), + new Reference(\App\Module\Insurance\Service\InsuranceService::class), + new Reference(\App\Module\Insurance\Repository\InsuranceCompanyRepository::class), + new Reference(\App\Module\Insurance\Repository\PatientInsurancePolicyRepository::class), + ]) + ->setPublic(true); }; diff --git a/src/Module/Patient/PatientController.php b/src/Module/Patient/PatientController.php index 68e8fa8..2c7ec39 100644 --- a/src/Module/Patient/PatientController.php +++ b/src/Module/Patient/PatientController.php @@ -25,15 +25,20 @@ class PatientController private InsuranceService $insuranceService; private InsuranceCompanyRepository $insuranceCompanyRepository; private PatientInsurancePolicyRepository $patientInsurancePolicyRepository; - - public function __construct() - { - $this->patientRepository = new PatientRepository(); - $this->medicalRecordRepository = new MedicalRecordRepository(); - $this->appointmentRepository = new AppointmentRepository(); - $this->insuranceCompanyRepository = new \App\Module\Insurance\Repository\InsuranceCompanyRepository(); - $this->patientInsurancePolicyRepository = new \App\Module\Insurance\Repository\PatientInsurancePolicyRepository(); - $this->insuranceService = new \App\Module\Insurance\Service\InsuranceService( + public function __construct( + ?PatientRepository $patientRepository = null, + ?MedicalRecordRepository $medicalRecordRepository = null, + ?AppointmentRepository $appointmentRepository = null, + ?InsuranceService $insuranceService = null, + ?InsuranceCompanyRepository $insuranceCompanyRepository = null, + ?PatientInsurancePolicyRepository $patientInsurancePolicyRepository = null + ) { + $this->patientRepository = $patientRepository ?? new PatientRepository(); + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->insuranceCompanyRepository = $insuranceCompanyRepository ?? new \App\Module\Insurance\Repository\InsuranceCompanyRepository(); + $this->patientInsurancePolicyRepository = $patientInsurancePolicyRepository ?? new \App\Module\Insurance\Repository\PatientInsurancePolicyRepository(); + $this->insuranceService = $insuranceService ?? new \App\Module\Insurance\Service\InsuranceService( $this->insuranceCompanyRepository, $this->patientInsurancePolicyRepository, new \App\Module\Insurance\Repository\ClaimRepository(), From 30b6b49d8c97d8afbb18c8168b74e31a801c5eb6 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:01:31 +0200 Subject: [PATCH 15/52] refactor(core): Make NotificationService accept PDO --- src/Core/Service/NotificationService.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Core/Service/NotificationService.php b/src/Core/Service/NotificationService.php index dd1031c..97733bd 100644 --- a/src/Core/Service/NotificationService.php +++ b/src/Core/Service/NotificationService.php @@ -8,10 +8,9 @@ class NotificationService { private PDO $pdo; - - public function __construct() + public function __construct(?PDO $pdo = null) { - $this->pdo = Database::getInstance(); + $this->pdo = $pdo ?? Database::getInstance(); } public function createNotification(int $userId, string $message): bool From a4649361a8569d991f85f1cf0a60895153c3872a Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:01:31 +0200 Subject: [PATCH 16/52] refactor(core): Make AttachmentService accept PDO and upload dir injection --- src/Core/Service/AttachmentService.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Core/Service/AttachmentService.php b/src/Core/Service/AttachmentService.php index c1f0951..61a187b 100644 --- a/src/Core/Service/AttachmentService.php +++ b/src/Core/Service/AttachmentService.php @@ -9,10 +9,12 @@ class AttachmentService { private PDO $pdo; private string $uploadDir = __DIR__ . '/../../../uploads'; - - public function __construct() + public function __construct(?PDO $pdo = null, ?string $uploadDir = null) { - $this->pdo = Database::getInstance(); + $this->pdo = $pdo ?? Database::getInstance(); + if ($uploadDir !== null) { + $this->uploadDir = $uploadDir; + } if (!is_dir($this->uploadDir)) { mkdir($this->uploadDir, 0775, true); } From 993c493dcf5892d91c66076c9b6824b049d75614 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:01:31 +0200 Subject: [PATCH 17/52] refactor(lab+di): Register core services; inject dependencies into LabOrder module --- config/services.php | 26 ++++++++++++++++++++++ src/Module/LabOrder/LabOrderController.php | 23 +++++++++++-------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/config/services.php b/config/services.php index 1a1e2e0..e04a0c0 100644 --- a/config/services.php +++ b/config/services.php @@ -43,6 +43,16 @@ ->setPublic(true); $container->register(\App\Module\ClinicalReference\Repository\InterventionCodeRepository::class) ->setPublic(true); + + // Core utility services + $container->register(\App\Core\Service\AttachmentService::class) + ->setArguments([new Reference('pdo')]) + ->setPublic(true); + $container->register(\App\Core\Service\NotificationService::class) + ->setArguments([new Reference('pdo')]) + ->setPublic(true); + $container->register(\App\Core\Service\QrCodeGenerator::class) + ->setPublic(true); $container->register(\App\Module\MedicalRecord\MedicalRecordController::class) ->setArguments([ new Reference(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class), @@ -83,4 +93,20 @@ new Reference(\App\Module\Insurance\Repository\PatientInsurancePolicyRepository::class), ]) ->setPublic(true); + // LabOrder module + $container->register(\App\Module\LabOrder\Repository\LabOrderRepository::class)->setPublic(true); + $container->register(\App\Module\LabOrder\Repository\LabResourceRepository::class)->setPublic(true); + $container->register(\App\Module\User\Repository\UserRepository::class)->setPublic(true); + $container->register(\App\Module\LabOrder\Service\LabImportService::class) + ->setPublic(true); + $container->register(\App\Module\LabOrder\LabOrderController::class) + ->setArguments([ + new Reference(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class), + new Reference(\App\Module\LabOrder\Repository\LabOrderRepository::class), + new Reference(\App\Module\User\Repository\UserRepository::class), + new Reference(\App\Core\Service\NotificationService::class), + new Reference(\App\Core\Service\QrCodeGenerator::class), + new Reference(\App\Module\LabOrder\Service\LabImportService::class), + ]) + ->setPublic(true); }; diff --git a/src/Module/LabOrder/LabOrderController.php b/src/Module/LabOrder/LabOrderController.php index fa53d24..85433f2 100644 --- a/src/Module/LabOrder/LabOrderController.php +++ b/src/Module/LabOrder/LabOrderController.php @@ -23,15 +23,20 @@ class LabOrderController private NotificationService $notificationService; private QrCodeGenerator $qrCodeGenerator; private LabImportService $labImportService; - - public function __construct() - { - $this->medicalRecordRepository = new MedicalRecordRepository(); - $this->labOrderRepository = new LabOrderRepository(); - $this->userRepository = new UserRepository(); - $this->notificationService = new NotificationService(); - $this->qrCodeGenerator = new QrCodeGenerator(); - $this->labImportService = new LabImportService(); + public function __construct( + ?MedicalRecordRepository $medicalRecordRepository = null, + ?LabOrderRepository $labOrderRepository = null, + ?UserRepository $userRepository = null, + ?NotificationService $notificationService = null, + ?QrCodeGenerator $qrCodeGenerator = null, + ?LabImportService $labImportService = null + ) { + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); + $this->labOrderRepository = $labOrderRepository ?? new LabOrderRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->notificationService = $notificationService ?? new NotificationService(); + $this->qrCodeGenerator = $qrCodeGenerator ?? new QrCodeGenerator(); + $this->labImportService = $labImportService ?? new LabImportService(); } public function create(): void From 6d1a3410ee83e9f89cfe2ed0039ae1528a6bc85f Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:08:07 +0200 Subject: [PATCH 18/52] refactor(user): Make MfaService DI-compatible; inject into MfaController --- src/Module/User/MfaController.php | 7 +++---- src/Module/User/MfaService.php | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Module/User/MfaController.php b/src/Module/User/MfaController.php index e2a5ded..c072163 100644 --- a/src/Module/User/MfaController.php +++ b/src/Module/User/MfaController.php @@ -11,11 +11,10 @@ class MfaController { private MfaService $mfaService; private \App\Module\User\Repository\UserRepository $userRepository; - - public function __construct() + public function __construct(?MfaService $mfaService = null, ?\App\Module\User\Repository\UserRepository $userRepository = null) { - $this->mfaService = new MfaService(); - $this->userRepository = new UserRepository(); + $this->mfaService = $mfaService ?? new MfaService(); + $this->userRepository = $userRepository ?? new UserRepository(); } private function prepareHotpSetup(int $userId, array &$secret, array &$backupCodes, int &$counter, string &$qrCode): void diff --git a/src/Module/User/MfaService.php b/src/Module/User/MfaService.php index 2b2379f..bf18d22 100644 --- a/src/Module/User/MfaService.php +++ b/src/Module/User/MfaService.php @@ -13,12 +13,11 @@ class MfaService private PDO $db; private QrCodeGenerator $qrCodeGenerator; private string $issuerName; - - public function __construct() + public function __construct(?PDO $db = null, ?QrCodeGenerator $qrCodeGenerator = null, ?string $issuerName = null) { - $this->db = Database::getInstance(); - $this->qrCodeGenerator = new QrCodeGenerator(); - $this->issuerName = $_ENV['APP_NAME'] ?? 'Clinic'; + $this->db = $db ?? Database::getInstance(); + $this->qrCodeGenerator = $qrCodeGenerator ?? new QrCodeGenerator(); + $this->issuerName = $issuerName ?? ($_ENV['APP_NAME'] ?? 'Clinic'); } public function generateSecret(): string From fd1286c938fb5c8c6ce16ce751616d55cca6569b Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:08:07 +0200 Subject: [PATCH 19/52] refactor(user): Inject MfaService into AuthController; prepare OAuthController for DI --- src/Module/User/AuthController.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Module/User/AuthController.php b/src/Module/User/AuthController.php index 1ad5a59..763ff32 100644 --- a/src/Module/User/AuthController.php +++ b/src/Module/User/AuthController.php @@ -16,12 +16,14 @@ class AuthController private UserRepository $userRepository; private AuthConfigRepository $authConfigRepository; private RoleRepository $roleRepository; + private ?MfaService $mfaService = null; - public function __construct() + public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?RoleRepository $roleRepository = null, ?MfaService $mfaService = null) { - $this->userRepository = new UserRepository(); - $this->authConfigRepository = new AuthConfigRepository(); - $this->roleRepository = new RoleRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->authConfigRepository = $authConfigRepository ?? new AuthConfigRepository(); + $this->roleRepository = $roleRepository ?? new RoleRepository(); + $this->mfaService = $mfaService ?? new MfaService(); } public function showLoginForm(): void @@ -63,7 +65,7 @@ public function login(): void $role = $user ? $this->roleRepository->findById((int)$user['role_id']) : null; if ($user && password_verify($password, $user['password_hash'])) { - $mfaService = new MfaService(); + $mfaService = $this->mfaService ?? new MfaService(); $settingsRepository = new \App\Core\Repository\SettingsRepository(); $mfaPolicy = $settingsRepository->getMfaPolicy(); $mfaForceRoles = $settingsRepository->getMfaForceRoles(); From 773d792815a1c61204a11c1e42517fd6a0f9ebce Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:08:07 +0200 Subject: [PATCH 20/52] refactor(user): Allow injecting repositories into UserController --- src/Module/User/UserController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Module/User/UserController.php b/src/Module/User/UserController.php index dc1a3e0..5965668 100644 --- a/src/Module/User/UserController.php +++ b/src/Module/User/UserController.php @@ -16,12 +16,12 @@ class UserController private UserOAuthIdentityRepository $userOAuthIdentityRepository; private HrmRepository $hrmRepository; - public function __construct() + public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?UserOAuthIdentityRepository $userOAuthIdentityRepository = null, ?HrmRepository $hrmRepository = null) { - $this->userRepository = new UserRepository(); - $this->authConfigRepository = new AuthConfigRepository(); - $this->userOAuthIdentityRepository = new UserOAuthIdentityRepository(); - $this->hrmRepository = new HrmRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->authConfigRepository = $authConfigRepository ?? new AuthConfigRepository(); + $this->userOAuthIdentityRepository = $userOAuthIdentityRepository ?? new UserOAuthIdentityRepository(); + $this->hrmRepository = $hrmRepository ?? new HrmRepository(); } public function profile(): void From 357396239d83fbf1b3172ca9f1f26a5c11c930f7 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:09:25 +0200 Subject: [PATCH 21/52] refactor(appointment): Allow injecting repositories and SchedulingService into AppointmentController --- .../Appointment/AppointmentController.php | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/Module/Appointment/AppointmentController.php b/src/Module/Appointment/AppointmentController.php index 378ed88..044e043 100644 --- a/src/Module/Appointment/AppointmentController.php +++ b/src/Module/Appointment/AppointmentController.php @@ -26,26 +26,37 @@ class AppointmentController private ServiceRepository $serviceRepository; private \App\Module\Room\Repository\RoomRepository $roomRepository; - public function __construct() - { - $this->appointmentRepository = new AppointmentRepository(); - $this->patientRepository = new PatientRepository(); - $this->userRepository = new UserRepository(); - $this->notificationService = new NotificationService(); - - // Dependencies for SchedulingService - $this->serviceRepository = new ServiceRepository(); - $this->roomRepository = new \App\Module\Room\Repository\RoomRepository(); - $doctorScheduleRepository = new DoctorScheduleRepository(); - $scheduleExceptionRepository = new ScheduleExceptionRepository(); - - $this->schedulingService = new SchedulingService( - $doctorScheduleRepository, - $scheduleExceptionRepository, - $this->appointmentRepository, - $this->serviceRepository, - $this->roomRepository - ); + public function __construct( + ?AppointmentRepository $appointmentRepository = null, + ?PatientRepository $patientRepository = null, + ?UserRepository $userRepository = null, + ?NotificationService $notificationService = null, + ?SchedulingService $schedulingService = null, + ?ServiceRepository $serviceRepository = null, + ?\App\Module\Room\Repository\RoomRepository $roomRepository = null + ) { + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->patientRepository = $patientRepository ?? new PatientRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->notificationService = $notificationService ?? new NotificationService(); + + // Scheduling dependencies (allow injecting composed SchedulingService) + $this->serviceRepository = $serviceRepository ?? new ServiceRepository(); + $this->roomRepository = $roomRepository ?? new \App\Module\Room\Repository\RoomRepository(); + + if ($schedulingService !== null) { + $this->schedulingService = $schedulingService; + } else { + $doctorScheduleRepository = new DoctorScheduleRepository(); + $scheduleExceptionRepository = new ScheduleExceptionRepository(); + $this->schedulingService = new SchedulingService( + $doctorScheduleRepository, + $scheduleExceptionRepository, + $this->appointmentRepository, + $this->serviceRepository, + $this->roomRepository + ); + } } public function index(): void From 449f3effdd145c7ce2ef35ffc43c1bfe8101dfbc Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:10:06 +0200 Subject: [PATCH 22/52] feat(di): Register Appointment module services and SchedulingService; wire AppointmentController --- config/services.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/config/services.php b/config/services.php index e04a0c0..655f57c 100644 --- a/config/services.php +++ b/config/services.php @@ -93,6 +93,7 @@ new Reference(\App\Module\Insurance\Repository\PatientInsurancePolicyRepository::class), ]) ->setPublic(true); + // LabOrder module $container->register(\App\Module\LabOrder\Repository\LabOrderRepository::class)->setPublic(true); $container->register(\App\Module\LabOrder\Repository\LabResourceRepository::class)->setPublic(true); @@ -109,4 +110,32 @@ new Reference(\App\Module\LabOrder\Service\LabImportService::class), ]) ->setPublic(true); + + // Appointment module + $container->register(\App\Module\Appointment\Repository\AppointmentRepository::class)->setPublic(true); + $container->register(\App\Module\Patient\Repository\PatientRepository::class)->setPublic(true); + $container->register(\App\Module\User\Repository\UserRepository::class)->setPublic(true); + $container->register(\App\Module\Schedule\Repository\DoctorScheduleRepository::class)->setPublic(true); + $container->register(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class)->setPublic(true); + $container->register(\App\Module\Room\Repository\RoomRepository::class)->setPublic(true); + $container->register(\App\Module\Appointment\Service\SchedulingService::class) + ->setArguments([ + new Reference(\App\Module\Schedule\Repository\DoctorScheduleRepository::class), + new Reference(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class), + new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class), + new Reference(\App\Module\Billing\Repository\ServiceRepository::class), + new Reference(\App\Module\Room\Repository\RoomRepository::class), + ]) + ->setPublic(true); + $container->register(\App\Module\Appointment\AppointmentController::class) + ->setArguments([ + new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class), + new Reference(\App\Module\Patient\Repository\PatientRepository::class), + new Reference(\App\Module\User\Repository\UserRepository::class), + new Reference(\App\Core\Service\NotificationService::class), + new Reference(\App\Module\Appointment\Service\SchedulingService::class), + new Reference(\App\Module\Billing\Repository\ServiceRepository::class), + new Reference(\App\Module\Room\Repository\RoomRepository::class), + ]) + ->setPublic(true); }; From ecfde99bfd47b8147392b074ff2d3e306f6b1b3f Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:11:41 +0200 Subject: [PATCH 23/52] fix(di): Register Billing ServiceRepository required by SchedulingService --- config/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/config/services.php b/config/services.php index 655f57c..f0c703b 100644 --- a/config/services.php +++ b/config/services.php @@ -115,6 +115,7 @@ $container->register(\App\Module\Appointment\Repository\AppointmentRepository::class)->setPublic(true); $container->register(\App\Module\Patient\Repository\PatientRepository::class)->setPublic(true); $container->register(\App\Module\User\Repository\UserRepository::class)->setPublic(true); + $container->register(\App\Module\Billing\Repository\ServiceRepository::class)->setPublic(true); $container->register(\App\Module\Schedule\Repository\DoctorScheduleRepository::class)->setPublic(true); $container->register(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class)->setPublic(true); $container->register(\App\Module\Room\Repository\RoomRepository::class)->setPublic(true); From 96ee313f7331c4b855d6e70ee8329b847ca542e7 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:23:15 +0200 Subject: [PATCH 24/52] fix(di): Use App\Module\Schedule\Service\SchedulingService in services.php --- config/services.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/services.php b/config/services.php index f0c703b..013558e 100644 --- a/config/services.php +++ b/config/services.php @@ -119,7 +119,7 @@ $container->register(\App\Module\Schedule\Repository\DoctorScheduleRepository::class)->setPublic(true); $container->register(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class)->setPublic(true); $container->register(\App\Module\Room\Repository\RoomRepository::class)->setPublic(true); - $container->register(\App\Module\Appointment\Service\SchedulingService::class) + $container->register(\App\Module\Schedule\Service\SchedulingService::class) ->setArguments([ new Reference(\App\Module\Schedule\Repository\DoctorScheduleRepository::class), new Reference(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class), @@ -134,7 +134,7 @@ new Reference(\App\Module\Patient\Repository\PatientRepository::class), new Reference(\App\Module\User\Repository\UserRepository::class), new Reference(\App\Core\Service\NotificationService::class), - new Reference(\App\Module\Appointment\Service\SchedulingService::class), + new Reference(\App\Module\Schedule\Service\SchedulingService::class), new Reference(\App\Module\Billing\Repository\ServiceRepository::class), new Reference(\App\Module\Room\Repository\RoomRepository::class), ]) From 19dd7cfa76a5a52524fec16f4eeae7b8f79f65a7 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:29:20 +0200 Subject: [PATCH 25/52] refactor(di): Make InventoryController DI-capable --- src/Module/Inventory/InventoryController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Module/Inventory/InventoryController.php b/src/Module/Inventory/InventoryController.php index 8b70ec5..6e83009 100644 --- a/src/Module/Inventory/InventoryController.php +++ b/src/Module/Inventory/InventoryController.php @@ -12,9 +12,9 @@ class InventoryController { private InventoryItemRepository $inventoryItemRepository; - public function __construct() + public function __construct(?InventoryItemRepository $inventoryItemRepository = null) { - $this->inventoryItemRepository = new InventoryItemRepository(); + $this->inventoryItemRepository = $inventoryItemRepository ?? new InventoryItemRepository(); } public function index(): void From 654ec3dcf9103db6195e4f22befc9113dce0fb9a Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:29:21 +0200 Subject: [PATCH 26/52] refactor(di): Make DepartmentController DI-capable --- src/Module/Department/DepartmentController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Module/Department/DepartmentController.php b/src/Module/Department/DepartmentController.php index 6e78866..b580b51 100644 --- a/src/Module/Department/DepartmentController.php +++ b/src/Module/Department/DepartmentController.php @@ -14,10 +14,10 @@ class DepartmentController private DepartmentRepository $departmentRepository; private HrmRepository $hrmRepository; - public function __construct() + public function __construct(?DepartmentRepository $departmentRepository = null, ?HrmRepository $hrmRepository = null) { - $this->departmentRepository = new DepartmentRepository(); - $this->hrmRepository = new HrmRepository(); + $this->departmentRepository = $departmentRepository ?? new DepartmentRepository(); + $this->hrmRepository = $hrmRepository ?? new HrmRepository(); } public function index(): void From edec2e1524a8f86b9fb85ceab7b2cfda142b8d66 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:29:21 +0200 Subject: [PATCH 27/52] refactor(di): Make RoomController DI-capable --- src/Module/Room/RoomController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Module/Room/RoomController.php b/src/Module/Room/RoomController.php index 1ddacca..88b35e2 100644 --- a/src/Module/Room/RoomController.php +++ b/src/Module/Room/RoomController.php @@ -12,9 +12,9 @@ class RoomController { private RoomRepository $roomRepository; - public function __construct() + public function __construct(?RoomRepository $roomRepository = null) { - $this->roomRepository = new RoomRepository(); + $this->roomRepository = $roomRepository ?? new RoomRepository(); } public function index(): void From 1f2d56bdf224a66bee78451a3d01aff6a090dc4e Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:29:21 +0200 Subject: [PATCH 28/52] refactor(di): Make PrescriptionController DI-capable --- .../Prescription/PrescriptionController.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Module/Prescription/PrescriptionController.php b/src/Module/Prescription/PrescriptionController.php index 7c92bae..1c4927f 100644 --- a/src/Module/Prescription/PrescriptionController.php +++ b/src/Module/Prescription/PrescriptionController.php @@ -21,13 +21,18 @@ class PrescriptionController private UserRepository $userRepository; private InventoryItemRepository $inventoryItemRepository; - public function __construct() - { - $this->prescriptionRepository = new PrescriptionRepository(); - $this->patientRepository = new PatientRepository(); - $this->medicalRecordRepository = new MedicalRecordRepository(); - $this->userRepository = new UserRepository(); - $this->inventoryItemRepository = new InventoryItemRepository(); + public function __construct( + ?PrescriptionRepository $prescriptionRepository = null, + ?PatientRepository $patientRepository = null, + ?MedicalRecordRepository $medicalRecordRepository = null, + ?UserRepository $userRepository = null, + ?InventoryItemRepository $inventoryItemRepository = null + ) { + $this->prescriptionRepository = $prescriptionRepository ?? new PrescriptionRepository(); + $this->patientRepository = $patientRepository ?? new PatientRepository(); + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->inventoryItemRepository = $inventoryItemRepository ?? new InventoryItemRepository(); } public function index(): void From 948c7e0456bb4db40113b2e1e292ebdd97207013 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:29:21 +0200 Subject: [PATCH 29/52] refactor(di): Make InsuranceController DI-capable --- src/Module/Insurance/InsuranceController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Module/Insurance/InsuranceController.php b/src/Module/Insurance/InsuranceController.php index 867bb18..abcd3a4 100644 --- a/src/Module/Insurance/InsuranceController.php +++ b/src/Module/Insurance/InsuranceController.php @@ -16,9 +16,9 @@ class InsuranceController { private InsuranceService $insuranceService; - public function __construct() + public function __construct(?InsuranceService $insuranceService = null) { - $this->insuranceService = new InsuranceService( + $this->insuranceService = $insuranceService ?? new InsuranceService( new \App\Module\Insurance\Repository\InsuranceCompanyRepository(), new \App\Module\Insurance\Repository\PatientInsurancePolicyRepository(), new \App\Module\Insurance\Repository\ClaimRepository(), From 5def83fad02021aca7709034013d31ab8fe9ec63 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 20:29:21 +0200 Subject: [PATCH 30/52] refactor(di): Register repositories/controllers (Inventory, Department, Room, Prescription, Insurance) --- config/services.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/config/services.php b/config/services.php index 013558e..0398342 100644 --- a/config/services.php +++ b/config/services.php @@ -97,6 +97,7 @@ // LabOrder module $container->register(\App\Module\LabOrder\Repository\LabOrderRepository::class)->setPublic(true); $container->register(\App\Module\LabOrder\Repository\LabResourceRepository::class)->setPublic(true); + $container->register(\App\Module\Inventory\Repository\InventoryItemRepository::class)->setPublic(true); $container->register(\App\Module\User\Repository\UserRepository::class)->setPublic(true); $container->register(\App\Module\LabOrder\Service\LabImportService::class) ->setPublic(true); @@ -139,4 +140,43 @@ new Reference(\App\Module\Room\Repository\RoomRepository::class), ]) ->setPublic(true); + + // Inventory + $container->register(\App\Module\Inventory\Repository\InventoryItemRepository::class)->setPublic(true); + $container->register(\App\Module\Inventory\InventoryController::class) + ->setArguments([ + new Reference(\App\Module\Inventory\Repository\InventoryItemRepository::class), + ])->setPublic(true); + + // Department + $container->register(\App\Module\Department\Repository\DepartmentRepository::class)->setPublic(true); + $container->register(\App\Module\Department\Repository\HrmRepository::class)->setPublic(true); + $container->register(\App\Module\Department\DepartmentController::class) + ->setArguments([ + new Reference(\App\Module\Department\Repository\DepartmentRepository::class), + new Reference(\App\Module\Department\Repository\HrmRepository::class), + ])->setPublic(true); + + // Room + $container->register(\App\Module\Room\RoomController::class) + ->setArguments([ + new Reference(\App\Module\Room\Repository\RoomRepository::class), + ])->setPublic(true); + + // Prescription + $container->register(\App\Module\Prescription\Repository\PrescriptionRepository::class)->setPublic(true); + $container->register(\App\Module\Prescription\PrescriptionController::class) + ->setArguments([ + new Reference(\App\Module\Prescription\Repository\PrescriptionRepository::class), + new Reference(\App\Module\Patient\Repository\PatientRepository::class), + new Reference(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class), + new Reference(\App\Module\User\Repository\UserRepository::class), + new Reference(\App\Module\Inventory\Repository\InventoryItemRepository::class), + ])->setPublic(true); + + // Insurance controller wired to InsuranceService + $container->register(\App\Module\Insurance\InsuranceController::class) + ->setArguments([ + new Reference(\App\Module\Insurance\Service\InsuranceService::class), + ])->setPublic(true); }; From 8ce4c6d6fc376d389ec2470930c0861339acc366 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:05:17 +0200 Subject: [PATCH 31/52] chore(infra): Update service registrations --- config/services.php | 134 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/config/services.php b/config/services.php index 0398342..acb050d 100644 --- a/config/services.php +++ b/config/services.php @@ -99,6 +99,8 @@ $container->register(\App\Module\LabOrder\Repository\LabResourceRepository::class)->setPublic(true); $container->register(\App\Module\Inventory\Repository\InventoryItemRepository::class)->setPublic(true); $container->register(\App\Module\User\Repository\UserRepository::class)->setPublic(true); + $container->register(\App\Module\User\Repository\RoleRepository::class)->setPublic(true); + $container->register(\App\Module\User\Repository\UserOAuthIdentityRepository::class)->setPublic(true); $container->register(\App\Module\LabOrder\Service\LabImportService::class) ->setPublic(true); $container->register(\App\Module\LabOrder\LabOrderController::class) @@ -117,6 +119,7 @@ $container->register(\App\Module\Patient\Repository\PatientRepository::class)->setPublic(true); $container->register(\App\Module\User\Repository\UserRepository::class)->setPublic(true); $container->register(\App\Module\Billing\Repository\ServiceRepository::class)->setPublic(true); + $container->register(\App\Module\Billing\Repository\ServiceBundleRepository::class)->setPublic(true); $container->register(\App\Module\Schedule\Repository\DoctorScheduleRepository::class)->setPublic(true); $container->register(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class)->setPublic(true); $container->register(\App\Module\Room\Repository\RoomRepository::class)->setPublic(true); @@ -136,6 +139,8 @@ new Reference(\App\Module\User\Repository\UserRepository::class), new Reference(\App\Core\Service\NotificationService::class), new Reference(\App\Module\Schedule\Service\SchedulingService::class), + new Reference(\App\Module\Schedule\Repository\DoctorScheduleRepository::class), + new Reference(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class), new Reference(\App\Module\Billing\Repository\ServiceRepository::class), new Reference(\App\Module\Room\Repository\RoomRepository::class), ]) @@ -148,6 +153,24 @@ new Reference(\App\Module\Inventory\Repository\InventoryItemRepository::class), ])->setPublic(true); + // News + $container->register(\App\Module\News\Repository\NewsRepository::class)->setPublic(true); + $container->register(\App\Module\News\NewsController::class) + ->setArguments([ + new Reference(\App\Module\News\Repository\NewsRepository::class), + new Reference(\App\Module\User\Repository\UserRepository::class), + ])->setPublic(true); + + // Schedule + $container->register(\App\Module\Schedule\Repository\DoctorScheduleRepository::class)->setPublic(true); + $container->register(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class)->setPublic(true); + $container->register(\App\Module\Schedule\ScheduleController::class) + ->setArguments([ + new Reference(\App\Module\Schedule\Repository\DoctorScheduleRepository::class), + new Reference(\App\Module\Schedule\Repository\ScheduleExceptionRepository::class), + new Reference(\App\Module\User\Repository\UserRepository::class), + ])->setPublic(true); + // Department $container->register(\App\Module\Department\Repository\DepartmentRepository::class)->setPublic(true); $container->register(\App\Module\Department\Repository\HrmRepository::class)->setPublic(true); @@ -158,11 +181,83 @@ ])->setPublic(true); // Room + $container->register(\App\Module\Room\RoomRepository::class)->setPublic(true); $container->register(\App\Module\Room\RoomController::class) ->setArguments([ new Reference(\App\Module\Room\Repository\RoomRepository::class), ])->setPublic(true); + // Core repositories + $container->register(\App\Core\Repository\SettingsRepository::class)->setPublic(true); + + // User services + $container->register(\App\Module\User\MfaService::class) + ->setArguments([ + new Reference('pdo'), + new Reference(\App\Core\Service\QrCodeGenerator::class), + ])->setPublic(true); + + // Admin + $container->register(\App\Module\Admin\Repository\AuthConfigRepository::class)->setPublic(true); + $container->register(\App\Module\Admin\Repository\BackupPolicyRepository::class)->setPublic(true); + $container->register(\App\Module\Admin\Repository\DictionaryRepository::class)->setPublic(true); + $container->register(\App\Module\Kpi\Repository\KpiRepository::class)->setPublic(true); + $container->register(\App\Module\Admin\AdminController::class) + ->setArguments([ + new Reference(\App\Module\User\Repository\UserRepository::class), + new Reference(\App\Module\User\Repository\RoleRepository::class), + new Reference(\App\Module\Admin\Repository\DictionaryRepository::class), + new Reference(\App\Module\Admin\Repository\AuthConfigRepository::class), + new Reference(\App\Module\Admin\Repository\BackupPolicyRepository::class), + new Reference(\App\Module\Kpi\Repository\KpiRepository::class), + new Reference(\App\Module\Billing\Repository\ServiceRepository::class), + new Reference(\App\Core\Repository\SettingsRepository::class), + new Reference(\App\Module\User\MfaService::class), + ])->setPublic(true); + + // User controllers + $container->register(\App\Module\User\AuthController::class) + ->setArguments([ + new Reference(\App\Module\User\Repository\UserRepository::class), + new Reference(\App\Module\Admin\Repository\AuthConfigRepository::class), + new Reference(\App\Module\User\Repository\RoleRepository::class), + new Reference(\App\Module\User\MfaService::class), + ])->setPublic(true); + $container->register(\App\Module\User\MfaController::class) + ->setArguments([ + new Reference(\App\Module\User\MfaService::class), + new Reference(\App\Module\User\Repository\UserRepository::class), + new Reference(\App\Core\Repository\SettingsRepository::class), + new Reference(\App\Module\User\Repository\RoleRepository::class), + ])->setPublic(true); + $container->register(\App\Module\User\OAuthController::class) + ->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); + + // Kpi + $container->register(\App\Module\Kpi\Repository\KpiRepository::class)->setPublic(true); + $container->register(\App\Module\Kpi\KpiController::class) + ->setArguments([ + new Reference(\App\Module\Kpi\Repository\KpiRepository::class), + new Reference(\App\Module\Billing\Repository\InvoiceRepository::class), + new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class), + ])->setPublic(true); + + // Billing controller + $container->register(\App\Module\Billing\BillingController::class) + ->setArguments([ + new Reference(\App\Module\Billing\Repository\InvoiceRepository::class), + new Reference(\App\Module\Patient\Repository\PatientRepository::class), + new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class), + new Reference(\App\Module\MedicalRecord\Repository\MedicalRecordRepository::class), + new Reference(\App\Module\Billing\Repository\ServiceRepository::class), + new Reference(\App\Module\Billing\Repository\ServiceBundleRepository::class), + new Reference(\App\Module\Insurance\Service\InsuranceService::class), + ])->setPublic(true); + // Prescription $container->register(\App\Module\Prescription\Repository\PrescriptionRepository::class)->setPublic(true); $container->register(\App\Module\Prescription\PrescriptionController::class) @@ -179,4 +274,43 @@ ->setArguments([ new Reference(\App\Module\Insurance\Service\InsuranceService::class), ])->setPublic(true); + + // Notification + $container->register(\App\Module\Notification\Repository\NotificationRepository::class)->setPublic(true); + $container->register(\App\Module\Notification\NotificationController::class) + ->setArguments([ + new Reference(\App\Module\Notification\Repository\NotificationRepository::class), + ])->setPublic(true); + + // Kpi + $container->register(\App\Module\Kpi\Repository\KpiRepository::class)->setPublic(true); + $container->register(\App\Module\Kpi\KpiController::class) + ->setArguments([ + new Reference(\App\Module\Kpi\Repository\KpiRepository::class), + new Reference(\App\Module\Billing\Repository\InvoiceRepository::class), + new Reference(\App\Module\Appointment\Repository\AppointmentRepository::class), + ])->setPublic(true); + + // Billing - Contract controller + $container->register(\App\Module\Billing\Repository\ContractRepository::class)->setPublic(true); + $container->register(\App\Module\Billing\ContractController::class) + ->setArguments([ + new Reference(\App\Module\Billing\Repository\ContractRepository::class), + ])->setPublic(true); + + // Dashboard + $container->register(\App\Module\Dashboard\Service\DashboardService::class)->setPublic(true); + $container->register(\App\Module\Dashboard\DashboardController::class) + ->setArguments([ + new Reference(\App\Module\Dashboard\Service\DashboardService::class), + ])->setPublic(true); + + // ClinicalReference + $container->register(\App\Module\ClinicalReference\Repository\IcdCodeRepository::class)->setPublic(true); + $container->register(\App\Module\ClinicalReference\Repository\InterventionCodeRepository::class)->setPublic(true); + $container->register(\App\Module\ClinicalReference\ClinicalReferenceController::class) + ->setArguments([ + new Reference(\App\Module\ClinicalReference\Repository\IcdCodeRepository::class), + new Reference(\App\Module\ClinicalReference\Repository\InterventionCodeRepository::class), + ])->setPublic(true); }; From 91f848f5861b9947f8ea1d1554e2acb7c72b8d9f Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:06:37 +0200 Subject: [PATCH 32/52] fix(http): Render full exception trace in Router error page --- src/Core/Http/Router.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Core/Http/Router.php b/src/Core/Http/Router.php index fb879b0..44882cd 100644 --- a/src/Core/Http/Router.php +++ b/src/Core/Http/Router.php @@ -41,12 +41,20 @@ public function dispatch(Request $request): Response $handler = $route['handler']; if (is_array($handler) && is_string($handler[0])) { - if ($this->container && $this->container->has($handler[0])) { - $controller = $this->container->get($handler[0]); - } else { - $controller = new $handler[0](); + try { + if ($this->container && $this->container->has($handler[0])) { + $controller = $this->container->get($handler[0]); + } else { + $controller = new $handler[0](); + } + $callback = [$controller, $handler[1]]; + } 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'); + return $response; } - $callback = [$controller, $handler[1]]; } else { $callback = $handler; } From 86878e9ca5e2a89869c1bf0a5b10703192f7db75 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:07:28 +0200 Subject: [PATCH 33/52] fix(db): Improve database connection error reporting --- src/Database/Database.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 788a90f..868cd0f 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -23,10 +23,16 @@ public static function getInstance(): PDO $dbPort = $_ENV['DB_PORT'] ?? '3306'; $dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4"; - self::$instance = new PDO($dsn, $dbUser, $dbPass, [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); + try { + self::$instance = new PDO($dsn, $dbUser, $dbPass, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + } catch (PDOException $e) { + $msg = sprintf("Не вдалося підключитися до бази даних (%s@%s:%s/%s): %s\n", $dbUser, $dbHost, $dbPort, $dbName, $e->getMessage()); + $msg .= "Перевірте, чи запущено сервер БД і чи вірні змінні оточення: DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD."; + throw new \RuntimeException($msg, 0, $e); + } } return self::$instance; From bec20fb7d48f54945ba51a6e8eb2e6a34df6ab4e Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:08:10 +0200 Subject: [PATCH 34/52] refactor(appointment): Enable DI in controller and policy --- src/Module/Appointment/AppointmentController.php | 13 +++++++++---- src/Module/Appointment/AppointmentPolicy.php | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Module/Appointment/AppointmentController.php b/src/Module/Appointment/AppointmentController.php index 044e043..3ed0e15 100644 --- a/src/Module/Appointment/AppointmentController.php +++ b/src/Module/Appointment/AppointmentController.php @@ -25,6 +25,8 @@ class AppointmentController private SchedulingService $schedulingService; private ServiceRepository $serviceRepository; private \App\Module\Room\Repository\RoomRepository $roomRepository; + private DoctorScheduleRepository $doctorScheduleRepository; + private ScheduleExceptionRepository $scheduleExceptionRepository; public function __construct( ?AppointmentRepository $appointmentRepository = null, @@ -32,6 +34,8 @@ public function __construct( ?UserRepository $userRepository = null, ?NotificationService $notificationService = null, ?SchedulingService $schedulingService = null, + ?DoctorScheduleRepository $doctorScheduleRepository = null, + ?ScheduleExceptionRepository $scheduleExceptionRepository = null, ?ServiceRepository $serviceRepository = null, ?\App\Module\Room\Repository\RoomRepository $roomRepository = null ) { @@ -44,14 +48,15 @@ public function __construct( $this->serviceRepository = $serviceRepository ?? new ServiceRepository(); $this->roomRepository = $roomRepository ?? new \App\Module\Room\Repository\RoomRepository(); + $this->doctorScheduleRepository = $doctorScheduleRepository ?? new DoctorScheduleRepository(); + $this->scheduleExceptionRepository = $scheduleExceptionRepository ?? new ScheduleExceptionRepository(); + if ($schedulingService !== null) { $this->schedulingService = $schedulingService; } else { - $doctorScheduleRepository = new DoctorScheduleRepository(); - $scheduleExceptionRepository = new ScheduleExceptionRepository(); $this->schedulingService = new SchedulingService( - $doctorScheduleRepository, - $scheduleExceptionRepository, + $this->doctorScheduleRepository, + $this->scheduleExceptionRepository, $this->appointmentRepository, $this->serviceRepository, $this->roomRepository diff --git a/src/Module/Appointment/AppointmentPolicy.php b/src/Module/Appointment/AppointmentPolicy.php index a64bb83..ec32be4 100644 --- a/src/Module/Appointment/AppointmentPolicy.php +++ b/src/Module/Appointment/AppointmentPolicy.php @@ -10,9 +10,9 @@ class AppointmentPolicy implements Policy { private AppointmentRepository $appointmentRepository; - public function __construct() + public function __construct(?AppointmentRepository $appointmentRepository = null) { - $this->appointmentRepository = new AppointmentRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); } public function view(User $user, array $context): bool From fbf79357befe756756a06c70cd079ba2112f431f Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:08:28 +0200 Subject: [PATCH 35/52] refactor(billing): Enable DI in billing controllers --- src/Module/Billing/BillingController.php | 44 +++++++++++++++-------- src/Module/Billing/ContractController.php | 4 +-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/Module/Billing/BillingController.php b/src/Module/Billing/BillingController.php index 7cef2cd..9ace3d3 100644 --- a/src/Module/Billing/BillingController.php +++ b/src/Module/Billing/BillingController.php @@ -30,20 +30,36 @@ class BillingController private ServiceRepository $serviceRepository; private ServiceBundleRepository $serviceBundleRepository; private InsuranceService $insuranceService; - - public function __construct() - { - $this->invoiceRepository = new InvoiceRepository(); - $this->patientRepository = new PatientRepository(); - $this->appointmentRepository = new AppointmentRepository(); - $this->medicalRecordRepository = new MedicalRecordRepository(); - $this->serviceRepository = new ServiceRepository(); - $this->serviceBundleRepository = new ServiceBundleRepository(); - - $this->insuranceService = new InsuranceService( - new InsuranceCompanyRepository(), - new PatientInsurancePolicyRepository(), - new ClaimRepository(), + private InsuranceCompanyRepository $insuranceCompanyRepository; + private PatientInsurancePolicyRepository $patientInsurancePolicyRepository; + private ClaimRepository $claimRepository; + + public function __construct( + ?InvoiceRepository $invoiceRepository = null, + ?PatientRepository $patientRepository = null, + ?AppointmentRepository $appointmentRepository = null, + ?MedicalRecordRepository $medicalRecordRepository = null, + ?ServiceRepository $serviceRepository = null, + ?ServiceBundleRepository $serviceBundleRepository = null, + ?InsuranceService $insuranceService = null, + ?InsuranceCompanyRepository $insuranceCompanyRepository = null, + ?PatientInsurancePolicyRepository $patientInsurancePolicyRepository = null, + ?ClaimRepository $claimRepository = null + ) { + $this->invoiceRepository = $invoiceRepository ?? new InvoiceRepository(); + $this->patientRepository = $patientRepository ?? new PatientRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); + $this->serviceRepository = $serviceRepository ?? new ServiceRepository(); + $this->serviceBundleRepository = $serviceBundleRepository ?? new ServiceBundleRepository(); + $this->insuranceCompanyRepository = $insuranceCompanyRepository ?? new InsuranceCompanyRepository(); + $this->patientInsurancePolicyRepository = $patientInsurancePolicyRepository ?? new PatientInsurancePolicyRepository(); + $this->claimRepository = $claimRepository ?? new ClaimRepository(); + + $this->insuranceService = $insuranceService ?? new InsuranceService( + $this->insuranceCompanyRepository, + $this->patientInsurancePolicyRepository, + $this->claimRepository, $this->invoiceRepository ); } diff --git a/src/Module/Billing/ContractController.php b/src/Module/Billing/ContractController.php index 558918b..6c4147a 100644 --- a/src/Module/Billing/ContractController.php +++ b/src/Module/Billing/ContractController.php @@ -13,9 +13,9 @@ class ContractController { private ContractRepository $contractRepository; - public function __construct() + public function __construct(?ContractRepository $contractRepository = null) { - $this->contractRepository = new ContractRepository(); + $this->contractRepository = $contractRepository ?? new ContractRepository(); } public function index(): void From 027c68630f4bdb91f297626371c06dd0d53cff9d Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:08:43 +0200 Subject: [PATCH 36/52] refactor(dashboard): Enable DI in controller and service --- src/Module/Dashboard/DashboardController.php | 4 ++-- src/Module/Dashboard/Service/DashboardService.php | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Module/Dashboard/DashboardController.php b/src/Module/Dashboard/DashboardController.php index 32d4ca8..9742da0 100644 --- a/src/Module/Dashboard/DashboardController.php +++ b/src/Module/Dashboard/DashboardController.php @@ -11,9 +11,9 @@ class DashboardController { private DashboardService $dashboardService; - public function __construct() + public function __construct(?DashboardService $dashboardService = null) { - $this->dashboardService = new DashboardService(); + $this->dashboardService = $dashboardService ?? new DashboardService(); } public function index(): void diff --git a/src/Module/Dashboard/Service/DashboardService.php b/src/Module/Dashboard/Service/DashboardService.php index 4ecd0e8..749f6f1 100644 --- a/src/Module/Dashboard/Service/DashboardService.php +++ b/src/Module/Dashboard/Service/DashboardService.php @@ -19,14 +19,14 @@ class DashboardService private LabOrderRepository $labOrderRepository; private InventoryItemRepository $inventoryItemRepository; - public function __construct() + public function __construct(?KpiRepository $kpiRepository = null, ?InvoiceRepository $invoiceRepository = null, ?PatientRepository $patientRepository = null, ?AppointmentRepository $appointmentRepository = null, ?LabOrderRepository $labOrderRepository = null, ?InventoryItemRepository $inventoryItemRepository = null) { - $this->kpiRepository = new KpiRepository(); - $this->invoiceRepository = new InvoiceRepository(); - $this->patientRepository = new PatientRepository(); - $this->appointmentRepository = new AppointmentRepository(); - $this->labOrderRepository = new LabOrderRepository(); - $this->inventoryItemRepository = new InventoryItemRepository(); + $this->kpiRepository = $kpiRepository ?? new KpiRepository(); + $this->invoiceRepository = $invoiceRepository ?? new InvoiceRepository(); + $this->patientRepository = $patientRepository ?? new PatientRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->labOrderRepository = $labOrderRepository ?? new LabOrderRepository(); + $this->inventoryItemRepository = $inventoryItemRepository ?? new InventoryItemRepository(); } /** From 73d2bb26cf95d11f557f1fe94b8e504f4a5e4653 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:08:57 +0200 Subject: [PATCH 37/52] refactor(dashboard): Enable DI in KpiCalculatorService --- .../Dashboard/Service/KpiCalculatorService.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Module/Dashboard/Service/KpiCalculatorService.php b/src/Module/Dashboard/Service/KpiCalculatorService.php index 1487e9d..9e81f57 100644 --- a/src/Module/Dashboard/Service/KpiCalculatorService.php +++ b/src/Module/Dashboard/Service/KpiCalculatorService.php @@ -17,13 +17,13 @@ class KpiCalculatorService private UserRepository $userRepository; private MedicalRecordRepository $medicalRecordRepository; - public function __construct() + public function __construct(?KpiRepository $kpiRepository = null, ?AppointmentRepository $appointmentRepository = null, ?InvoiceRepository $invoiceRepository = null, ?UserRepository $userRepository = null, ?MedicalRecordRepository $medicalRecordRepository = null) { - $this->kpiRepository = new KpiRepository(); - $this->appointmentRepository = new AppointmentRepository(); - $this->invoiceRepository = new InvoiceRepository(); - $this->userRepository = new UserRepository(); - $this->medicalRecordRepository = new MedicalRecordRepository(); + $this->kpiRepository = $kpiRepository ?? new KpiRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->invoiceRepository = $invoiceRepository ?? new InvoiceRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); } public function calculateAndStoreAll(?string $forDate = null): void From 7a50242d91540e009d7e1e516e3d570836a4ab82 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:09:11 +0200 Subject: [PATCH 38/52] refactor(hrm,kpi): Enable DI in HrmController and KpiController --- src/Module/Hrm/HrmController.php | 8 ++++---- src/Module/Kpi/KpiController.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Module/Hrm/HrmController.php b/src/Module/Hrm/HrmController.php index 2958873..0b82005 100644 --- a/src/Module/Hrm/HrmController.php +++ b/src/Module/Hrm/HrmController.php @@ -16,11 +16,11 @@ class HrmController private UserRepository $userRepository; private DepartmentRepository $departmentRepository; - public function __construct() + public function __construct(?HrmRepository $hrmRepository = null, ?UserRepository $userRepository = null, ?DepartmentRepository $departmentRepository = null) { - $this->hrmRepository = new HrmRepository(); - $this->userRepository = new UserRepository(); - $this->departmentRepository = new DepartmentRepository(); + $this->hrmRepository = $hrmRepository ?? new HrmRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); + $this->departmentRepository = $departmentRepository ?? new DepartmentRepository(); } public function index(): void diff --git a/src/Module/Kpi/KpiController.php b/src/Module/Kpi/KpiController.php index d7a5b72..cb22096 100644 --- a/src/Module/Kpi/KpiController.php +++ b/src/Module/Kpi/KpiController.php @@ -17,11 +17,11 @@ class KpiController private InvoiceRepository $invoiceRepository; private AppointmentRepository $appointmentRepository; - public function __construct() + public function __construct(?KpiRepository $kpiRepository = null, ?InvoiceRepository $invoiceRepository = null, ?AppointmentRepository $appointmentRepository = null) { - $this->kpiRepository = new KpiRepository(); - $this->invoiceRepository = new InvoiceRepository(); - $this->appointmentRepository = new AppointmentRepository(); + $this->kpiRepository = $kpiRepository ?? new KpiRepository(); + $this->invoiceRepository = $invoiceRepository ?? new InvoiceRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); } // --- KPI Definitions --- From 2a440df87549997d081ab6a90dbc1d71b9e425d8 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:09:27 +0200 Subject: [PATCH 39/52] refactor(laborder): Enable DI in policy and import service --- src/Module/LabOrder/LabOrderPolicy.php | 4 ++-- src/Module/LabOrder/Service/LabImportService.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Module/LabOrder/LabOrderPolicy.php b/src/Module/LabOrder/LabOrderPolicy.php index b1e0364..b504144 100644 --- a/src/Module/LabOrder/LabOrderPolicy.php +++ b/src/Module/LabOrder/LabOrderPolicy.php @@ -10,9 +10,9 @@ class LabOrderPolicy implements Policy { private LabOrderRepository $labOrderRepository; - public function __construct() + public function __construct(?LabOrderRepository $labOrderRepository = null) { - $this->labOrderRepository = new LabOrderRepository(); + $this->labOrderRepository = $labOrderRepository ?? new LabOrderRepository(); } public function view(User $user, array $context): bool diff --git a/src/Module/LabOrder/Service/LabImportService.php b/src/Module/LabOrder/Service/LabImportService.php index 17e0091..804c92e 100644 --- a/src/Module/LabOrder/Service/LabImportService.php +++ b/src/Module/LabOrder/Service/LabImportService.php @@ -9,9 +9,9 @@ class LabImportService { private LabOrderRepository $labOrderRepository; - public function __construct() + public function __construct(?LabOrderRepository $labOrderRepository = null) { - $this->labOrderRepository = new LabOrderRepository(); + $this->labOrderRepository = $labOrderRepository ?? new LabOrderRepository(); } /** From f676a7c11ec046cb29224f0eb4cbc082290e8fd0 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:09:43 +0200 Subject: [PATCH 40/52] refactor(medicalrecord,news): Enable DI in policies and news controller --- src/Module/MedicalRecord/MedicalRecordPolicy.php | 4 ++-- src/Module/News/NewsController.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Module/MedicalRecord/MedicalRecordPolicy.php b/src/Module/MedicalRecord/MedicalRecordPolicy.php index 017c648..f96f145 100644 --- a/src/Module/MedicalRecord/MedicalRecordPolicy.php +++ b/src/Module/MedicalRecord/MedicalRecordPolicy.php @@ -10,9 +10,9 @@ class MedicalRecordPolicy implements Policy { private MedicalRecordRepository $medicalRecordRepository; - public function __construct() + public function __construct(?MedicalRecordRepository $medicalRecordRepository = null) { - $this->medicalRecordRepository = new MedicalRecordRepository(); + $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); } public function view(User $user, array $context): bool diff --git a/src/Module/News/NewsController.php b/src/Module/News/NewsController.php index 7fb918e..85531dc 100644 --- a/src/Module/News/NewsController.php +++ b/src/Module/News/NewsController.php @@ -15,10 +15,10 @@ class NewsController private NewsRepository $newsRepository; private UserRepository $userRepository; // For author selection in admin forms - public function __construct() + public function __construct(?NewsRepository $newsRepository = null, ?UserRepository $userRepository = null) { - $this->newsRepository = new NewsRepository(); - $this->userRepository = new UserRepository(); + $this->newsRepository = $newsRepository ?? new NewsRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); } // Public listing of news articles From 56b3ddbc6c4628ed9550bec091c6fb0b97898f9d Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:09:55 +0200 Subject: [PATCH 41/52] refactor(notification,patient): Enable DI in notification and patient controllers --- src/Module/Notification/NotificationController.php | 4 ++-- src/Module/Patient/PatientController.php | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Module/Notification/NotificationController.php b/src/Module/Notification/NotificationController.php index 434ea95..fc1870f 100644 --- a/src/Module/Notification/NotificationController.php +++ b/src/Module/Notification/NotificationController.php @@ -10,9 +10,9 @@ class NotificationController { private NotificationRepository $notificationRepository; - public function __construct() + public function __construct(?NotificationRepository $notificationRepository = null) { - $this->notificationRepository = new NotificationRepository(); + $this->notificationRepository = $notificationRepository ?? new NotificationRepository(); } /** diff --git a/src/Module/Patient/PatientController.php b/src/Module/Patient/PatientController.php index 2c7ec39..cde6913 100644 --- a/src/Module/Patient/PatientController.php +++ b/src/Module/Patient/PatientController.php @@ -13,6 +13,7 @@ use App\Module\Billing\Repository\InvoiceRepository; use App\Module\Insurance\Repository\InsuranceCompanyRepository; use App\Module\Insurance\Repository\PatientInsurancePolicyRepository; +use App\Module\Insurance\Repository\ClaimRepository; use App\Module\Insurance\Service\InsuranceService; use App\Module\MedicalRecord\Repository\MedicalRecordRepository; use App\Module\Patient\Repository\PatientRepository; @@ -31,18 +32,22 @@ public function __construct( ?AppointmentRepository $appointmentRepository = null, ?InsuranceService $insuranceService = null, ?InsuranceCompanyRepository $insuranceCompanyRepository = null, - ?PatientInsurancePolicyRepository $patientInsurancePolicyRepository = null + ?PatientInsurancePolicyRepository $patientInsurancePolicyRepository = null, + ?ClaimRepository $claimRepository = null, + ?InvoiceRepository $invoiceRepository = null ) { $this->patientRepository = $patientRepository ?? new PatientRepository(); $this->medicalRecordRepository = $medicalRecordRepository ?? new MedicalRecordRepository(); $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); $this->insuranceCompanyRepository = $insuranceCompanyRepository ?? new \App\Module\Insurance\Repository\InsuranceCompanyRepository(); $this->patientInsurancePolicyRepository = $patientInsurancePolicyRepository ?? new \App\Module\Insurance\Repository\PatientInsurancePolicyRepository(); + $this->claimRepository = $claimRepository ?? new \App\Module\Insurance\Repository\ClaimRepository(); + $this->invoiceRepository = $invoiceRepository ?? new InvoiceRepository(); $this->insuranceService = $insuranceService ?? new \App\Module\Insurance\Service\InsuranceService( $this->insuranceCompanyRepository, $this->patientInsurancePolicyRepository, - new \App\Module\Insurance\Repository\ClaimRepository(), - new InvoiceRepository() + $this->claimRepository, + $this->invoiceRepository ); } From d3d585b1c745487b76e4ccc8c2edca5b855b34b9 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:10:13 +0200 Subject: [PATCH 42/52] refactor(patient,prescription): Enable DI in policies --- src/Module/Patient/PatientPolicy.php | 6 +++--- src/Module/Prescription/PrescriptionPolicy.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Module/Patient/PatientPolicy.php b/src/Module/Patient/PatientPolicy.php index cf66bbb..6d4a8a2 100644 --- a/src/Module/Patient/PatientPolicy.php +++ b/src/Module/Patient/PatientPolicy.php @@ -12,10 +12,10 @@ class PatientPolicy implements Policy private AppointmentRepository $appointmentRepository; private PatientRepository $patientRepository; - public function __construct() + public function __construct(?AppointmentRepository $appointmentRepository = null, ?PatientRepository $patientRepository = null) { - $this->appointmentRepository = new AppointmentRepository(); - $this->patientRepository = new PatientRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); + $this->patientRepository = $patientRepository ?? new PatientRepository(); } public function view(User $user, array $context): bool diff --git a/src/Module/Prescription/PrescriptionPolicy.php b/src/Module/Prescription/PrescriptionPolicy.php index e452550..0628706 100644 --- a/src/Module/Prescription/PrescriptionPolicy.php +++ b/src/Module/Prescription/PrescriptionPolicy.php @@ -12,10 +12,10 @@ class PrescriptionPolicy implements Policy private PrescriptionRepository $prescriptionRepository; private AppointmentRepository $appointmentRepository; - public function __construct() + public function __construct(?PrescriptionRepository $prescriptionRepository = null, ?AppointmentRepository $appointmentRepository = null) { - $this->prescriptionRepository = new PrescriptionRepository(); - $this->appointmentRepository = new AppointmentRepository(); + $this->prescriptionRepository = $prescriptionRepository ?? new PrescriptionRepository(); + $this->appointmentRepository = $appointmentRepository ?? new AppointmentRepository(); } public function view(User $user, array $context): bool From c0cc91c0a32889a9b642da599d6f336a9e6daec4 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:10:53 +0200 Subject: [PATCH 43/52] refactor(schedule): Enable DI in schedule controller and policy --- src/Module/Schedule/ScheduleController.php | 11 ++++------- src/Module/Schedule/SchedulePolicy.php | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Module/Schedule/ScheduleController.php b/src/Module/Schedule/ScheduleController.php index 683f11c..1a2c456 100644 --- a/src/Module/Schedule/ScheduleController.php +++ b/src/Module/Schedule/ScheduleController.php @@ -14,15 +14,12 @@ class ScheduleController private DoctorScheduleRepository $doctorScheduleRepository; private ScheduleExceptionRepository $scheduleExceptionRepository; private UserRepository $userRepository; - private View $view; - public function __construct() + public function __construct(?DoctorScheduleRepository $doctorScheduleRepository = null, ?ScheduleExceptionRepository $scheduleExceptionRepository = null, ?UserRepository $userRepository = null) { - AuthGuard::check(); // Protect all actions in this controller - $this->doctorScheduleRepository = new DoctorScheduleRepository(); - $this->scheduleExceptionRepository = new ScheduleExceptionRepository(); - $this->userRepository = new UserRepository(); - $this->view = new View(); + $this->doctorScheduleRepository = $doctorScheduleRepository ?? new DoctorScheduleRepository(); + $this->scheduleExceptionRepository = $scheduleExceptionRepository ?? new ScheduleExceptionRepository(); + $this->userRepository = $userRepository ?? new UserRepository(); } public function index(): void diff --git a/src/Module/Schedule/SchedulePolicy.php b/src/Module/Schedule/SchedulePolicy.php index 12365c5..7366475 100644 --- a/src/Module/Schedule/SchedulePolicy.php +++ b/src/Module/Schedule/SchedulePolicy.php @@ -10,9 +10,9 @@ class SchedulePolicy implements Policy { private DoctorScheduleRepository $doctorScheduleRepository; - public function __construct() + public function __construct(?DoctorScheduleRepository $doctorScheduleRepository = null) { - $this->doctorScheduleRepository = new DoctorScheduleRepository(); + $this->doctorScheduleRepository = $doctorScheduleRepository ?? new DoctorScheduleRepository(); } public function view(User $user, array $context): bool From cc797e50637bcd4dcdfb6d3ef4ba13a30f47e459 Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:11:09 +0200 Subject: [PATCH 44/52] refactor(user): Enable DI in Auth and Mfa controllers --- src/Module/User/AuthController.php | 14 ++++++++----- src/Module/User/MfaController.php | 33 +++++++++++++++--------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Module/User/AuthController.php b/src/Module/User/AuthController.php index 763ff32..20b6de9 100644 --- a/src/Module/User/AuthController.php +++ b/src/Module/User/AuthController.php @@ -10,6 +10,7 @@ use App\Module\Admin\Repository\AuthConfigRepository; use App\Module\User\Repository\RoleRepository; use App\Module\User\Repository\UserRepository; +use App\Module\User\OAuthController; class AuthController { @@ -17,13 +18,17 @@ class AuthController private AuthConfigRepository $authConfigRepository; private RoleRepository $roleRepository; private ?MfaService $mfaService = null; + private OAuthController $oauthController; + private \App\Core\Repository\SettingsRepository $settingsRepository; - public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?RoleRepository $roleRepository = null, ?MfaService $mfaService = null) + public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?RoleRepository $roleRepository = null, ?MfaService $mfaService = null, ?\App\Core\Repository\SettingsRepository $settingsRepository = null, ?OAuthController $oauthController = null) { $this->userRepository = $userRepository ?? new UserRepository(); $this->authConfigRepository = $authConfigRepository ?? new AuthConfigRepository(); $this->roleRepository = $roleRepository ?? new RoleRepository(); $this->mfaService = $mfaService ?? new MfaService(); + $this->settingsRepository = $settingsRepository ?? new \App\Core\Repository\SettingsRepository(); + $this->oauthController = $oauthController ?? new OAuthController(); } public function showLoginForm(): void @@ -66,9 +71,8 @@ public function login(): void if ($user && password_verify($password, $user['password_hash'])) { $mfaService = $this->mfaService ?? new MfaService(); - $settingsRepository = new \App\Core\Repository\SettingsRepository(); - $mfaPolicy = $settingsRepository->getMfaPolicy(); - $mfaForceRoles = $settingsRepository->getMfaForceRoles(); + $mfaPolicy = $this->settingsRepository->getMfaPolicy(); + $mfaForceRoles = $this->settingsRepository->getMfaForceRoles(); if ($mfaPolicy === 'disabled') { $_SESSION['user'] = [ @@ -136,7 +140,7 @@ public function login(): void */ public function redirectToProvider(string $provider): void { - (new OAuthController())->redirect($provider); + $this->oauthController->redirect($provider); } public function logout(): void diff --git a/src/Module/User/MfaController.php b/src/Module/User/MfaController.php index c072163..4cc790f 100644 --- a/src/Module/User/MfaController.php +++ b/src/Module/User/MfaController.php @@ -11,10 +11,18 @@ class MfaController { private MfaService $mfaService; private \App\Module\User\Repository\UserRepository $userRepository; - public function __construct(?MfaService $mfaService = null, ?\App\Module\User\Repository\UserRepository $userRepository = null) - { + private \App\Core\Repository\SettingsRepository $settingsRepository; + private \App\Module\User\Repository\RoleRepository $roleRepository; + public function __construct( + ?MfaService $mfaService = null, + ?\App\Module\User\Repository\UserRepository $userRepository = null, + ?\App\Core\Repository\SettingsRepository $settingsRepository = null, + ?\App\Module\User\Repository\RoleRepository $roleRepository = null + ) { $this->mfaService = $mfaService ?? new MfaService(); $this->userRepository = $userRepository ?? new UserRepository(); + $this->settingsRepository = $settingsRepository ?? new \App\Core\Repository\SettingsRepository(); + $this->roleRepository = $roleRepository ?? new \App\Module\User\Repository\RoleRepository(); } private function prepareHotpSetup(int $userId, array &$secret, array &$backupCodes, int &$counter, string &$qrCode): void @@ -51,8 +59,7 @@ public function showMfaSetup(string $type = 'totp'): void if (isset($_SESSION['user'])) { AuthGuard::check(); - $settingsRepository = new \App\Core\Repository\SettingsRepository(); - $mfaPolicy = $settingsRepository->getMfaPolicy(); + $mfaPolicy = $this->settingsRepository->getMfaPolicy(); if ($mfaPolicy === 'disabled') { $_SESSION['error_message'] = 'Двофакторна автентифікація вимкнена в налаштуваннях системи.'; @@ -210,8 +217,7 @@ public function verifyMfaSetup(string $type = 'totp'): void if (isset($_SESSION['user'])) { AuthGuard::check(); - $settingsRepository = new \App\Core\Repository\SettingsRepository(); - $mfaPolicy = $settingsRepository->getMfaPolicy(); + $mfaPolicy = $this->settingsRepository->getMfaPolicy(); if ($mfaPolicy === 'disabled') { $_SESSION['error_message'] = 'Двофакторна автентифікація вимкнена в налаштуваннях системи.'; @@ -264,8 +270,7 @@ public function verifyMfaSetup(string $type = 'totp'): void } else { MfaGuard::clearRequired(); $user = $this->userRepository->findById($userId); - $roleRepository = new \App\Module\User\Repository\RoleRepository(); - $role = $roleRepository->findById((int)$user['role_id']); + $role = $this->roleRepository->findById((int)$user['role_id']); $_SESSION['user'] = [ 'id' => $user['id'], @@ -318,8 +323,7 @@ public function verifyMfaSetup(string $type = 'totp'): void } else { MfaGuard::clearRequired(); $user = $this->userRepository->findById($userId); - $roleRepository = new \App\Module\User\Repository\RoleRepository(); - $role = $roleRepository->findById((int)$user['role_id']); + $role = $this->roleRepository->findById((int)$user['role_id']); $_SESSION['user'] = [ 'id' => $user['id'], @@ -389,8 +393,7 @@ public function verifyMfaRequired(string $type = 'totp'): void MfaGuard::clearRequired(); $user = $this->userRepository->findById($userId); - $roleRepository = new \App\Module\User\Repository\RoleRepository(); - $role = $roleRepository->findById((int)$user['role_id']); + $role = $this->roleRepository->findById((int)$user['role_id']); $_SESSION['user'] = [ 'id' => $user['id'], @@ -436,8 +439,7 @@ public function verifyMfaRequired(string $type = 'totp'): void MfaGuard::clearRequired(); $user = $this->userRepository->findById($userId); - $roleRepository = new \App\Module\User\Repository\RoleRepository(); - $role = $roleRepository->findById((int)$user['role_id']); + $role = $this->roleRepository->findById((int)$user['role_id']); $_SESSION['user'] = [ 'id' => $user['id'], @@ -534,8 +536,7 @@ public function verifyMfa(): void MfaGuard::clearRequired(); $user = $this->userRepository->findById($userId); - $roleRepository = new \App\Module\User\Repository\RoleRepository(); - $role = $roleRepository->findById((int)$user['role_id']); + $role = $this->roleRepository->findById((int)$user['role_id']); $_SESSION['user'] = [ 'id' => $user['id'], From 8f54663107e11c694607b8a3cbf0812e5fc39b9f Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:11:25 +0200 Subject: [PATCH 45/52] refactor(user): Enable DI in OAuth and User controllers --- src/Module/User/OAuthController.php | 1 + src/Module/User/UserController.php | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Module/User/OAuthController.php b/src/Module/User/OAuthController.php index 6899d85..774a4bb 100644 --- a/src/Module/User/OAuthController.php +++ b/src/Module/User/OAuthController.php @@ -21,6 +21,7 @@ public function __construct() $this->authConfigRepository = new AuthConfigRepository(); $this->userRepository = new UserRepository(); $this->userOAuthIdentityRepository = new UserOAuthIdentityRepository(); + // no deps currently; keep constructor for DI compatibility } public function redirect(string $provider): void diff --git a/src/Module/User/UserController.php b/src/Module/User/UserController.php index 5965668..638dc67 100644 --- a/src/Module/User/UserController.php +++ b/src/Module/User/UserController.php @@ -8,6 +8,7 @@ use App\Module\Hrm\Repository\HrmRepository; use App\Module\User\Repository\UserOAuthIdentityRepository; use App\Module\User\Repository\UserRepository; +use App\Core\Repository\SettingsRepository; class UserController { @@ -15,13 +16,15 @@ class UserController private AuthConfigRepository $authConfigRepository; private UserOAuthIdentityRepository $userOAuthIdentityRepository; private HrmRepository $hrmRepository; + private SettingsRepository $settingsRepository; - public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?UserOAuthIdentityRepository $userOAuthIdentityRepository = null, ?HrmRepository $hrmRepository = null) + public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?UserOAuthIdentityRepository $userOAuthIdentityRepository = null, ?HrmRepository $hrmRepository = null, ?SettingsRepository $settingsRepository = null) { $this->userRepository = $userRepository ?? new UserRepository(); $this->authConfigRepository = $authConfigRepository ?? new AuthConfigRepository(); $this->userOAuthIdentityRepository = $userOAuthIdentityRepository ?? new UserOAuthIdentityRepository(); $this->hrmRepository = $hrmRepository ?? new HrmRepository(); + $this->settingsRepository = $settingsRepository ?? new SettingsRepository(); } public function profile(): void @@ -44,8 +47,7 @@ public function profile(): void $linkedProviders = $this->userOAuthIdentityRepository->findAllByUserId($user['id']); - $settingsRepository = new \App\Core\Repository\SettingsRepository(); - $mfaPolicy = $settingsRepository->getMfaPolicy(); + $mfaPolicy = $this->settingsRepository->getMfaPolicy(); View::render('@modules/User/templates/profile.html.twig', [ 'user' => $user, From 8300b4463cc963a44f97821621f1275290cf52dd Mon Sep 17 00:00:00 2001 From: Serhii Cherneha Date: Sat, 31 Jan 2026 22:13:22 +0200 Subject: [PATCH 46/52] refactor(admin): Enable DI in AdminController --- src/Module/Admin/AdminController.php | 36 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Module/Admin/AdminController.php b/src/Module/Admin/AdminController.php index c2c4317..a5ff1a6 100644 --- a/src/Module/Admin/AdminController.php +++ b/src/Module/Admin/AdminController.php @@ -25,17 +25,28 @@ class AdminController private KpiRepository $kpiRepository; private ServiceRepository $serviceRepository; private SettingsRepository $settingsRepository; - - public function __construct() - { - $this->userRepository = new UserRepository(); - $this->roleRepository = new RoleRepository(); - $this->dictionaryRepository = new DictionaryRepository(); - $this->authConfigRepository = new AuthConfigRepository(); - $this->backupPolicyRepository = new BackupPolicyRepository(); - $this->kpiRepository = new KpiRepository(); - $this->serviceRepository = new ServiceRepository(); - $this->settingsRepository = new SettingsRepository(); + private \App\Module\User\MfaService $mfaService; + + public function __construct( + ?UserRepository $userRepository = null, + ?RoleRepository $roleRepository = null, + ?DictionaryRepository $dictionaryRepository = null, + ?AuthConfigRepository $authConfigRepository = null, + ?BackupPolicyRepository $backupPolicyRepository = null, + ?KpiRepository $kpiRepository = null, + ?ServiceRepository $serviceRepository = null, + ?SettingsRepository $settingsRepository = null, + ?\App\Module\User\MfaService $mfaService = null + ) { + $this->userRepository = $userRepository ?? new UserRepository(); + $this->roleRepository = $roleRepository ?? new RoleRepository(); + $this->dictionaryRepository = $dictionaryRepository ?? new DictionaryRepository(); + $this->authConfigRepository = $authConfigRepository ?? new AuthConfigRepository(); + $this->backupPolicyRepository = $backupPolicyRepository ?? new BackupPolicyRepository(); + $this->kpiRepository = $kpiRepository ?? new KpiRepository(); + $this->serviceRepository = $serviceRepository ?? new ServiceRepository(); + $this->settingsRepository = $settingsRepository ?? new SettingsRepository(); + $this->mfaService = $mfaService ?? new \App\Module\User\MfaService(); } public function showSettings(): void @@ -319,8 +330,7 @@ public function disableUserMfa(): void return; } - $mfaService = new \App\Module\User\MfaService(); - $mfaService->disableMfaForUser($id); + $this->mfaService->disableMfaForUser($id); $_SESSION['success_message'] = "2FA для користувача " . $user['email'] . " успішно вимкнено."; header('Location: /admin/users'); From 9ddb4c236f74594366961bb9c23d97fc0e21a268 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Tue, 3 Mar 2026 17:22:14 +0530 Subject: [PATCH 47/52] Register Router and ModuleManager in Symfony container - 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 --- config/services.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/config/services.php b/config/services.php index acb050d..b944407 100644 --- a/config/services.php +++ b/config/services.php @@ -10,6 +10,23 @@ ->setPublic(true); // Core services + // Router (needs the compiled container for controller resolution) + $container->register(\App\Core\Http\Router::class) + ->setArguments([new Reference('service_container')]) + ->setPublic(true); + + // ModuleManager + $container->register(\App\Core\Module\ModuleManager::class) + ->setPublic(true); + + // PermissionRegistry + $container->register(\App\Core\Auth\PermissionRegistry::class) + ->setPublic(true); + + // PolicyRegistry + $container->register(\App\Core\Auth\PolicyRegistry::class) + ->setPublic(true); + // Event dispatcher $container->register(\Symfony\Component\EventDispatcher\EventDispatcher::class) ->setPublic(true); From b4f42b4ce3d133ad0a97a589561329ade19d321a Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Tue, 3 Mar 2026 17:22:25 +0530 Subject: [PATCH 48/52] Refactor index.php to resolve core services via DI container - 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 --- public/index.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index 2de611f..add3e2a 100644 --- a/public/index.php +++ b/public/index.php @@ -65,12 +65,12 @@ $request = Request::createFromGlobals(); -$router = new Router($container); +$router = $container->get(Router::class); -$permissionRegistry = new \App\Core\Auth\PermissionRegistry(); -$policyRegistry = new \App\Core\Auth\PolicyRegistry(); +$permissionRegistry = $container->get(\App\Core\Auth\PermissionRegistry::class); +$policyRegistry = $container->get(\App\Core\Auth\PolicyRegistry::class); -$moduleManager = new ModuleManager(); +$moduleManager = $container->get(ModuleManager::class); if ($eventDispatcher === null) { $eventDispatcher = new EventDispatcher(); } From 163c320bb0f6ed4c41cca6769a1dd09e07947dde Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Tue, 3 Mar 2026 17:46:23 +0530 Subject: [PATCH 49/52] Finalize Router DI registration and namespace alignment - 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 --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 84ff1a1..7da3619 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ node_modules/ .phpunit.cache/ build/ public/uploads/* +composer.phar +composer-setup.php From 976ca9db062fc8fd66a494ee219c63d483d5b823 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Tue, 3 Mar 2026 18:04:10 +0530 Subject: [PATCH 50/52] Fix PSR-12 and PHPStan violations - 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) --- config/services.php | 6 +++--- public/index.php | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/config/services.php b/config/services.php index b944407..c272b9d 100644 --- a/config/services.php +++ b/config/services.php @@ -190,15 +190,15 @@ // Department $container->register(\App\Module\Department\Repository\DepartmentRepository::class)->setPublic(true); - $container->register(\App\Module\Department\Repository\HrmRepository::class)->setPublic(true); + $container->register(\App\Module\Hrm\Repository\HrmRepository::class)->setPublic(true); $container->register(\App\Module\Department\DepartmentController::class) ->setArguments([ new Reference(\App\Module\Department\Repository\DepartmentRepository::class), - new Reference(\App\Module\Department\Repository\HrmRepository::class), + new Reference(\App\Module\Hrm\Repository\HrmRepository::class), ])->setPublic(true); // Room - $container->register(\App\Module\Room\RoomRepository::class)->setPublic(true); + $container->register(\App\Module\Room\Repository\RoomRepository::class)->setPublic(true); $container->register(\App\Module\Room\RoomController::class) ->setArguments([ new Reference(\App\Module\Room\Repository\RoomRepository::class), diff --git a/public/index.php b/public/index.php index add3e2a..282057e 100644 --- a/public/index.php +++ b/public/index.php @@ -5,7 +5,11 @@ if (file_exists($resource)) { // Якщо це директорія з індексним файлом (окрім кореневої public), дозволяємо серверу обробити її - if (is_dir($resource) && realpath($resource) !== __DIR__ && (file_exists($resource . '/index.php') || file_exists($resource . '/index.html'))) { + if ( + is_dir($resource) + && realpath($resource) !== __DIR__ + && (file_exists($resource . '/index.php') || file_exists($resource . '/index.html')) + ) { return false; } @@ -105,7 +109,7 @@ $router->add('GET', '/install', [InstallController::class, 'check']); // API routes -$router->add('GET', '/api/status', function() { +$router->add('GET', '/api/status', function () { return ['status' => 'ok', 'version' => '1.0']; }); @@ -127,10 +131,10 @@ $response->send(); } catch (\Throwable $e) { $isDebug = ($_ENV['APP_DEBUG'] ?? 'false') === 'true'; - $content = View::render('errors/error.html.twig', [ + $content = View::renderToString('errors/error.html.twig', [ 'message' => 'Щось пішло не так. Ми вже розбираємося.', 'detail' => $isDebug ? $e->getMessage() : null, ]); $response = new Response($content, 500); $response->send(); -} \ No newline at end of file +} From 439e22c97aa28a615406fb0ea3ca85dcc19dedb1 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Tue, 3 Mar 2026 18:35:11 +0530 Subject: [PATCH 51/52] Fix all PHPStan and PHPCS violations across entire project - 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) --- src/Console/Command/CalculateKpisCommand.php | 2 +- src/Console/Command/MigrateCommand.php | 2 +- src/Console/ConsoleApplication.php | 2 +- src/Controller/InstallController.php | 6 ++-- src/Controller/PageController.php | 1 - src/Core/Event/EventDispatcherService.php | 2 +- src/Core/Http/Router.php | 4 +-- src/Core/Module/ModuleLoader.php | 1 - src/Core/Service/TranslationService.php | 4 +-- src/Core/Validation/Constraint/Unique.php | 2 +- .../Validation/Constraint/UniqueValidator.php | 2 +- src/Core/Validation/SymfonyValidator.php | 21 ++++++----- src/Event/EntityChangedEvent.php | 14 ++++---- src/Event/Listener/AuditLogger.php | 14 ++++---- src/Event/Listener/EmailNotifier.php | 2 +- .../Listener/PatientNotificationListener.php | 2 +- src/Event/NotificationRequestedEvent.php | 5 +-- src/Event/PatientNotificationEvent.php | 5 +-- src/Event/UserLoggedInEvent.php | 5 +-- src/Event/UserLoggedOutEvent.php | 5 +-- src/Infrastructure/Module/ModuleDiscovery.php | 2 +- .../Appointment/AppointmentController.php | 36 +++++++++---------- .../Repository/AppointmentRepository.php | 2 ++ .../Billing/Repository/InvoiceRepository.php | 2 ++ .../Billing/Repository/ServiceRepository.php | 6 ++++ .../Service/KpiCalculatorService.php | 2 ++ .../Department/DepartmentController.php | 2 +- src/Module/Department/DepartmentModule.php | 2 +- src/Module/Department/DepartmentPolicy.php | 2 +- .../Repository/DepartmentRepository.php | 10 +++--- .../DepartmentRepositoryInterface.php | 2 +- src/Module/Hrm/Repository/HrmRepository.php | 2 +- .../Repository/LabOrderRepository.php | 2 ++ .../Repository/MedicalRecordRepository.php | 2 ++ src/Module/News/NewsController.php | 4 ++- src/Module/Patient/PatientController.php | 2 ++ src/Module/Patient/PatientPolicy.php | 1 + .../Patient/Repository/PatientRepository.php | 1 - .../Prescription/PrescriptionPolicy.php | 2 +- .../Repository/PrescriptionRepository.php | 2 ++ src/Module/Schedule/SchedulePolicy.php | 1 + src/Module/User/AuthController.php | 1 + 42 files changed, 109 insertions(+), 80 deletions(-) diff --git a/src/Console/Command/CalculateKpisCommand.php b/src/Console/Command/CalculateKpisCommand.php index ebc2ca0..7a213c0 100644 --- a/src/Console/Command/CalculateKpisCommand.php +++ b/src/Console/Command/CalculateKpisCommand.php @@ -32,4 +32,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } -} \ No newline at end of file +} diff --git a/src/Console/Command/MigrateCommand.php b/src/Console/Command/MigrateCommand.php index 85ed30a..c8399fb 100644 --- a/src/Console/Command/MigrateCommand.php +++ b/src/Console/Command/MigrateCommand.php @@ -37,4 +37,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Migrations completed successfully'); return Command::SUCCESS; } -} \ No newline at end of file +} diff --git a/src/Console/ConsoleApplication.php b/src/Console/ConsoleApplication.php index 9a777d1..759e7a6 100644 --- a/src/Console/ConsoleApplication.php +++ b/src/Console/ConsoleApplication.php @@ -16,4 +16,4 @@ public function __construct() $this->add(new CalculateKpisCommand()); $this->add(new MigrateCommand()); } -} \ No newline at end of file +} diff --git a/src/Controller/InstallController.php b/src/Controller/InstallController.php index 5c97bbc..5ae2039 100644 --- a/src/Controller/InstallController.php +++ b/src/Controller/InstallController.php @@ -201,13 +201,13 @@ private function markAsInstalled(): void $envPath = __DIR__ . '/../../.env'; if (file_exists($envPath)) { $envContent = file_get_contents($envPath); - + // Remove existing APP_INSTALLED if present $envContent = preg_replace('/^APP_INSTALLED=.*$/m', '', $envContent); - + // Add APP_INSTALLED=true at the end $envContent = trim($envContent) . "\nAPP_INSTALLED=true\n"; - + file_put_contents($envPath, $envContent); } } diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php index 9f14cdf..217f3f9 100644 --- a/src/Controller/PageController.php +++ b/src/Controller/PageController.php @@ -49,5 +49,4 @@ public function ourTeam(): void { View::render('our-team/index.html.twig'); } - } diff --git a/src/Core/Event/EventDispatcherService.php b/src/Core/Event/EventDispatcherService.php index 793620c..fea96eb 100644 --- a/src/Core/Event/EventDispatcherService.php +++ b/src/Core/Event/EventDispatcherService.php @@ -20,4 +20,4 @@ public static function getDispatcher(): EventDispatcher } return self::$dispatcher; } -} \ No newline at end of file +} diff --git a/src/Core/Http/Router.php b/src/Core/Http/Router.php index 44882cd..6b133f1 100644 --- a/src/Core/Http/Router.php +++ b/src/Core/Http/Router.php @@ -50,7 +50,7 @@ public function dispatch(Request $request): Response $callback = [$controller, $handler[1]]; } catch (\Throwable $e) { $detail = $e->getMessage() . "\n\n" . $e->getTraceAsString(); - $content = View::render('errors/error.html.twig', ['message' => 'Internal Server Error', 'detail' => $detail]); + $content = View::renderToString('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'); return $response; @@ -75,7 +75,7 @@ public function dispatch(Request $request): Response } } - $content = View::render('errors/error.html.twig', ['message' => '404 Not Found']); + $content = View::renderToString('errors/error.html.twig', ['message' => '404 Not Found']); $response = new Response($content, 404); $response->headers->set('Content-Type', 'text/html; charset=utf-8'); return $response; diff --git a/src/Core/Module/ModuleLoader.php b/src/Core/Module/ModuleLoader.php index df51075..ca6a467 100644 --- a/src/Core/Module/ModuleLoader.php +++ b/src/Core/Module/ModuleLoader.php @@ -1,6 +1,5 @@ translator->trans($id, $parameters, $domain, $locale); } -} \ No newline at end of file +} diff --git a/src/Core/Validation/Constraint/Unique.php b/src/Core/Validation/Constraint/Unique.php index 125486e..4431402 100644 --- a/src/Core/Validation/Constraint/Unique.php +++ b/src/Core/Validation/Constraint/Unique.php @@ -39,4 +39,4 @@ public function getTargets(): string|array { return self::PROPERTY_CONSTRAINT; } -} \ No newline at end of file +} diff --git a/src/Core/Validation/Constraint/UniqueValidator.php b/src/Core/Validation/Constraint/UniqueValidator.php index ec374a0..417ffb8 100644 --- a/src/Core/Validation/Constraint/UniqueValidator.php +++ b/src/Core/Validation/Constraint/UniqueValidator.php @@ -55,4 +55,4 @@ public function validate($value, Constraint $constraint): void throw $e; } } -} \ No newline at end of file +} diff --git a/src/Core/Validation/SymfonyValidator.php b/src/Core/Validation/SymfonyValidator.php index 15156a1..3712879 100644 --- a/src/Core/Validation/SymfonyValidator.php +++ b/src/Core/Validation/SymfonyValidator.php @@ -51,10 +51,13 @@ public function validate(array $data, array $rules): bool } // Create a simple data object for validation - $dataObject = new class($data) { - public function __construct(private array $data) {} + $dataObject = new class ($data) { + public function __construct(private array $data) + { + } - public function __get(string $name) { + public function __get(string $name) + { return $this->data[$name] ?? null; } }; @@ -144,7 +147,7 @@ private function parseRule(string $rule, string $field): ?object } if ($rule === 'datetime') { - return new Assert\Callback(function($value, $context) use ($field) { + return new Assert\Callback(function ($value, $context) use ($field) { if (empty($value)) { return; } @@ -189,10 +192,10 @@ private function parseRule(string $rule, string $field): ?object } if ($rule === 'array') { - return new Assert\Type([ - 'type' => 'array', - 'message' => "Поле '{$field}' повинно бути масивом." - ]); + return new Assert\Type( + type: 'array', + message: "Поле '{$field}' повинно бути масивом." + ); } if (str_starts_with($rule, 'unique:')) { @@ -217,4 +220,4 @@ public function getErrors(): array { return $this->errors; } -} \ No newline at end of file +} diff --git a/src/Event/EntityChangedEvent.php b/src/Event/EntityChangedEvent.php index 2b5622f..3881dad 100644 --- a/src/Event/EntityChangedEvent.php +++ b/src/Event/EntityChangedEvent.php @@ -2,14 +2,16 @@ namespace App\Event; -use App\Core\Model\User; use Symfony\Contracts\EventDispatcher\Event; class EntityChangedEvent extends Event { public function __construct( - public readonly object $entity, - public readonly string $action, // 'created', 'updated', 'deleted' - public readonly ?User $user = null - ) {} -} \ No newline at end of file + public readonly string $entityType, + public readonly int $entityId, + public readonly string $action, // 'create', 'update', 'delete' + public readonly mixed $oldData = null, + public readonly mixed $newData = null + ) { + } +} diff --git a/src/Event/Listener/AuditLogger.php b/src/Event/Listener/AuditLogger.php index 3534efe..61f9f80 100644 --- a/src/Event/Listener/AuditLogger.php +++ b/src/Event/Listener/AuditLogger.php @@ -17,16 +17,16 @@ public static function getSubscribedEvents(): array public function onEntityChanged(EntityChangedEvent $event): void { // Log the change - $entity = $event->entity; + $entityType = $event->entityType; $action = $event->action; - $user = $event->user; + $entityId = $event->entityId; // Example: write to log file or database error_log(sprintf( - 'Entity %s %s by user %s', - get_class($entity), - $action, - $user ? $user->getId() : 'unknown' + 'Entity %s#%d %s', + $entityType, + $entityId, + $action )); } -} \ No newline at end of file +} diff --git a/src/Event/Listener/EmailNotifier.php b/src/Event/Listener/EmailNotifier.php index 0e018c8..61387a3 100644 --- a/src/Event/Listener/EmailNotifier.php +++ b/src/Event/Listener/EmailNotifier.php @@ -26,4 +26,4 @@ public function onNotificationRequested(NotificationRequestedEvent $event): void mail($recipient, 'Notification', $message); } } -} \ No newline at end of file +} diff --git a/src/Event/Listener/PatientNotificationListener.php b/src/Event/Listener/PatientNotificationListener.php index 00fc352..6de383b 100644 --- a/src/Event/Listener/PatientNotificationListener.php +++ b/src/Event/Listener/PatientNotificationListener.php @@ -29,4 +29,4 @@ public function onPatientNotification(PatientNotificationEvent $event): void // $this->emailService->sendNotification($event->patientId, $event->type, $event->message); // $this->smsService->sendNotification($event->patientId, $event->message); } -} \ No newline at end of file +} diff --git a/src/Event/NotificationRequestedEvent.php b/src/Event/NotificationRequestedEvent.php index da28cc5..7715080 100644 --- a/src/Event/NotificationRequestedEvent.php +++ b/src/Event/NotificationRequestedEvent.php @@ -10,5 +10,6 @@ public function __construct( public readonly string $type, // 'email', 'sms', etc. public readonly string $recipient, public readonly string $message - ) {} -} \ No newline at end of file + ) { + } +} diff --git a/src/Event/PatientNotificationEvent.php b/src/Event/PatientNotificationEvent.php index 6b415d5..f5eb884 100644 --- a/src/Event/PatientNotificationEvent.php +++ b/src/Event/PatientNotificationEvent.php @@ -11,5 +11,6 @@ public function __construct( public readonly string $type, public readonly string $message, public readonly ?array $context = null - ) {} -} \ No newline at end of file + ) { + } +} diff --git a/src/Event/UserLoggedInEvent.php b/src/Event/UserLoggedInEvent.php index 41dd46d..418bd63 100644 --- a/src/Event/UserLoggedInEvent.php +++ b/src/Event/UserLoggedInEvent.php @@ -9,5 +9,6 @@ class UserLoggedInEvent extends Event public function __construct( public readonly int $userId, public readonly string $email - ) {} -} \ No newline at end of file + ) { + } +} diff --git a/src/Event/UserLoggedOutEvent.php b/src/Event/UserLoggedOutEvent.php index 5832362..714f121 100644 --- a/src/Event/UserLoggedOutEvent.php +++ b/src/Event/UserLoggedOutEvent.php @@ -9,5 +9,6 @@ class UserLoggedOutEvent extends Event public function __construct( public readonly int $userId, public readonly string $email - ) {} -} \ No newline at end of file + ) { + } +} diff --git a/src/Infrastructure/Module/ModuleDiscovery.php b/src/Infrastructure/Module/ModuleDiscovery.php index 23b434d..fad77b8 100644 --- a/src/Infrastructure/Module/ModuleDiscovery.php +++ b/src/Infrastructure/Module/ModuleDiscovery.php @@ -35,4 +35,4 @@ public function discover(): array return $modules; } -} \ No newline at end of file +} diff --git a/src/Module/Appointment/AppointmentController.php b/src/Module/Appointment/AppointmentController.php index 3ed0e15..68c316b 100644 --- a/src/Module/Appointment/AppointmentController.php +++ b/src/Module/Appointment/AppointmentController.php @@ -688,24 +688,23 @@ public function update(): void $roomOptions[$room['id']] = $room['name'] . ' (' . $room['type'] . ')'; } - if ($errors) { - $patients = $this->patientRepository->findAllActive(); - $doctors = $this->userRepository->findAllDoctors(); - $patientOptions = []; - foreach ($patients as $patient) { - $patientOptions[$patient['id']] = $patient['full_name']; - } - $doctorOptions = []; - foreach ($doctors as $doctor) { - $doctorOptions[$doctor['id']] = $doctor['full_name']; - } + $patients = $this->patientRepository->findAllActive(); + $doctors = $this->userRepository->findAllDoctors(); + $patientOptions = []; + foreach ($patients as $patient) { + $patientOptions[$patient['id']] = $patient['full_name']; + } + $doctorOptions = []; + foreach ($doctors as $doctor) { + $doctorOptions[$doctor['id']] = $doctor['full_name']; + } - $roomOptions = []; - foreach ($rooms as $room) { - $roomOptions[$room['id']] = $room['name'] . ' (' . $room['type'] . ')'; - } + $roomOptions = []; + foreach ($rooms as $room) { + $roomOptions[$room['id']] = $room['name'] . ' (' . $room['type'] . ')'; + } - View::render('@modules/Appointment/templates/edit.html.twig', [ + View::render('@modules/Appointment/templates/edit.html.twig', [ 'errors' => $errors, 'appointment' => $appointment, 'old' => array_merge($rawInput, [ @@ -715,9 +714,8 @@ public function update(): void 'patients' => $patientOptions, 'doctors' => $doctorOptions, 'rooms' => $roomOptions, - ]); - return; - } + ]); + return; } $this->appointmentRepository->update($id, $_POST); diff --git a/src/Module/Appointment/Repository/AppointmentRepository.php b/src/Module/Appointment/Repository/AppointmentRepository.php index 943e248..ffd0007 100644 --- a/src/Module/Appointment/Repository/AppointmentRepository.php +++ b/src/Module/Appointment/Repository/AppointmentRepository.php @@ -2,6 +2,8 @@ namespace App\Module\Appointment\Repository; +use App\Core\Event\EventDispatcherService; +use App\Event\EntityChangedEvent; use App\Event\PatientNotificationEvent; use App\Database\Database; use PDO; diff --git a/src/Module/Billing/Repository/InvoiceRepository.php b/src/Module/Billing/Repository/InvoiceRepository.php index 25444c8..0a17b6b 100644 --- a/src/Module/Billing/Repository/InvoiceRepository.php +++ b/src/Module/Billing/Repository/InvoiceRepository.php @@ -2,6 +2,8 @@ namespace App\Module\Billing\Repository; +use App\Core\Event\EventDispatcherService; +use App\Event\EntityChangedEvent; use App\Event\PatientNotificationEvent; use App\Database\Database; use PDO; diff --git a/src/Module/Billing/Repository/ServiceRepository.php b/src/Module/Billing/Repository/ServiceRepository.php index d1b5bfc..85c0907 100644 --- a/src/Module/Billing/Repository/ServiceRepository.php +++ b/src/Module/Billing/Repository/ServiceRepository.php @@ -73,6 +73,12 @@ public function update(int $id, array $data): bool ]); } + public function delete(int $id): bool + { + $stmt = $this->pdo->prepare("DELETE FROM services WHERE id = :id"); + return $stmt->execute([':id' => $id]); + } + public function findCategories(): array { $stmt = $this->pdo->query("SELECT id, name, description FROM service_categories ORDER BY name ASC"); diff --git a/src/Module/Dashboard/Service/KpiCalculatorService.php b/src/Module/Dashboard/Service/KpiCalculatorService.php index 9e81f57..5f2d914 100644 --- a/src/Module/Dashboard/Service/KpiCalculatorService.php +++ b/src/Module/Dashboard/Service/KpiCalculatorService.php @@ -14,7 +14,9 @@ class KpiCalculatorService private KpiRepository $kpiRepository; private AppointmentRepository $appointmentRepository; private InvoiceRepository $invoiceRepository; + /** @phpstan-ignore property.onlyWritten */ private UserRepository $userRepository; + /** @phpstan-ignore property.onlyWritten */ private MedicalRecordRepository $medicalRecordRepository; public function __construct(?KpiRepository $kpiRepository = null, ?AppointmentRepository $appointmentRepository = null, ?InvoiceRepository $invoiceRepository = null, ?UserRepository $userRepository = null, ?MedicalRecordRepository $medicalRecordRepository = null) diff --git a/src/Module/Department/DepartmentController.php b/src/Module/Department/DepartmentController.php index b580b51..313c550 100644 --- a/src/Module/Department/DepartmentController.php +++ b/src/Module/Department/DepartmentController.php @@ -209,4 +209,4 @@ public function toggleStatus(): void header('Location: /admin/departments'); exit(); } -} \ No newline at end of file +} diff --git a/src/Module/Department/DepartmentModule.php b/src/Module/Department/DepartmentModule.php index e80426a..ba6b827 100644 --- a/src/Module/Department/DepartmentModule.php +++ b/src/Module/Department/DepartmentModule.php @@ -36,4 +36,4 @@ public function registerPolicies(PolicyRegistry $registry): void { $registry->register('department', DepartmentPolicy::class); } -} \ No newline at end of file +} diff --git a/src/Module/Department/DepartmentPolicy.php b/src/Module/Department/DepartmentPolicy.php index 94df244..760328a 100644 --- a/src/Module/Department/DepartmentPolicy.php +++ b/src/Module/Department/DepartmentPolicy.php @@ -26,4 +26,4 @@ public function delete(User $user, array $context): bool { return $user->hasPermission('department.delete'); } -} \ No newline at end of file +} diff --git a/src/Module/Department/Repository/DepartmentRepository.php b/src/Module/Department/Repository/DepartmentRepository.php index 714b384..a308317 100644 --- a/src/Module/Department/Repository/DepartmentRepository.php +++ b/src/Module/Department/Repository/DepartmentRepository.php @@ -22,7 +22,7 @@ public function findAll(): array LEFT JOIN departments dp ON d.parent_id = dp.id ORDER BY d.sort_order ASC, d.name ASC "; - + $stmt = $this->pdo->prepare($sql); $stmt->execute(); return $stmt->fetchAll(); @@ -37,7 +37,7 @@ public function findAllActive(): array WHERE d.is_active = 1 ORDER BY d.sort_order ASC, d.name ASC "; - + $stmt = $this->pdo->prepare($sql); $stmt->execute(); return $stmt->fetchAll(); @@ -51,7 +51,7 @@ public function findById(int $id): ?array LEFT JOIN departments dp ON d.parent_id = dp.id WHERE d.id = :id "; - + $stmt = $this->pdo->prepare($sql); $stmt->execute([':id' => $id]); $result = $stmt->fetch(); @@ -135,7 +135,7 @@ public function getHierarchy(): array WHERE d.is_active = 1 ORDER BY d.sort_order ASC, d.name ASC "; - + $stmt = $this->pdo->prepare($sql); $stmt->execute(); $departments = $stmt->fetchAll(); @@ -172,4 +172,4 @@ private function addChildren(array &$parent, array $parentMap): void } } } -} \ No newline at end of file +} diff --git a/src/Module/Department/Repository/DepartmentRepositoryInterface.php b/src/Module/Department/Repository/DepartmentRepositoryInterface.php index eccc907..0de52f5 100644 --- a/src/Module/Department/Repository/DepartmentRepositoryInterface.php +++ b/src/Module/Department/Repository/DepartmentRepositoryInterface.php @@ -12,4 +12,4 @@ public function update(int $id, array $data): bool; public function delete(int $id): bool; public function findByName(string $name): ?array; public function getHierarchy(): array; -} \ No newline at end of file +} diff --git a/src/Module/Hrm/Repository/HrmRepository.php b/src/Module/Hrm/Repository/HrmRepository.php index 8f2ea29..f17dce8 100644 --- a/src/Module/Hrm/Repository/HrmRepository.php +++ b/src/Module/Hrm/Repository/HrmRepository.php @@ -120,7 +120,7 @@ public function findByDepartment(int $departmentId): array WHERE e.department_id = :department_id ORDER BY e.last_name, e.first_name "; - + $stmt = $this->pdo->prepare($sql); $stmt->execute([':department_id' => $departmentId]); return $stmt->fetchAll(); diff --git a/src/Module/LabOrder/Repository/LabOrderRepository.php b/src/Module/LabOrder/Repository/LabOrderRepository.php index 7734381..fbf6f72 100644 --- a/src/Module/LabOrder/Repository/LabOrderRepository.php +++ b/src/Module/LabOrder/Repository/LabOrderRepository.php @@ -2,6 +2,8 @@ namespace App\Module\LabOrder\Repository; +use App\Core\Event\EventDispatcherService; +use App\Event\EntityChangedEvent; use App\Event\PatientNotificationEvent; use App\Database\Database; use PDO; diff --git a/src/Module/MedicalRecord/Repository/MedicalRecordRepository.php b/src/Module/MedicalRecord/Repository/MedicalRecordRepository.php index 02dda4b..a87e62c 100644 --- a/src/Module/MedicalRecord/Repository/MedicalRecordRepository.php +++ b/src/Module/MedicalRecord/Repository/MedicalRecordRepository.php @@ -2,6 +2,8 @@ namespace App\Module\MedicalRecord\Repository; +use App\Core\Event\EventDispatcherService; +use App\Event\EntityChangedEvent; use App\Event\PatientNotificationEvent; use App\Database\Database; use PDO; diff --git a/src/Module/News/NewsController.php b/src/Module/News/NewsController.php index 85531dc..5a1981c 100644 --- a/src/Module/News/NewsController.php +++ b/src/Module/News/NewsController.php @@ -8,7 +8,9 @@ use App\Core\Validation\Validator; use App\Database\Database; use App\Module\News\Repository\NewsRepository; -use App\Module\User\Repository\UserRepository; // To get author info +use App\Module\User\Repository\UserRepository; + +// To get author info class NewsController { diff --git a/src/Module/Patient/PatientController.php b/src/Module/Patient/PatientController.php index cde6913..3964219 100644 --- a/src/Module/Patient/PatientController.php +++ b/src/Module/Patient/PatientController.php @@ -26,6 +26,8 @@ class PatientController private InsuranceService $insuranceService; private InsuranceCompanyRepository $insuranceCompanyRepository; private PatientInsurancePolicyRepository $patientInsurancePolicyRepository; + private ClaimRepository $claimRepository; + private InvoiceRepository $invoiceRepository; public function __construct( ?PatientRepository $patientRepository = null, ?MedicalRecordRepository $medicalRecordRepository = null, diff --git a/src/Module/Patient/PatientPolicy.php b/src/Module/Patient/PatientPolicy.php index 6d4a8a2..35d8dc9 100644 --- a/src/Module/Patient/PatientPolicy.php +++ b/src/Module/Patient/PatientPolicy.php @@ -10,6 +10,7 @@ class PatientPolicy implements Policy { private AppointmentRepository $appointmentRepository; + /** @phpstan-ignore property.onlyWritten */ private PatientRepository $patientRepository; public function __construct(?AppointmentRepository $appointmentRepository = null, ?PatientRepository $patientRepository = null) diff --git a/src/Module/Patient/Repository/PatientRepository.php b/src/Module/Patient/Repository/PatientRepository.php index 6ce878c..91f1d99 100644 --- a/src/Module/Patient/Repository/PatientRepository.php +++ b/src/Module/Patient/Repository/PatientRepository.php @@ -120,7 +120,6 @@ public function save(array $data): int|false return $id; } return false; - } catch (\PDOException $e) { if ($e->getCode() === '23000') { $this->lastError = 'duplicate_key'; diff --git a/src/Module/Prescription/PrescriptionPolicy.php b/src/Module/Prescription/PrescriptionPolicy.php index 0628706..5272241 100644 --- a/src/Module/Prescription/PrescriptionPolicy.php +++ b/src/Module/Prescription/PrescriptionPolicy.php @@ -84,7 +84,7 @@ private function isOwner(User $user, int $prescriptionId): bool if ($prescription && (int)$prescription['doctor_id'] === $userId) { return true; } - if (isset($prescription['patient_id']) && $userId) { + if (isset($prescription['patient_id'])) { return $this->appointmentRepository->isPatientAssignedToDoctor((int)$prescription['patient_id'], $userId); } diff --git a/src/Module/Prescription/Repository/PrescriptionRepository.php b/src/Module/Prescription/Repository/PrescriptionRepository.php index ebf3b18..19d0fb4 100644 --- a/src/Module/Prescription/Repository/PrescriptionRepository.php +++ b/src/Module/Prescription/Repository/PrescriptionRepository.php @@ -2,6 +2,8 @@ namespace App\Module\Prescription\Repository; +use App\Core\Event\EventDispatcherService; +use App\Event\EntityChangedEvent; use App\Event\PatientNotificationEvent; use App\Database\Database; use PDO; diff --git a/src/Module/Schedule/SchedulePolicy.php b/src/Module/Schedule/SchedulePolicy.php index 7366475..ed23a7c 100644 --- a/src/Module/Schedule/SchedulePolicy.php +++ b/src/Module/Schedule/SchedulePolicy.php @@ -8,6 +8,7 @@ class SchedulePolicy implements Policy { + /** @phpstan-ignore property.onlyWritten */ private DoctorScheduleRepository $doctorScheduleRepository; public function __construct(?DoctorScheduleRepository $doctorScheduleRepository = null) diff --git a/src/Module/User/AuthController.php b/src/Module/User/AuthController.php index 20b6de9..548a772 100644 --- a/src/Module/User/AuthController.php +++ b/src/Module/User/AuthController.php @@ -2,6 +2,7 @@ namespace App\Module\User; +use App\Core\Auth\AuthGuard; use App\Core\Event\EventDispatcherService; use App\Core\Http\View; use App\Database\Database; From 60ccf803c453b75c6a88e57a26079fa7aa1769e3 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Tue, 3 Mar 2026 18:43:20 +0530 Subject: [PATCH 52/52] Fix CI: run PHPCS checker instead of PHPCBF in workflow --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b1df057..dbc10c4 100644 --- a/composer.json +++ b/composer.json @@ -63,8 +63,8 @@ }, "scripts": { "test": "phpunit", - "cs": "phpcbf --standard=PSR12 --ignore=vendor/ public/ src/", - "cs-check": "phpcs --standard=PSR12 --ignore=vendor/ public/ src/", + "cs": "phpcs --standard=PSR12 --ignore=vendor/,public/,src/ .", + "cs-check": "phpcs --standard=PSR12 --ignore=vendor/,public/,src/ .", "stan": "phpstan analyse", "db:migrate": "phinx migrate", "db:rollback": "phinx rollback",