Skip to content

Commit aa0ea9f

Browse files
kojiromikeclaude
andcommitted
refactor: add GlobalsAccessor abstraction for OpenEMR globals access
- Add ConfigAccessorInterface with typed getters (get, getString, getBoolean, getInt, has) - Implement GlobalsAccessor to wrap $GLOBALS access - Update CodeImporter to use injected ConfigAccessorInterface - Update OpenEMRConnector to use injected ConfigAccessorInterface - Update ImportCodesCommand to wire up GlobalsAccessor by default - Add comprehensive unit tests for GlobalsAccessor (21 tests) - Update existing tests for new constructor signatures Fixes #19 Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 69b514f commit aa0ea9f

File tree

9 files changed

+353
-9
lines changed

9 files changed

+353
-9
lines changed

src/Command/ImportCodesCommand.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace OpenCoreEMR\CLI\ImportCodes\Command;
1515

16+
use OpenCoreEMR\CLI\ImportCodes\Config\ConfigAccessorInterface;
17+
use OpenCoreEMR\CLI\ImportCodes\Config\GlobalsAccessor;
1618
use OpenCoreEMR\CLI\ImportCodes\Service\CodeImporter;
1719
use OpenCoreEMR\CLI\ImportCodes\Service\OpenEMRConnector;
1820
use OpenCoreEMR\CLI\ImportCodes\Service\MetadataDetector;
@@ -31,13 +33,20 @@ class ImportCodesCommand extends Command
3133
private ?OutputInterface $output = null;
3234

3335
public function __construct(
34-
private readonly CodeImporter $importer = new CodeImporter(),
35-
private readonly OpenEMRConnector $connector = new OpenEMRConnector(),
36+
?ConfigAccessorInterface $config = null,
37+
?CodeImporter $importer = null,
38+
?OpenEMRConnector $connector = null,
3639
private readonly MetadataDetector $detector = new MetadataDetector()
3740
) {
41+
$config ??= new GlobalsAccessor();
42+
$this->importer = $importer ?? new CodeImporter($config);
43+
$this->connector = $connector ?? new OpenEMRConnector($config);
3844
parent::__construct();
3945
}
4046

