Skip to content

Move openemr/openemr out of root composer.json into a tools/openemr sub-vendor #20

@kojiromike

Description

@kojiromike

Problem

If a generated module declares openemr/openemr as a require-dev entry in its root composer.json (the current template default), then in any dev environment built with a full composer install:

  1. vendor/openemr/openemr/ lands on disk under the module's vendor tree.
  2. The module's vendor/autoload.php registers a PSR-4 mapping OpenEMR\\ → its vendor's src/, alongside OpenEMR core's identical mapping.
  3. At runtime, OpenEMR\… class lookups can resolve to the module's vendor copy of (e.g.) OpenEMR\Common\Acl\AclMain. Several OpenEMR classes call require_once(__DIR__ . '/../../../library/lists.inc.php');__DIR__ then resolves into the module's vendor tree and the same procedural file is loaded under a different absolute path than OpenEMR core already loaded it.
  4. require_once dedups by string path, not by content, so the procedural function definitions get re-run → fatal Cannot redeclare collect_issue_type_category() (or any other function in library/*.inc.php) on patient demographics and other in-chart pages.

Production deploys are not affected (they use composer install --no-dev and the shadow vendor never exists), so this is purely a local-dev / test trap. But the fatal blocks essentially every chart workflow during development. We hit it in oce-module-sinch-conversations and fixed it in openCoreEMR/oce-module-sinch-conversations#119 (closes openCoreEMR/oce-module-sinch-conversations#118).

Proposal

Adopt the same fix in this template so newly-generated modules don't inherit the trap. The recipe:

  • Remove openemr/openemr from root composer.json (require-dev and repositories).
  • Add tools/openemr/composer.json (sub-composer) requiring openemr/openemr with the existing constraints + repositories. Gitignore its vendor/ and composer.lock.
  • phpstan.neon: add bootstrapFiles: [tools/openemr/vendor/autoload.php] so PHPStan still resolves OpenEMR\… symbols. Also add tools/openemr/vendor (?) to excludePaths.
  • composer phpstan script should self-install the sub-vendor if tools/openemr/vendor/autoload.php is missing — otherwise a fresh checkout that ran only composer install hits a confusing missing-file error.
  • compose.yml (if the template ships one for local dev): bind-mount from ./tools/openemr/vendor/openemr/openemr instead of ./vendor/openemr/openemr.
  • Taskfile.yml: new tools:install task; have openemr:prebuild (or whatever does npm install inside OpenEMR) deps: on it; let setup reach it transitively. Update vendor:clean to also clean tools/openemr/vendor/.
  • CI (.github/workflows/phpstan.yml): install + cache tools/openemr/vendor before running PHPStan. Cache key off composer.json files, since composer.lock is gitignored.
  • Module CLI's path discovery (in bin/install-module.php / AbstractModuleCommand::findOpenEmrPath): also look under __DIR__ . '/../../../../tools/openemr/vendor/openemr/openemr' so host-run module commands still find OpenEMR without --openemr-path.
  • Test bootstrap: replace any reliance on the runtime root vendor providing OpenEMR types with explicit mocks under tests/Mocks/. The sinch-conversations PR added stubs for OEGlobalsBag, GlobalsInitializedEvent, PatientCreatedEvent, PatientUpdatedEvent, MenuEvent, GlobalsService, GlobalSetting — the template likely needs an analogous (probably smaller) starter set.
  • Docs: a short "why is OpenEMR under tools/openemr/?" note in the README (or CLAUDE.md) so a future contributor doesn't move it back.

Reference

The full set of changes for sinch-conversations is in openCoreEMR/oce-module-sinch-conversations#119 — it can be diffed directly to extract the template-applicable pieces. Out of scope for this template issue: rolling the same pattern into other already-existing OCE modules (each gets its own per-module follow-up).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions