Module Skeleton Package

So I needed to explain to someone how to create a module and I figured I’d just create a skeleton project that people can build off of if they want. Similar to what we did with @sjpadgett’s fax/sms module.

So I put together this sample skeleton project with instructions on how to create a very loosely coupled custom module. Its rudimentary right now but I plan on adding to it over time.

You can install it in your OpenEMR project via packagist by using a
composer require adunsulag/oe-module-custom-skeleton

The link on packagist is here:
https://packagist.org/packages/adunsulag/oe-module-custom-skeleton

The github if you want to fork it and use it for your own custom modules is here: https://github.com/adunsulag/oe-module-custom-skeleton

7 Likes

This is a good tool to use for developers to know. I have used this to help make minimal changes to the codebase.

Hi guys, that is really useful, thank you very much! I was able to install this module and play with it a little bit.

Could you please let me know if there are any descriptions/thoughts on architecture approach for loosely coupled modules and how they should be integrated?

I see there are few situations:

  1. Module don’t use any third-party components and extends functionality of the system using existing functionality and PHP.
  2. Module use third-party components (I assume Telehealth module does it) and need to send data to those components and get data from them. In this case is there an ability to store those third party components on the server there OpenEMR installed or it’s better to keep them outside on a separate server? Or let’s say this can be a decision of each installation?

I ask because work on AI/ML module for some illnesses prediction and it would be easy if I can keep using python (as I see dockerized developer version already has python installed)

We haven’t given much thought to installing any non-php service via modules as our module architecture now is all delivered via composer which will not allow you to tell the target OS to install other software. In my view the best approach is to communicate to 3rd parties that are not written in PHP is via a REST api. Its scalable, allows you to move things around, and follows the micro-services architecture pattern. Where you store this 3rd party service is up to you. You can store it on the same service and communicate via sockets, move it to some other service, or whatever.

If you will be installing a 3rd party service alongside OpenEMR on the same target OS, you can make calls out to the system via any kind of php exec call or via sockets, or some other kind of IPC mechanism in php. This is the most secure communication and also the fastest but you’ll have scaling issues.

Another option that will have better scaling characteristics would be to leverage the MySQL database or REDIS as your communication layer. It all comes down to your comfort level with the various technologies and your functional requirements for reliability, availability, and speed.

I want to mention that there’s nothing really stopping you as the module writer from making calls out to python but we currently have no mechanism for you to install python via a module if its not already available in the target OS. I believe most linux installs have python installed but I can’t attest to windows or mac environments if that will be the case.

Now that said if you are trying to deploy your module to the widest available audience there’s nothing preventing you from going off a static linking approach and bundling up whatever python runtimes and libraries you need to execute inside your module that will get downloaded from your git repository via composer.

One thing to bear in mind if you have to follow any privacy laws such as HIPAA or GPDR is the passing around of PHI data between services. Whatever you develop as a 3rd party will need to protect the PHI data as well as provide an audit log of who viewed/modified data as well as when it was viewed/modified. This is handled at the core SQL level inside of OpenEMR and if you operate outside that, you’ll need to make sure you are handling all the compliance aspects yourself.

1 Like

Challenge for new developer is there are multiple approaches listed / found if they just type “openemr modules”. Top hits are :

  1. ZH version of Zend modules
  2. @sjpadgett’s Fax module
  3. @htuck’s Patient Privacy module page.
  4. @juggernautsei/LifeMesh’s Telehealth module page.
  5. Several references in various posts to any and every function as a module - e.g. Scheduling, Inpatient, Inventory, Rx, FHIR - refer to “Globals”…

Not sure if there is way to lead new users along a decision tree but at least some index page may help. And of course each of the pages above should let viewer know about that page.

2 Likes

Hi @mdsupport
Just to be clear, my contribution in your list was strictly to provide a page where devs can post summaries of their modules for potential users to read about and contact them if they want to use the modules. The Patient Privacy module is all @ken 's work; I only massaged his quick summary and wikified it.

Gotta say, one centralized repo of descriptions of the modules available for OpenEMR may be one of the better ideas, but nobody heard about it so it isn’t going anywhere.
Best- HT

1 Like

I think your index page would be a great central location we can push people too. So far the modules I’ve created (outside of the skeleton module) have been for private companies which they have yet to release to the broader community, if they start pushing them to the community I’ll make sure to add them to that wiki page.

Hi @adunsulag -
Thanks for the push, as it were :slight_smile: If you have any questions about getting your stuff on the wiki, feel free to ask.
But of course, maybe being listed all together here is as centralized as they’re going to get!

  • HT

I thought that was what this page was for

https://www.open-emr.org/wiki/index.php/OpenEMR_Modules

Yup, that’s what it’s for.
But my earlier remark came from my impression that hardly anybody seems to know the wiki exists so that wiki page goes unnoticed. Whereas this forum page has a lot better chance of appearing in searches, so maybe it’s where the list of modules will get seen.

  • HT

I posted a repo to add alike in the module section to the modules page that hardly gets seen as @htuck pointed out. It is hardly seen because it is brand new relationship to the rest of the wiki.

You can have a solution for world hunger. But if hungry people are not searching for it or it is not within their means/skill set to use that solution, they will continue to go hungry.

Perhaps put something in standard code that makes ‘What’s New’ page pop up periodically when an administrator logs in.

1 Like

I’ve been considering modularizing some of my custom forms (after all, I can always use an extra project or two lying around) but my pet project depends on other additions to the OEMR codebase. For example, I’ve added a few special-purpose data types to the library/options.inc.php file. Is there a way to use the module installation process to add code to this file (or simply replace the file with a newer enhanced file)? Likewise, if I were to reduce some of my custom specialty visit forms to modules can I (or should I - and how?) have the module installation process place the code in the interface/forms directory or leave it in the interface/modules/custom_modules directory? Sorry about these rather amateurish questions but I can’t see how to accomplish the above and need a basic direction.

1 Like

@Mouse55 So the way I would recommend going about doing this is to add new events in the options.inc page to allow module writers to extend the data types that are available. Same thing with the forms. That way you don’t have to muck with the file system and everything would live inside your module.

If you want to take a stab at adding the new events and the dispatching of the events in a PR for both the options and forms code that would make it possible to support your changes without having to replace files in core.

1 Like

I’m hoping someone can help me here. As noted above, I’ve been trying to convert some of my custom encounter forms (i.e., those appropriate to visits for my specialty - ENT) to modules. This is primarily for what I hope will be ease of installation but also for sharing (I’m perfectly willing to share these on packagist but not until I’ve got all the kinks worked out). With this in mind, I have the following problems.
The use of the modules works perfectly (I can install it on my modified machine and everything runs correctly) however at least some of the OEMR code base needs to be changed in order for them to install correctly and be used effectively. This isn’t a problem for my own personal installation as I can make whatever changes I wish but 1), it does require that any user who wants to utilize these modules will have to make similar edits to their own base code prior to installation (which in turn requires explanation/documentation/proficiency at doing so), and 2), it would seem to violate the underlying principle that modules should be entirely self-standing and not place any demands on the existing code. Specifically, forms.php, load_form.php and view_form.php (all of which reside in the interface/patient_file/encounter folder) as well as interface/modules/zend_modules/module/Installer/src/Installer/Model/InstModuleTable.php (that path is way too long) all need to be edited. I presume that the first three are doable, but don’t know how. The final one would appear to pose a problem as it would require that file to be modified prior to actually installing the module, but that wouldn’t seem to be able to be done by the new module until it was actually installed. Rather, it would have to be done at the level of the original codebase.

My plan, if possible, would be to simply write alternate versions of the first three above-mentioned files and stash them somewhere within the module file structure. Unfortunately, I haven’t been able to figure out how to do this and reference them appropriately. Do current events exist into which I can tap or do I have to create my own events? I’ve searched through the events in src/Events but nothing seems right. I can probably figure out how to do so (eventually) but I’m hoping that someone can point me in the correct direction before I lose my focus.