47+
private readonly CodeImporter $importer;
48+
private readonly OpenEMRConnector $connector;
49+
4150
protected function configure()
4251
{
4352
$supportedTypes = implode(', ', self::SUPPORTED_TYPES);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/**
4+
* ConfigAccessorInterface.php
5+
* Interface for accessing configuration values with type safety
6+
*
7+
* @package OpenCoreEMR\CLI\ImportCodes
8+
* @link https://opencoreemr.com
9+
* @author Michael A. Smith <[email protected]>
10+
* @copyright Copyright (c) 2026 OpenCoreEMR Inc
11+
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
12+
*/
13+
14+
namespace OpenCoreEMR\CLI\ImportCodes\Config;
15+
16+
interface ConfigAccessorInterface
17+
{
18+
/**
19+
* Get a configuration value
20+
*/
21+
public function get(string $key, mixed $default = null): mixed;
22+
23+
/**
24+
* Check if a configuration key exists
25+
*/
26+
public function has(string $key): bool;
27+
28+
/**
29+
* Get a configuration value as a string
30+
*/
31+
public function getString(string $key, string $default = ''): string;
32+
33+
/**
34+
* Get a configuration value as a boolean
35+
*/
36+
public function getBoolean(string $key, bool $default = false): bool;
37+
38+
/**
39+
* Get a configuration value as an integer
40+
*/
41+
public function getInt(string $key, int $default = 0): int;
42+
}

src/Config/GlobalsAccessor.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/**
4+
* GlobalsAccessor.php
5+
* Centralized accessor for OpenEMR $GLOBALS with type-safe getters
6+
*
7+
* @package OpenCoreEMR\CLI\ImportCodes
8+
* @link https://opencoreemr.com
9+
* @author Michael A. Smith <[email protected]>
10+
* @copyright Copyright (c) 2026 OpenCoreEMR Inc
11+
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
12+
*/
13+
14+
namespace OpenCoreEMR\CLI\ImportCodes\Config;
15+
16+
class GlobalsAccessor implements ConfigAccessorInterface
17+
{
18+
/**
19+
* @inheritDoc
20+
*/
21+
public function get(string $key, mixed $default = null): mixed
22+
{
23+
return $GLOBALS[$key] ?? $default;
24+
}
25+
26+
/**
27+
* @inheritDoc
28+
*/
29+
public function has(string $key): bool
30+
{
31+
return isset($GLOBALS[$key]);
32+
}
33+
34+
/**
35+
* @inheritDoc
36+
*/
37+
public function getString(string $key, string $default = ''): string
38+
{
39+
$value = $this->get($key, $default);
40+
if (is_string($value)) {
41+
return $value;
42+
}
43+
return is_scalar($value) || $value === null ? (string) $value : $default;
44+
}
45+
46+
/**
47+
* @inheritDoc
48+
*/
49+
public function getBoolean(string $key, bool $default = false): bool
50+
{
51+
$value = $this->get($key, $default);
52+
if (is_bool($value)) {
53+
return $value;
54+
}
55+
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
56+
}
57+
58+
/**
59+
* @inheritDoc
60+
*/
61+
public function getInt(string $key, int $default = 0): int
62+
{
63+
$value = $this->get($key, $default);
64+
if (is_int($value)) {
65+
return $value;
66+
}
67+
return is_numeric($value) ? (int) $value : $default;
68+
}
69+
70+
/**
71+
* Get all globals (use sparingly)
72+
*
73+
* @return array<string, mixed>
74+
*/
75+
public function all(): array
76+
{
77+
return $GLOBALS;
78+
}
79+
}

src/Service/CodeImporter.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace OpenCoreEMR\CLI\ImportCodes\Service;
1515

16+
use OpenCoreEMR\CLI\ImportCodes\Config\ConfigAccessorInterface;
1617
use OpenCoreEMR\CLI\ImportCodes\Exception\CodeImportException;
1718
use OpenCoreEMR\CLI\ImportCodes\Exception\FileSystemException;
1819
use OpenCoreEMR\CLI\ImportCodes\Exception\DatabaseLockException;
@@ -24,6 +25,11 @@ class CodeImporter
2425
private int $lockRetryDelaySeconds = 30;
2526
private bool $waitedForLock = false;
2627

28+
public function __construct(
29+
private readonly ConfigAccessorInterface $config
30+
) {
31+
}
32+
2733
/**
2834
* Set custom temporary directory
2935
*/
@@ -280,11 +286,12 @@ public function cleanup(string $type): void
280286
*/
281287
public function getStagingFiles(string $type): array
282288
{
283-
if (!isset($GLOBALS['temporary_files_dir']) || !is_string($GLOBALS['temporary_files_dir'])) {
289+
$tempDir = $this->config->getString('temporary_files_dir');
290+
if ($tempDir === '') {
284291
return [];
285292
}
286293

287-
$stagingDir = $GLOBALS['temporary_files_dir'] . '/' . $type;
294+
$stagingDir = $tempDir . '/' . $type;
288295
if (!is_dir($stagingDir)) {
289296
return [];
290297
}

src/Service/OpenEMRConnector.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace OpenCoreEMR\CLI\ImportCodes\Service;
1515

16+
use OpenCoreEMR\CLI\ImportCodes\Config\ConfigAccessorInterface;
1617
use OpenCoreEMR\CLI\ImportCodes\Exception\OpenEMRConnectorException;
1718

1819
class OpenEMRConnector
@@ -21,6 +22,11 @@ class OpenEMRConnector
2122
private string $site;
2223
private bool $initialized = false;
2324

25+
public function __construct(
26+
private readonly ConfigAccessorInterface $config
27+
) {
28+
}
29+
2430
/**
2531
* Initialize connection to OpenEMR
2632
*/
@@ -52,15 +58,16 @@ public function initialize(string $openemrPath, string $site = 'default'): void
5258
require_once $standardTablesPath;
5359

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

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

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

114-
return $GLOBALS['temporary_files_dir'] ?? sys_get_temp_dir();
121+
$tempDir = $this->config->getString('temporary_files_dir');
122+
return $tempDir !== '' ? $tempDir : sys_get_temp_dir();
115123
}
116124

117125
/**

tests/Unit/Command/ImportCodesCommandTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ protected function setUp(): void
3939
$this->detectorMock = $this->createMock(MetadataDetector::class);
4040

4141
$this->command = new ImportCodesCommand(
42+
null,
4243
$this->importerMock,
4344
$this->connectorMock,
4445
$this->detectorMock

0 commit comments

Comments
 (0)