diff --git a/.gitignore b/.gitignore index 84ff1a15..7da36196 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ node_modules/ .phpunit.cache/ build/ public/uploads/* +composer.phar +composer-setup.php diff --git a/composer.json b/composer.json index 2efdbe7b..dbc10c49 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": { @@ -62,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", diff --git a/composer.lock b/composer.lock index ef14dceb..1481b5c5 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", diff --git a/config/services.php b/config/services.php new file mode 100644 index 00000000..c272b9d9 --- /dev/null +++ b/config/services.php @@ -0,0 +1,333 @@ +register('pdo', PDO::class) + ->setFactory([\App\Database\Database::class, 'getInstance']) + ->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); + + $container->register(\App\Core\Service\TranslationService::class) + ->setPublic(true); + + $container->register(\App\Core\Service\AuditLogger::class) + ->setArguments([new Reference('pdo')]) + ->setPublic(true); + + // 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); + + // 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); + + // 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), + 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); + + // 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); + + // 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\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) + ->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); + + // 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\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); + $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), + 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\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), + ]) + ->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); + + // 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\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\Hrm\Repository\HrmRepository::class), + ])->setPublic(true); + + // Room + $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), + ])->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) + ->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); + + // 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); +}; diff --git a/public/index.php b/public/index.php index aef91d1d..282057e5 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; } @@ -17,6 +21,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,17 +56,28 @@ 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(); +$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(); -$eventDispatcher = new EventDispatcher(); +$moduleManager = $container->get(ModuleManager::class); +if ($eventDispatcher === null) { + $eventDispatcher = new EventDispatcher(); +} $moduleLoader = new ModuleLoader($moduleManager); $moduleLoader->loadAll(); @@ -70,6 +87,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); @@ -87,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']; }); @@ -109,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 +} diff --git a/src/Console/Command/CalculateKpisCommand.php b/src/Console/Command/CalculateKpisCommand.php index ebc2ca03..7a213c03 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 85ed30af..c8399fb3 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 9a777d16..759e7a64 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 5c97bbcb..5ae20391 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 9f14cdf1..217f3f9e 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 793620c1..fea96eb3 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 f0058051..6b133f14 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,8 +41,20 @@ public function dispatch(Request $request): Response $handler = $route['handler']; if (is_array($handler) && is_string($handler[0])) { - $controller = new $handler[0](); - $callback = [$controller, $handler[1]]; + 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::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; + } } else { $callback = $handler; } @@ -56,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/Http/View.php b/src/Core/Http/View.php index a37e87e1..119e3eb7 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; diff --git a/src/Core/Module/ModuleLoader.php b/src/Core/Module/ModuleLoader.php index df510751..ca6a4672 100644 --- a/src/Core/Module/ModuleLoader.php +++ b/src/Core/Module/ModuleLoader.php @@ -1,6 +1,5 @@ pdo = Database::getInstance(); + $this->pdo = $pdo ?? Database::getInstance(); + if ($uploadDir !== null) { + $this->uploadDir = $uploadDir; + } if (!is_dir($this->uploadDir)) { mkdir($this->uploadDir, 0775, true); } diff --git a/src/Core/Service/AuditLogger.php b/src/Core/Service/AuditLogger.php index f9d66c01..b5e9d343 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 diff --git a/src/Core/Service/NotificationService.php b/src/Core/Service/NotificationService.php index dd1031c8..97733bd6 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 diff --git a/src/Core/Service/TranslationService.php b/src/Core/Service/TranslationService.php index 0f02caf1..397ab764 100644 --- a/src/Core/Service/TranslationService.php +++ b/src/Core/Service/TranslationService.php @@ -9,7 +9,7 @@ class TranslationService { - private TranslatorInterface $translator; + private Translator $translator; private string $currentLocale = 'uk'; private array $availableLocales = []; // Cache for available locales @@ -138,4 +138,4 @@ public function trans(string $id, array $parameters = [], ?string $domain = null { return $this->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 125486ea..4431402a 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 ec374a03..417ffb8e 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 15156a1c..3712879d 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/Database/Database.php b/src/Database/Database.php index 788a90fe..868cd0fe 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; diff --git a/src/Event/EntityChangedEvent.php b/src/Event/EntityChangedEvent.php index 2b5622f6..3881dadf 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 3534efef..61f9f809 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 0e018c87..61387a30 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 00fc352e..6de383b4 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 da28cc5d..77150807 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 6b415d59..f5eb8847 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 41dd46dd..418bd630 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 5832362a..714f1214 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/DI/ContainerFactory.php b/src/Infrastructure/DI/ContainerFactory.php new file mode 100644 index 00000000..810c289d --- /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; + } +} diff --git a/src/Infrastructure/Module/ModuleDiscovery.php b/src/Infrastructure/Module/ModuleDiscovery.php index 23b434d6..fad77b8f 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/Admin/AdminController.php b/src/Module/Admin/AdminController.php index c2c4317d..a5ff1a6f 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'); diff --git a/src/Module/Appointment/AppointmentController.php b/src/Module/Appointment/AppointmentController.php index 378ed88e..68c316bb 100644 --- a/src/Module/Appointment/AppointmentController.php +++ b/src/Module/Appointment/AppointmentController.php @@ -25,27 +25,43 @@ class AppointmentController private SchedulingService $schedulingService; 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 - ); + private DoctorScheduleRepository $doctorScheduleRepository; + private ScheduleExceptionRepository $scheduleExceptionRepository; + + public function __construct( + ?AppointmentRepository $appointmentRepository = null, + ?PatientRepository $patientRepository = null, + ?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 + ) { + $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(); + + $this->doctorScheduleRepository = $doctorScheduleRepository ?? new DoctorScheduleRepository(); + $this->scheduleExceptionRepository = $scheduleExceptionRepository ?? new ScheduleExceptionRepository(); + + if ($schedulingService !== null) { + $this->schedulingService = $schedulingService; + } else { + $this->schedulingService = new SchedulingService( + $this->doctorScheduleRepository, + $this->scheduleExceptionRepository, + $this->appointmentRepository, + $this->serviceRepository, + $this->roomRepository + ); + } } public function index(): void @@ -672,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, [ @@ -699,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/AppointmentPolicy.php b/src/Module/Appointment/AppointmentPolicy.php index a64bb834..ec32be4a 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 diff --git a/src/Module/Appointment/Repository/AppointmentRepository.php b/src/Module/Appointment/Repository/AppointmentRepository.php index 943e248e..ffd0007b 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/BillingController.php b/src/Module/Billing/BillingController.php index 7cef2cde..9ace3d33 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 558918b7..6c4147af 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 diff --git a/src/Module/Billing/Repository/InvoiceRepository.php b/src/Module/Billing/Repository/InvoiceRepository.php index 25444c8b..0a17b6be 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 d1b5bfc5..85c0907c 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/DashboardController.php b/src/Module/Dashboard/DashboardController.php index 32d4ca8a..9742da0d 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 4ecd0e8e..749f6f11 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(); } /** diff --git a/src/Module/Dashboard/Service/KpiCalculatorService.php b/src/Module/Dashboard/Service/KpiCalculatorService.php index 1487e9d4..5f2d914b 100644 --- a/src/Module/Dashboard/Service/KpiCalculatorService.php +++ b/src/Module/Dashboard/Service/KpiCalculatorService.php @@ -14,16 +14,18 @@ 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() + 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 diff --git a/src/Module/Department/DepartmentController.php b/src/Module/Department/DepartmentController.php index 6e78866d..313c550a 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 @@ -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 e80426ad..ba6b827d 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 94df2441..760328a3 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 714b3849..a3083171 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 eccc907a..0de52f5b 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/HrmController.php b/src/Module/Hrm/HrmController.php index 29588732..0b82005a 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/Hrm/Repository/HrmRepository.php b/src/Module/Hrm/Repository/HrmRepository.php index 8f2ea29a..f17dce89 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/Insurance/InsuranceController.php b/src/Module/Insurance/InsuranceController.php index 867bb189..abcd3a4b 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(), diff --git a/src/Module/Inventory/InventoryController.php b/src/Module/Inventory/InventoryController.php index 8b70ec5b..6e830094 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 diff --git a/src/Module/Kpi/KpiController.php b/src/Module/Kpi/KpiController.php index d7a5b72e..cb220968 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 --- diff --git a/src/Module/LabOrder/LabOrderController.php b/src/Module/LabOrder/LabOrderController.php index fa53d24a..85433f2e 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 diff --git a/src/Module/LabOrder/LabOrderPolicy.php b/src/Module/LabOrder/LabOrderPolicy.php index b1e0364f..b504144e 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/Repository/LabOrderRepository.php b/src/Module/LabOrder/Repository/LabOrderRepository.php index 7734381e..fbf6f72b 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/LabOrder/Service/LabImportService.php b/src/Module/LabOrder/Service/LabImportService.php index 17e0091f..804c92e2 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(); } /** diff --git a/src/Module/MedicalRecord/MedicalRecordController.php b/src/Module/MedicalRecord/MedicalRecordController.php index 9cae16a1..1c5904b2 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 diff --git a/src/Module/MedicalRecord/MedicalRecordPolicy.php b/src/Module/MedicalRecord/MedicalRecordPolicy.php index 017c6486..f96f1451 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/MedicalRecord/Repository/MedicalRecordRepository.php b/src/Module/MedicalRecord/Repository/MedicalRecordRepository.php index 02dda4b2..a87e62c9 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 7fb918e2..5a1981ce 100644 --- a/src/Module/News/NewsController.php +++ b/src/Module/News/NewsController.php @@ -8,17 +8,19 @@ 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 { 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 diff --git a/src/Module/Notification/NotificationController.php b/src/Module/Notification/NotificationController.php index 434ea959..fc1870f6 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 68e8fa80..39642192 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; @@ -25,19 +26,30 @@ 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( + private ClaimRepository $claimRepository; + private InvoiceRepository $invoiceRepository; + public function __construct( + ?PatientRepository $patientRepository = null, + ?MedicalRecordRepository $medicalRecordRepository = null, + ?AppointmentRepository $appointmentRepository = null, + ?InsuranceService $insuranceService = null, + ?InsuranceCompanyRepository $insuranceCompanyRepository = 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 ); } diff --git a/src/Module/Patient/PatientPolicy.php b/src/Module/Patient/PatientPolicy.php index cf66bbb6..35d8dc9b 100644 --- a/src/Module/Patient/PatientPolicy.php +++ b/src/Module/Patient/PatientPolicy.php @@ -10,12 +10,13 @@ class PatientPolicy implements Policy { private AppointmentRepository $appointmentRepository; + /** @phpstan-ignore property.onlyWritten */ 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/Patient/Repository/PatientRepository.php b/src/Module/Patient/Repository/PatientRepository.php index b84d1573..91f1d996 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 @@ -117,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'; @@ -227,9 +229,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 +260,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; diff --git a/src/Module/Prescription/PrescriptionController.php b/src/Module/Prescription/PrescriptionController.php index 7c92baee..1c4927f3 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 diff --git a/src/Module/Prescription/PrescriptionPolicy.php b/src/Module/Prescription/PrescriptionPolicy.php index e4525505..52722414 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 @@ -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 ebf3b18d..19d0fb4f 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/Room/RoomController.php b/src/Module/Room/RoomController.php index 1ddacca0..88b35e25 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 diff --git a/src/Module/Schedule/ScheduleController.php b/src/Module/Schedule/ScheduleController.php index 683f11c1..1a2c456d 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 12365c55..ed23a7c1 100644 --- a/src/Module/Schedule/SchedulePolicy.php +++ b/src/Module/Schedule/SchedulePolicy.php @@ -8,11 +8,12 @@ class SchedulePolicy implements Policy { + /** @phpstan-ignore property.onlyWritten */ 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 diff --git a/src/Module/User/AuthController.php b/src/Module/User/AuthController.php index 1ad5a59f..548a772c 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; @@ -10,18 +11,25 @@ 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 { private UserRepository $userRepository; private AuthConfigRepository $authConfigRepository; private RoleRepository $roleRepository; + private ?MfaService $mfaService = null; + private OAuthController $oauthController; + private \App\Core\Repository\SettingsRepository $settingsRepository; - public function __construct() + 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 = 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(); + $this->settingsRepository = $settingsRepository ?? new \App\Core\Repository\SettingsRepository(); + $this->oauthController = $oauthController ?? new OAuthController(); } public function showLoginForm(): void @@ -63,10 +71,9 @@ 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(); - $settingsRepository = new \App\Core\Repository\SettingsRepository(); - $mfaPolicy = $settingsRepository->getMfaPolicy(); - $mfaForceRoles = $settingsRepository->getMfaForceRoles(); + $mfaService = $this->mfaService ?? new MfaService(); + $mfaPolicy = $this->settingsRepository->getMfaPolicy(); + $mfaForceRoles = $this->settingsRepository->getMfaForceRoles(); if ($mfaPolicy === 'disabled') { $_SESSION['user'] = [ @@ -134,7 +141,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 e2a5ded4..4cc790ff 100644 --- a/src/Module/User/MfaController.php +++ b/src/Module/User/MfaController.php @@ -11,11 +11,18 @@ class MfaController { private MfaService $mfaService; private \App\Module\User\Repository\UserRepository $userRepository; - - public function __construct() - { - $this->mfaService = new MfaService(); - $this->userRepository = new UserRepository(); + 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 @@ -52,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'] = 'Двофакторна автентифікація вимкнена в налаштуваннях системи.'; @@ -211,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'] = 'Двофакторна автентифікація вимкнена в налаштуваннях системи.'; @@ -265,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'], @@ -319,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'], @@ -390,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'], @@ -437,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'], @@ -535,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'], diff --git a/src/Module/User/MfaService.php b/src/Module/User/MfaService.php index 2b2379fd..bf18d223 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 diff --git a/src/Module/User/OAuthController.php b/src/Module/User/OAuthController.php index 6899d85f..774a4bbf 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 dc1a3e0e..638dc677 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() + public function __construct(?UserRepository $userRepository = null, ?AuthConfigRepository $authConfigRepository = null, ?UserOAuthIdentityRepository $userOAuthIdentityRepository = null, ?HrmRepository $hrmRepository = null, ?SettingsRepository $settingsRepository = 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(); + $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,