A few other things. I would consider myself to be an amateur developer. What that really means is that I’m a physician with no formal computer training who has managed to set up and run (with multiple customizations) my ENT office entirely on OpenEMR for the last 8 years. The computer part is more of a hobby for me but darn, my stuff actually works! If I can get this ready for primetime I’d be happy to share it with the community. Other ENTs could find it useful but, more importantly, it could be adapted for other purposes entirely.

Thank you in advance for whatever you can offer.

@Mouse55
Very happy to see your progress and as far as I’m concerned, M.Ds make for some of the best developers in the health care workspace. So keep at it.

Try to follow @adunsulag advise so we can see what types of changes you require in core. Forms can be tricking because of pathing and security however Brady recently developed a mechanism for using encounter forms in the portal that may help in your work.

As Stephen has said you may be able to leverage events which we are always happy to see included throughout core.

Are you able to show what changes you needed to make to this file (as well as the above 3 files)? If you can show what you’ve changed then we can provide guidance on possible events we can add to the system to accommodate the changes you are needing.

A short bit of history first. Encounter-based forms are indeed tricky. For better or worse (this could probably be changed) I have elected to “tag” such forms by appending an “_enc” to the end of the module name. My amended code looks for this tag and then handles the installation of the module differently. Chiefly, in order for the form to show up in the encounter menu it appears to need to be entered into the registry table as well as the modules table. Maybe there’s a better way to do this but, if so, I haven’t found it. The four files follow - the amended areas have been tagged with my initials (DRH).
InstModuleTable.php (40.1 KB)
forms.php (36.5 KB)
load_form.php (1.4 KB)
view_form.php (1.4 KB)

So I think what would work out best here is to add events to override any currently added form in the system with the form in the module.

Something along the lines of

$event = new CustomFormEvent($_GET['formname']); // CustomFormEvent would need to be added to the core system
$event->setFormPathLocation($incdir/forms/ . $_GET["formname"]);
$event =$GLOBALS["kernel"]->getEventDispatcher()->dispatch(CustomFormEvent::FILTER_CUSTOM_FORM_LOCATION, $event);
// the getFormPathLocation or the setFormPathLocation should have security checks to make sure a file cannot be included outside of the OpenEMR modules or regular forms directory for safety.  Don't want anyone accidently including data inside the patient documents directory

// for load_forms.php
include_once $event->getFormPathLocation() . "/new.php";

// for forms.php
include_once $event->getFormPathLocation() . "/report.php";

// view_form.php
include_once $event->getFormPathLocation() . "/view.php";

In your module you would just listen to the CustomFormEvent::FILTER_CUSTOM_FORM_LOCATION and then return the location for your module.

That solves the case for forms.php, load_form.php, and view_form.php.

To deal with the sql piece that you are adding in the InstModuleTable.php I would add into your module’s table.sql file the following

-- You would add this for each encounter form you are adding.  I'm using the example of phq9 here as an example
-- Note the #IfNotRow syntax will only add the registry item if it does not currently exist with a form name of 'phq9'
#IfNotRow registry name phq9
INSERT INTO registry SET name="phq9", state=0, directory="my_custom_modules", sql_run=0, unpackaged=1, category="Clinical",patient_encounter=1, therapy_group_encounter=0, aco_spec="encounters|notes", date=NOW();
#EndIf

We do need to add to our module installer another file to execute sql to uninstall the form as we don’t currently have the ability to execute any code in the module when a module is removed.

I probably won’t be able to get to this until next week or the week after if I were to code it up. However, I’m happy to review it and offer suggestions if you want to make a pull request from your github repository. If you write up the CustomFormEvent and stick it in the src/Events/Core/ folder then I can review this for you. You’ll need to add the dispatch of the events to the three files (forms.php, load_form.php, view_form.php) but hopefully that won’t be too tough seeing the changes you’ve already made.

Hope that helps. If you need help knowing how to make a pull request, you can see our contributing guide here https://github.com/openemr/openemr/blob/master/CONTRIBUTING.md (Steps 1-9 at the top, you can skip the local docker piece if you’ve already got OpenEMR installed a different way).

1 Like

Thank you so much. I’ll work on it from here but I have multiple other sticks in the fire, professionally and otherwise. It’ll probably take me longer than a week but I’ll post again when done and/or if I need more help and/or direction.

1 Like