Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/Command/ImportCodesCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace OpenCoreEMR\CLI\ImportCodes\Command;

use OpenCoreEMR\CLI\ImportCodes\Config\ConfigAccessorInterface;
use OpenCoreEMR\CLI\ImportCodes\Config\GlobalsAccessor;
use OpenCoreEMR\CLI\ImportCodes\Service\CodeImporter;
use OpenCoreEMR\CLI\ImportCodes\Service\OpenEMRConnector;
use OpenCoreEMR\CLI\ImportCodes\Service\MetadataDetector;
Expand All @@ -31,13 +33,20 @@ class ImportCodesCommand extends Command
private ?OutputInterface $output = null;

public function __construct(
private readonly CodeImporter $importer = new CodeImporter(),
private readonly OpenEMRConnector $connector = new OpenEMRConnector(),
?ConfigAccessorInterface $config = null,
?CodeImporter $importer = null,
?OpenEMRConnector $connector = null,
private readonly MetadataDetector $detector = new MetadataDetector()
) {
$config ??= new GlobalsAccessor();
$this->importer = $importer ?? new CodeImporter($config);
$this->connector = $connector ?? new OpenEMRConnector($config);
parent::__construct();
}

private readonly CodeImporter $importer;
private readonly OpenEMRConnector $connector;

