Skip to main content
OpenEyes modules encapsulate functionality for different event types and features. This guide covers how to create and structure custom modules.

Module Types

Event Type Modules

Event type modules represent clinical encounters. They follow naming conventions based on the type of event:
  • OphCi - Clinical Investigation
    • OphCiExamination - Clinical examination
    • OphCiPhasing - Phasing tests
    • OphCiDidNotAttend - DNA tracking
  • OphDr - Drug/Prescription related
    • OphDrPrescription - Prescriptions
    • OphDrPGDPSD - PGD/PSD protocols
  • OphTr - Treatment events
    • OphTrOperationnote - Operation notes
  • OphCo - Correspondence/Communication
    • OphCoCorrespondence - Letters
    • OphCoMessaging - Messages
    • OphCoCvi - Certificate of Vision Impairment

Administrative Modules

  • Admin - System administration
  • Api - API functionality
  • Genetics - Genetics features

Module Structure

A typical event type module has this structure:
protected/modules/OphCiExamination/
├── OphCiExaminationModule.php    # Module definition
├── controllers/
│   └── DefaultController.php     # Main event controller
├── models/
│   ├── Element_OphCiExamination_*.php  # Element models
│   └── *.php                     # Supporting models
├── views/
│   └── default/
│       ├── create.php
│       ├── view.php
│       ├── update.php
│       └── form_*.php            # Element forms
├── migrations/
│   └── m*.php                    # Database migrations
├── components/
│   └── OphCiExamination_API.php  # API component
├── assets/
│   ├── js/
│   └── css/
├── widgets/                      # Custom widgets
├── factories/                    # Test factories
│   └── models/
└── tests/                        # Module tests
    ├── unit/
    └── feature/

Creating a Module

Step 1: Module Definition

Create the module class extending BaseEventTypeModule:
protected/modules/OphCiExample/OphCiExampleModule.php
<?php
namespace OEModule\OphCiExample;

class OphCiExampleModule extends \BaseEventTypeModule
{
    public $controllerNamespace = '\OEModule\OphCiExample\controllers';

    public function init()
    {
        // Import other module models if needed
        $this->setImport(array(
            'application.modules.OphDrPrescription.models.*',
        ));

        // Set sub-modules if needed
        $this->setModules(['ExampleAdmin']);

        // Import module-level components
        $this->setImport(array(
            'OphCiExample.components.*',
            'OphCiExample.seeders.*',
        ));

        parent::init();
    }
}

Step 2: Controller

Create the default controller extending BaseEventTypeController:
protected/modules/OphCiExample/controllers/DefaultController.php
<?php
namespace OEModule\OphCiExample\controllers;

class DefaultController extends \BaseEventTypeController
{
    // Define custom action types
    protected static $action_types = array(
        'customAction' => self::ACTION_TYPE_FORM,
    );

    /**
     * Set to true if the index search bar should appear
     */
    protected $show_index_search = true;

    /**
     * Set to true if element sidebar should appear
     */
    protected $show_element_sidebar = true;

    /**
     * Set default options for elements
     */
    protected function setElementDefaultOptions($element, $action)
    {
        parent::setElementDefaultOptions($element, $action);

        if ($element instanceof models\Element_OphCiExample_Data) {
            $element->exam_date = date('Y-m-d');
        }
    }

    /**
     * Custom action example
     */
    public function actionCustomAction()
    {
        // Custom logic
        $this->renderJSON(['success' => true]);
    }
}

Step 3: Element Models

Create element models for the event:
protected/modules/OphCiExample/models/Element_OphCiExample_Data.php
<?php
namespace OEModule\OphCiExample\models;

use OE\factories\models\traits\HasFactory;

class Element_OphCiExample_Data extends \BaseEventTypeElement
{
    use HasFactory;

    /**
     * Returns the static model
     */
    public static function model($className = __CLASS__)
    {
        return parent::model($className);
    }

    /**
     * Returns the database table name
     */
    public function tableName()
    {
        return 'et_ophciexample_data';
    }

    /**
     * Validation rules
     */
    public function rules()
    {
        return array(
            array('event_id, exam_date', 'required'),
            array('exam_date', 'date', 'format' => 'yyyy-MM-dd'),
            array('notes', 'safe'),
        );
    }

    /**
     * Attribute labels
     */
    public function attributeLabels()
    {
        return array(
            'exam_date' => 'Examination Date',
            'notes' => 'Clinical Notes',
        );
    }

    /**
     * Relations
     */
    public function relations()
    {
        return array(
            'event' => array(self::BELONGS_TO, '\Event', 'event_id'),
            'user' => array(self::BELONGS_TO, '\User', 'created_user_id'),
        );
    }
}

Step 4: Views

Create view templates:
protected/modules/OphCiExample/views/default/form_Element_OphCiExample_Data.php
<?php
/**
 * @var Element_OphCiExample_Data $element
 * @var BaseEventTypeController $this
 */
?>

<div class="element-fields full-width">
    <?php echo $form->datePicker(
        $element,
        'exam_date',
        array(),
        array(),
        array('maxDate' => 'today')
    ) ?>

    <?php echo $form->textArea(
        $element,
        'notes',
        array('rows' => 4, 'cols' => 80),
        false,
        array('class' => 'autosize')
    ) ?>
</div>

Step 5: Database Migration

Create the database structure:
protected/modules/OphCiExample/migrations/m240101_120000_initial_tables.php
<?php

class m240101_120000_initial_tables extends OEMigration
{
    public function up()
    {
        // Create event type
        $this->createOETable(
            'event_type',
            array(
                'id' => 'pk',
                'name' => 'varchar(40) NOT NULL',
                'class_name' => 'varchar(64) NOT NULL',
            ),
            true
        );

        $this->insert('event_type', array(
            'name' => 'Examination',
            'class_name' => 'OphCiExample',
        ));

        // Create element table
        $this->createOETable(
            'et_ophciexample_data',
            array(
                'id' => 'pk',
                'event_id' => 'int(10) unsigned NOT NULL',
                'exam_date' => 'date NOT NULL',
                'notes' => 'text',
            ),
            true
        );

        $this->addForeignKey(
            'et_ophciexample_data_ev_fk',
            'et_ophciexample_data',
            'event_id',
            'event',
            'id'
        );
    }

    public function down()
    {
        $this->dropOETable('et_ophciexample_data', true);
        $this->delete('event_type', 'class_name = ?', array('OphCiExample'));
    }
}

Module Elements

Element Basics

Elements are the building blocks of events. Each element:
  • Extends BaseEventTypeElement
  • Represents a section of the event form
  • Has its own database table
  • Can be required or optional

Multiple Elements

Events can have multiple elements:
class Element_OphCiExample_History extends BaseEventTypeElement { }
class Element_OphCiExample_Examination extends BaseEventTypeElement { }
class Element_OphCiExample_Diagnosis extends BaseEventTypeElement { }

Element Dependencies

Elements can depend on other elements:
public function getDependencies()
{
    return array(
        'Element_OphCiExample_History',
    );
}

Module API Component

Create an API component for module-specific functionality:
protected/modules/OphCiExample/components/OphCiExample_API.php
<?php

class OphCiExample_API extends \BaseAPI
{
    /**
     * Get latest examination for patient
     */
    public function getLatestExamination($patient_id)
    {
        $event = $this->getMostRecentEventByType($patient_id, 'OphCiExample');

        if ($event) {
            return \Element_OphCiExample_Data::model()
                ->find('event_id = ?', array($event->id));
        }

        return null;
    }
}

Module Configuration

Modules can have their own configuration:
protected/modules/OphCiExample/config/common.php
<?php

return array(
    'params' => array(
        'ophciexample_default_duration' => 30,
        'ophciexample_enable_feature' => true,
    ),
);

Testing Modules

Create factories for your models:
protected/modules/OphCiExample/factories/models/Element_OphCiExample_DataFactory.php
<?php
namespace OEModule\OphCiExample\factories\models;

use OE\factories\ModelFactory;
use OEModule\OphCiExample\models\Element_OphCiExample_Data;

class Element_OphCiExample_DataFactory extends ModelFactory
{
    public function definition(): array
    {
        return [
            'exam_date' => $this->faker->date(),
            'notes' => $this->faker->sentence(),
            'event_id' => \Event::factory(),
        ];
    }
}
See Testing for more details on writing tests.

Real-World Example

The OphCiExamination module is a comprehensive example:
protected/modules/OphCiExamination/OphCiExaminationModule.php
namespace OEModule\OphCiExamination;

class OphCiExaminationModule extends \BaseEventTypeModule
{
    public $controllerNamespace = '\OEModule\OphCiExamination\controllers';

    public function init()
    {
        $this->setImport(array(
            'application.modules.OphDrPrescription.models.*',
        ));
        $this->setModules(['ExaminationAdmin']);
        $this->setImport(array(
            'OphCiExamination.seeders.*',
        ));
        parent::init();
    }
}
Explore the OphCiExamination module source code for a complete implementation example.

Module Registration

Modules are automatically discovered if placed in protected/modules/. They can also be registered in the application configuration.

Best Practices

  1. Follow naming conventions - Use appropriate prefixes (OphCi, OphDr, etc.)
  2. Use namespaces - Organize code with PHP namespaces
  3. Create factories - Support testing with model factories
  4. Write migrations - Always version database changes
  5. Document elements - Add PHPDoc comments to models
  6. Use the API component - Expose module functionality through an API class
  7. Test thoroughly - Write unit and feature tests

Next Steps

Architecture

Understand the technical architecture

Testing

Learn about testing your modules

Build docs developers (and LLMs) love