Skip to main content
OpenEyes is built on a modular, event-based architecture using the Yii 1.x PHP framework. This page covers the technical architecture and core design patterns.

Technology Stack

Core Framework

OpenEyes is built on Yii Framework 1.1.28, a high-performance PHP framework based on the Model-View-Controller (MVC) pattern.
protected/composer.json
{
  "require": {
    "php": "^7.4 || ^8.0 || ^8.1",
    "yiisoft/yii": "1.1.28"
  }
}

Dependencies

Key dependencies include:
  • PHP 7.4+ with support for PHP 8.0 and 8.1
  • MySQL/MariaDB for data storage
  • PHPUnit 9.6.5 for testing
  • Faker for test data generation
  • PHPStan for static analysis

Architecture Overview

MVC Pattern

OpenEyes follows the classic Model-View-Controller pattern:
protected/
├── models/          # Data models and business logic
├── controllers/     # Request handling and flow control
├── views/          # Presentation layer
├── components/     # Reusable components
└── modules/        # Event type modules

Core Models

The system is built around several key models:

Patient Model

The Patient model is central to the system:
protected/models/Patient.php
class Patient extends BaseActiveRecordVersioned
{
    use HasFactory;

    const CHILD_AGE_LIMIT = 16;
    const PATIENT_SOURCE_OTHER = 0;
    const PATIENT_SOURCE_REFERRAL = 1;
    const PATIENT_SOURCE_SELF_REGISTER = 2;

    // Properties
    // @property int $id
    // @property string $first_name
    // @property string $last_name
    // @property string $dob
    // @property string $gender
    // @property string $hos_num
    // @property string $nhs_num

    // Relations
    // @property Episode[] $episodes
    // @property Address[] $addresses
    // @property Contact $contact
    // @property Gp $gp
    // @property Practice $practice
    // @property Allergy[] $allergies
}

Event Model

Events represent clinical encounters:
protected/models/Event.php
class Event extends BaseActiveRecordVersioned
{
    use HasFactory;

    // Properties
    // @property string $id
    // @property string|int $episode_id
    // @property string|int $user_id
    // @property string|int $event_type_id
    // @property boolean $deleted
    // @property string $event_date

    // Relations
    // @property Episode $episode
    // @property User $user
    // @property EventType $eventType
    // @property EventIssue[] $issues
}

Event-Based Architecture

BaseEventTypeController

All event modules extend BaseEventTypeController, which provides standardized CRUD operations:
protected/controllers/BaseEventTypeController.php
class BaseEventTypeController extends BaseModuleController
{
    const ACTION_TYPE_CREATE = 'Create';
    const ACTION_TYPE_VIEW = 'View';
    const ACTION_TYPE_PRINT = 'Print';
    const ACTION_TYPE_EDIT = 'Edit';
    const ACTION_TYPE_DELETE = 'Delete';

    public $patient;
    public $site;
    public ?Event $event = null;
    public $episode;
    public $editable = true;
    protected $open_elements;
}
The controller is stateful and provides hooks for customization:
  • setElementDefaultOptions() - Set defaults on elements
  • setElementComplexAttributesFromData() - Set complex attributes
  • saveElementComplexAttributesFromData() - Save complex data

Event Type Modules

Each clinical event type is implemented as a module. Example from OphCiExamination:
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']);
        parent::init();
    }
}

Module System

OpenEyes uses a modular architecture where functionality is organized into modules:

Module Structure

protected/modules/OphCiExamination/
├── OphCiExaminationModule.php    # Module definition
├── controllers/
│   └── DefaultController.php     # Main controller
├── models/                       # Domain models
├── views/                        # View templates
├── migrations/                   # Database migrations
├── components/                   # Module components
├── factories/                    # Test factories
└── tests/                        # Module tests

Module Naming Conventions

Modules follow specific prefixes based on type:
  • OphCi - Clinical Investigation (e.g., OphCiExamination)
  • OphDr - Drug/Prescription (e.g., OphDrPrescription)
  • OphTr - Treatment (e.g., OphTrOperationnote)
  • OphCo - Correspondence/Communication (e.g., OphCoCorrespondence)

Database Architecture

Migrations

Database schema changes are managed through migrations:
protected/migrations/
├── m130913_000000_consolidation.php
├── m131009_083758_record_no_allergies.php
└── m131015_125648_proc_aliases.php
Each module can have its own migrations:
protected/modules/OphCiExamination/migrations/

Versioned Models

Core models extend BaseActiveRecordVersioned to track changes:
class Patient extends BaseActiveRecordVersioned
{
    // Automatic versioning of all changes
}

Autoloading

Composer autoloading is configured for modern PHP namespaces:
composer.json
{
  "autoload": {
    "psr-4": {
      "OE\\concerns\\": "protected/concerns",
      "OE\\contracts\\": "protected/contracts",
      "OE\\factories\\": "protected/factories",
      "OE\\listeners\\": "protected/listeners",
      "OE\\seeders\\": "protected/seeders"
    }
  }
}

Component Architecture

Behaviors

Yii behaviors provide reusable functionality:
protected/behaviors/

Widgets

Reusable UI components:
protected/widgets/

Helpers

Utility functions and helper classes:
protected/helpers/

Configuration

Main Configuration

Configuration is loaded through OEConfig:
protected/config/main.php
require_once dirname(__FILE__).'/OEConfig.php';
return OEConfig::getMergedConfig('main');
Local overrides can be placed in:
protected/config/local/

Event System

OpenEyes uses Yii’s event system for extensibility:
  • Event listeners in protected/listeners/
  • Components can raise and listen for events
  • Modules can hook into application-wide events

Services Layer

Business logic is organized into services:
protected/services/

Security

Access Control

Access control is managed through:
  • Role-based access control (RBAC)
  • Controller-level access rules
  • Action-type based permissions

Data Validation

All models implement validation rules:
public function rules()
{
    return [
        ['first_name, last_name', 'required'],
        ['email', 'email'],
    ];
}

Performance Considerations

Caching

Yii provides caching mechanisms:
  • Database query caching
  • Fragment caching
  • Full page caching

Asset Management

Assets are organized per module:
protected/modules/OphCiExamination/assets/

Next Steps

Module Development

Learn how to create custom modules

Testing

Understanding the testing approach

Build docs developers (and LLMs) love