protected function configure()
{
$supportedTypes = implode(', ', self::SUPPORTED_TYPES);
Expand Down
42 changes: 42 additions & 0 deletions src/Config/ConfigAccessorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* ConfigAccessorInterface.php
* Interface for accessing configuration values with type safety
*
* @package OpenCoreEMR\CLI\ImportCodes
* @link https://opencoreemr.com
* @author Michael A. Smith <[email protected]>
* @copyright Copyright (c) 2026 OpenCoreEMR Inc
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

namespace OpenCoreEMR\CLI\ImportCodes\Config;

interface ConfigAccessorInterface
{
/**
* Get a configuration value
*/
public function get(string $key, mixed $default = null): mixed;

/**
* Check if a configuration key exists
*/
public function has(string $key): bool;

/**
* Get a configuration value as a string
*/
public function getString(string $key, string $default = ''): string;

/**
* Get a configuration value as a boolean
*/
public function getBoolean(string $key, bool $default = false): bool;

/**
* Get a configuration value as an integer
*/
public function getInt(string $key, int $default = 0): int;
}
79 changes: 79 additions & 0 deletions src/Config/GlobalsAccessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/**
* GlobalsAccessor.php
* Centralized accessor for OpenEMR $GLOBALS with type-safe getters
*
* @package OpenCoreEMR\CLI\ImportCodes
* @link https://opencoreemr.com
* @author Michael A. Smith <[email protected]>
* @copyright Copyright (c) 2026 OpenCoreEMR Inc
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

namespace OpenCoreEMR\CLI\ImportCodes\Config;

class GlobalsAccessor implements ConfigAccessorInterface
{
/**
* @inheritDoc
*/
public function get(string $key, mixed $default = null): mixed
{
return $GLOBALS[$key] ?? $default;
}

/**
* @inheritDoc
*/
public function has(string $key): bool
{
return isset($GLOBALS[$key]);
}

/**
* @inheritDoc
*/
public function getString(string $key, string $default = ''): string
{
$value = $this->get($key, $default);
if (is_string($value)) {
return $value;
}
return is_scalar($value) || $value === null ? (string) $value : $default;
}

/**
* @inheritDoc
*/
public function getBoolean(string $key, bool $default = false): bool
{
$value = $this->get($key, $default);
if (is_bool($value)) {
return $value;
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}

/**
* @inheritDoc
*/
public function getInt(string $key, int $default = 0): int
{
$value = $this->get($key, $default);
if (is_int($value)) {
return $value;
}
return is_numeric($value) ? (int) $value : $default;
}

/**
* Get all globals (use sparingly)
*
* @return array<string, mixed>
*/
public function all(): array
{
return $GLOBALS;
}
}
11 changes: 9 additions & 2 deletions src/Service/CodeImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace OpenCoreEMR\CLI\ImportCodes\Service;

use OpenCoreEMR\CLI\ImportCodes\Config\ConfigAccessorInterface;
use OpenCoreEMR\CLI\ImportCodes\Exception\CodeImportException;
use OpenCoreEMR\CLI\ImportCodes\Exception\FileSystemException;
use OpenCoreEMR\CLI\ImportCodes\Exception\DatabaseLockException;
Expand All @@ -24,6 +25,11 @@ class CodeImporter
private int $lockRetryDelaySeconds = 30;
private bool $waitedForLock = false;

public function __construct(
private readonly ConfigAccessorInterface $config
) {
}

/**
* Validate temporary directory is writable
*
Expand Down Expand Up @@ -283,11 +289,12 @@ public function cleanup(string $type): void
*/
public function getStagingFiles(string $type): array
{
if (!isset($GLOBALS['temporary_files_dir']) || !is_string($GLOBALS['temporary_files_dir'])) {
$tempDir = $this->config->getString('temporary_files_dir');
if ($tempDir === '') {
return [];
}

$stagingDir = $GLOBALS['temporary_files_dir'] . '/' . $type;
$stagingDir = $tempDir . '/' . $type;
if (!is_dir($stagingDir)) {
return [];
}
Expand Down
14 changes: 11 additions & 3 deletions src/Service/OpenEMRConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace OpenCoreEMR\CLI\ImportCodes\Service;

use OpenCoreEMR\CLI\ImportCodes\Config\ConfigAccessorInterface;
use OpenCoreEMR\CLI\ImportCodes\Exception\OpenEMRConnectorException;

class OpenEMRConnector
Expand All @@ -21,6 +22,11 @@ class OpenEMRConnector
private string $site;
private bool $initialized = false;

public function __construct(
private readonly ConfigAccessorInterface $config
) {
}

/**
* Initialize connection to OpenEMR
*/
Expand Down Expand Up @@ -52,15 +58,16 @@ public function initialize(string $openemrPath, string $site = 'default'): void
require_once $standardTablesPath;

// Verify database connection (using OpenEMR's own validation method)
if (!isset($GLOBALS['dbh']) || !$GLOBALS['dbh']) {
if (!$this->config->get('dbh')) {
throw new OpenEMRConnectorException(
"OpenEMR database connection failed - " .
"check database configuration and ensure MySQL is running"
);
}

// Verify ADODB connection is working
if (!isset($GLOBALS['adodb']['db']) || !$GLOBALS['adodb']['db']) {
$adodb = $this->config->get('adodb');
if (!is_array($adodb) || !isset($adodb['db']) || !$adodb['db']) {
throw new OpenEMRConnectorException("OpenEMR ADODB database connection not established");
}

Expand Down Expand Up @@ -111,7 +118,8 @@ public function getTempDir(): string
throw new OpenEMRConnectorException("OpenEMR connector not initialized");
}

return $GLOBALS['temporary_files_dir'] ?? sys_get_temp_dir();
$tempDir = $this->config->getString('temporary_files_dir');
return $tempDir !== '' ? $tempDir : sys_get_temp_dir();
}

/**
Expand Down
3 changes: 2 additions & 1 deletion tests/E2E/RealVocabularyImportE2ETest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
namespace OpenCoreEMR\CLI\ImportCodes\Tests\E2E;

use OpenCoreEMR\CLI\ImportCodes\Command\ImportCodesCommand;
use OpenCoreEMR\CLI\ImportCodes\Config\GlobalsAccessor;
use OpenCoreEMR\CLI\ImportCodes\Service\OpenEMRConnector;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
Expand Down Expand Up @@ -61,7 +62,7 @@ protected function setUp(): void
$this->markTestSkipped('OpenEMR not available. Run inside Docker: task dev:start');
}

$this->connector = new OpenEMRConnector();
$this->connector = new OpenEMRConnector(new GlobalsAccessor());
try {
$this->connector->initialize(self::OPENEMR_PATH, self::SITE);
} catch (\Throwable $e) {
Expand Down
5 changes: 3 additions & 2 deletions tests/E2E/VocabularyImportE2ETest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
namespace OpenCoreEMR\CLI\ImportCodes\Tests\E2E;

use OpenCoreEMR\CLI\ImportCodes\Command\ImportCodesCommand;
use OpenCoreEMR\CLI\ImportCodes\Config\GlobalsAccessor;
use OpenCoreEMR\CLI\ImportCodes\Service\OpenEMRConnector;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
Expand Down Expand Up @@ -52,7 +53,7 @@ protected function setUp(): void
);
}

$this->connector = new OpenEMRConnector();
$this->connector = new OpenEMRConnector(new GlobalsAccessor());
try {
$this->connector->initialize(self::OPENEMR_PATH, self::SITE);
} catch (\Throwable $e) {
Expand Down Expand Up @@ -268,7 +269,7 @@ public function cleanupRemovesTemporaryFiles(): void
$this->assertEquals(0, $result);

// Verify temp directory was cleaned up
$tempDir = $GLOBALS['temporary_files_dir'] . '/CQM_VALUESET';
$tempDir = $this->connector->getTempDir() . '/CQM_VALUESET';
$this->assertDirectoryDoesNotExist($tempDir, 'Temp directory should be removed after cleanup');
}

Expand Down
7 changes: 4 additions & 3 deletions tests/Integration/ImportCodesIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
namespace OpenCoreEMR\CLI\ImportCodes\Tests\Integration;

use OpenCoreEMR\CLI\ImportCodes\Command\ImportCodesCommand;
use OpenCoreEMR\CLI\ImportCodes\Config\GlobalsAccessor;
use OpenCoreEMR\CLI\ImportCodes\Service\CodeImporter;
use OpenCoreEMR\CLI\ImportCodes\Service\MetadataDetector;
use OpenCoreEMR\CLI\ImportCodes\Service\OpenEMRConnector;
Expand Down Expand Up @@ -54,7 +55,7 @@ protected function setUp(): void
}

// Try to initialize the connector
$this->connector = new OpenEMRConnector();
$this->connector = new OpenEMRConnector(new GlobalsAccessor());
try {
$this->connector->initialize(self::OPENEMR_PATH, self::SITE);
$this->openemrAvailable = true;
Expand Down Expand Up @@ -92,7 +93,7 @@ public function openEmrConnectorCanGetTempDir(): void
#[Test]
public function codeImporterCanCheckIfVocabularyLoaded(): void
{
$importer = new CodeImporter();
$importer = new CodeImporter(new GlobalsAccessor());

// Should not throw exception when database is available
$result = $importer->isVocabularyLoaded('RXNORM');
Expand All @@ -103,7 +104,7 @@ public function codeImporterCanCheckIfVocabularyLoaded(): void
#[Test]
public function codeImporterCanCheckIfAlreadyLoaded(): void
{
$importer = new CodeImporter();
$importer = new CodeImporter(new GlobalsAccessor());

// Check with fake data - should return false
$result = $importer->isAlreadyLoaded(
Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Command/ImportCodesCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ protected function setUp(): void
$this->detectorMock = $this->createMock(MetadataDetector::class);

$this->command = new ImportCodesCommand(
null,
$this->importerMock,
$this->connectorMock,
$this->detectorMock
Expand Down
Loading