-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add configuration abstraction layer #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add flexible configuration system supporting both database-backed
(OpenEMR globals) and environment variable configurations.
- ConfigAccessorInterface: Common interface for typed config access
- GlobalsAccessor: Now implements ConfigAccessorInterface
- EnvironmentConfigAccessor: Reads config from environment variables
- ConfigFactory: Selects accessor based on ENV_CONFIG env var
- GlobalConfig: Centralized wrapper with typed getters
Set {VENDOR_PREFIX}_{MODULENAME}_ENV_CONFIG=1 to enable env mode.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ModuleAccessGuard to prevent access to module endpoints when: - Module is not registered in OpenEMR - Module is disabled in module management - Module's own 'enabled' setting is off Returns 404 (not 403) to avoid leaking module presence. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add structured exception classes with HTTP status codes:
- {ModuleName}ExceptionInterface: Base interface with getStatusCode()
- {ModuleName}Exception: Abstract base class
- {ModuleName}NotFoundException: 404
- {ModuleName}UnauthorizedException: 401
- {ModuleName}AccessDeniedException: 403
- {ModuleName}ValidationException: 400
- {ModuleName}ConfigurationException: 500
- {ModuleName}ApiException: 502
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add working example implementation demonstrating module patterns: - ExampleController: Shows dispatch pattern, CSRF validation, exceptions - public/index.php: Entry point with guard, error handling - templates/example/list.html.twig: List view with form - templates/example/view.html.twig: Detail view Demonstrates Twig filters (xlt, text, attr) and Bootstrap styling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update Bootstrap to use ConfigAccessorInterface injection: - Accept optional ConfigAccessorInterface in constructor - Use ConfigFactory to determine config source if not provided - Add env config mode handling in global settings section - Show informational message when config managed externally - Add getExampleController() factory method Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add mock implementations for testing configuration: - MockGlobalsAccessor: Mock for database config testing - MockEnvironmentConfigAccessor: Mock for env config testing - Update tests/bootstrap.php to load new mocks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update CLAUDE.md with comprehensive documentation: - Add Configuration Abstraction Layer section - Document environment variable mode usage - Add Module Access Guard section - Update file structure to include new files - Update public entry point pattern with guard and error handling - Update Bootstrap pattern with ConfigAccessorInterface - Add env config mode handling in admin UI example Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove .gitkeep files now that directories contain actual files: - public/.gitkeep (replaced by index.php) - templates/.gitkeep (replaced by example/ templates) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update all copyright headers from 2025 to 2026 across: - LICENSE - DEVELOPMENT.md - PHP source files - openemr.bootstrap.php - version.php Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5003ae2 to
ae0ee87
Compare
kojiromike
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a lot of good things here and this is definitely the right direction. Thanks for putting this together. I left some comments with the theme of making static analysis easier and doing less in the global space.
| // Redirect back to list | ||
| return new Response('', Response::HTTP_FOUND, [ | ||
| 'Location' => $_SERVER['PHP_SELF'] | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per CLAUDE.md guidelines, use RedirectResponse instead of manually setting Location header:
| ]); | |
| return new RedirectResponse($_SERVER['PHP_SELF']); |
This also requires adding the import:
use Symfony\Component\HttpFoundation\RedirectResponse;| namespace {VendorName}\Modules\{ModuleName}\Controller; | ||
|
|
||
| use {VendorName}\Modules\{ModuleName}\Exception\{ModuleName}AccessDeniedException; | ||
| use {VendorName}\Modules\{ModuleName}\Exception\{ModuleName}NotFoundException; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This import is unused - {ModuleName}NotFoundException only appears in a comment (line 90). Consider removing to avoid unused import warnings after template instantiation, or add an actual usage example.
| */ | ||
| private function showView(): Response | ||
| { | ||
| $id = $_GET['id'] ?? null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The controller accesses $_GET and $_POST directly here and in handleCreate(). The CLAUDE.md pattern shows dispatch(string $action, array $params) where params are passed in from the entry point. This would decouple the controller from superglobals and improve testability.
Consider:
public function dispatch(string $action, array $params = []): ResponseThen pass $_REQUEST or $_GET + $_POST from public/index.php.
| private const KEY_MAP = [ | ||
| GlobalConfig::CONFIG_OPTION_ENABLED => '{VENDOR_PREFIX}_{MODULENAME}_ENABLED', | ||
| // Add your config option mappings here | ||
| ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The KEY_MAP only maps CONFIG_OPTION_ENABLED, but GlobalConfig.php comments mention API_KEY and API_SECRET as examples. Consider adding commented placeholder mappings to match:
| ]; | |
| // Add your config option mappings here | |
| // GlobalConfig::CONFIG_OPTION_API_KEY => '{VENDOR_PREFIX}_{MODULENAME}_API_KEY', | |
| // GlobalConfig::CONFIG_OPTION_API_SECRET => '{VENDOR_PREFIX}_{MODULENAME}_API_SECRET', |
| * @license GNU General Public License 3 | ||
| */ | ||
|
|
||
| namespace {VendorName}\Modules\{ModuleName}\Controller; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| namespace {VendorName}\Modules\{ModuleName}\Controller; | |
| namespace OpenCoreEMR\Modules\{ModuleName}\Controller; |
For our purposes this will always be the case. If someone else uses this public repo they can change their vendor name themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like these exception types
| /** | ||
| * Get the HTTP status code for this exception | ||
| */ | ||
| public function getStatusCode(): int; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not actually sure every module exception implies a status code. The requirement that there be one seems to me to be the domain of a subtype of module exception. Anyway, I don't object to this interface existing, but I think the most abstract base exception type for the module shouldn't implement this.
| * Abstraction for configuration access, matching Symfony ParameterBag's typed accessor methods. | ||
| * Allows configuration to be read from either OpenEMR globals (database) or environment variables. | ||
| */ | ||
| interface ConfigAccessorInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getKernel, perhaps...
| </div> | ||
| </div> | ||
|
|
||
| <script src="{{ webroot|attr }}/public/assets/bootstrap/dist/js/bootstrap.bundle.min.js"></script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in with a defer attribute, no?
| </div> | ||
| </div> | ||
|
|
||
| <script src="{{ webroot|attr }}/public/assets/bootstrap/dist/js/bootstrap.bundle.min.js"></script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in head with a defer?
Summary
This branch adds a configuration abstraction layer to the OpenCoreEMR module template, enabling flexible configuration from either database-backed globals or environment variables.
Key additions:
Changes: 30 files, +1,345 / -49 lines
Commits
DRAFT: One pass using LLM's with some manual review.