Making Modules
From MirrorMed
Contents |
MVC
MirrorMed uses a Model - View - Controller architecture. The model is what owns the database and manages the records there. As much functionality as possible should be pushed to the ordo.
Creating a module
The newest version of the code has the calendar as a module. That is a good place to study how to create a module. Modules replicate the basic MVC architecture of the system. So the standard layout is
/local/controllers/ /local/templates/template_dirs/ /local/ordo/ /local/datasources/ /local/lib/ /local/includes/
The layout for a module is identical, just underneath the modules directory.
/modules/yoursexymodule/local/controllers/ /modules/yoursexymodule/local/templates/template_dirs/ /modules/yoursexymodule/local/ordo/ /modules/yoursexymodule/local/datasources/ /modules/yoursexymodule/local/lib/ /modules/yoursexymodule/local/includes/
To get your module working you need to follow several steps.
- Make a module directory for yourself
cd /modules mkdir yoursexymodule cd yoursexymodule mkdir local cd local mkdir controllers cd controllers
be careful to name these correctly. For instance using "controller" rather than "controllers" will cause silent failure.
- Create a controller stub by copying this code to /module/yoursexymodule/local/controllers/C_YourSexyModule.class.php
<?php
// other code that needs to be "included" or "required" goes here via calls to the loader
// something like...
// $loader->requireOnce('includes/SomeClass.class.php');
class C_YourSexyModule extends Controller {
function C_YourSexyModule() {
$this->Controller();
//usually thats all there is...
}
function actionDefault() {
return "hello world from your Sexy Module!!!";
}
function actionView_view() {
return "this hello world requires permissions";
}
}
?>
- Visit your module at http://yourhost/mirrormed/index.php/main/YourSexyModule/
You should get an error that looks like this...
Not found YourSexyModule action does not exist
This has happened because the controller logic is attempting to load "YourSexyModule" as an action against the main controller. To fix this..
- Add your module name to the /local/config.defaults.php file
- Visit your module at https://yourhost/mirrormed/index.php/main/YourSexyModule/
You should see the text
hello world from your Sexy Module!!!
appear beneath the MirrorMed menu
- Visit the "view" action at https://yourhost/mirrormed/index.php/main/YourSexyModule/view
You should get an error that looks like this...
You do not have the appropriate permissions for the requested resource. Error: Action "action->view" Role "users->ausername" Resource "resources->yoursexymodule"
- You should see a PHPGACL error fix it by following the instructions under Fixing PHPGACL Errors
You should see
this hello world requires permissions
beneath the MirrorMed menu now.
Create an Ordo
First, use phpmyadmin to create a table with the right values. For instance when making the messaging system I made a table called messages, and the table was of the form...
CREATE TABLE `messages` ( `message_id` int(11) NOT NULL default '0', `thread_id` int(11) NOT NULL default '0', `created` timestamp NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, `is_todo` int(11) NOT NULL default '0', `is_done` int(11) NOT NULL default '0', `content` text NOT NULL, PRIMARY KEY (`message_id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Note that the id field of the table must be set to the PRIMARY_KEY. You will get an error message like this...
Notice: Table: messages has no primary key specified and cannot be updated in /var/www/html/php4/ydp_trunk/cellini/ordo/ORDataObject.class.php on line 158
if you ignore this. Also note that the first field is called "message_id".. then use gen.php in cellini/celni to make a new ordo class.
php celini/ordo/gen.php messages > local/ordo/Messages.class.php
This will auto generate the following class from the messages table.
<?php
/**
* Object Relational Persistence Mapping Class for table: messages
*
* @package com.synseer.mirrormed
* @author Fred Trotter<fred.trotter@gmail.com>
*/
/**#@+
* Required Libs
*/
require_once CELINI_ROOT.'/ordo/ORDataObject.class.php';
/**#@-*/
/**
* Object Relational Persistence Mapping Class for table: messages
*
* @package com.synseer.mirrormed
*/
class Messages extends ORDataObject {
/**#@+
* Fields of table: messages mapped to class members
*/
var $id = ;
var $thread_id = ;
var $created = ;
var $is_todo = ;
var $is_done = ;
var $content = ;
/**#@-*/
/**
* Setup some basic attributes
* Shouldn't be called directly by the user, user the factory method on ORDataObject
*/
function Messages($db = null) {
parent::ORDataObject($db);
$this->_table = 'messages';
$this->_sequence_name = 'sequences';
}
/**
* Called by factory with passed in parameters, you can specify the primary_key of Messages with this
*/
function setup($id = 0) {
if ($id > 0) {
$this->set('id',$id);
$this->populate();
}
}
/**
* Populate the class from the db
*/
function populate() {
parent::populate('message_id');
}
/**#@+
* Getters and Setters for Table: messages
*/
/**
* Getter for Primary Key: message_id
*/
function get_message_id() {
return $this->id;
}
/**
* Setter for Primary Key: message_id
*/
function set_message_id($id) {
$this->id = $id;
}
/**#@-*/
}
?>
Note that the first variable is not message_id, but instead id. Celini automagically handles the transfer between these two values. So you can think of "message_id" as just "id" and celini will do the translation for you.
I will add specific functions like "send" which will correctly use this table, in combination with other ordos to send a message in the messaging system. Take a look at the other files in the mirrormed/local/ordo directory to see what already exists and to get ideas about what you might want to do.
Create a Controller/template
This is what a controller looks like...
<?php
require_once CELINI_ROOT."/controllers/Controller.class.php";
class C_Messages extends Controller {
function C_Messages ($template_mod = "general") {
parent::Controller();
$this->template_mod = $template_mod;
}
function actionDefault_view() {
$this->assign('message','Clinical Messaging System in not yet implemented');
return $this->view->render('default.html');
}
function actionInbox_view() {
$this->assign('message','Clinical Messaging System in not yet implemented');
return $this->view->render('default.html');
function actionSent_view() {
$this->assign('message','Clinical Messaging System in not yet implemented');
return $this->view->render('default.html');
}
function actionSearch_view() {
$this->assign('message','Clinical Messaging System in not yet implemented');
return $this->view->render('default.html');
}
function actionNew_add() {
$this->assign('message','Clinical Messaging System in not yet implemented');
return $this->view->render('default.html');
}
}
?>
Controller functions are special beasts. functions with the format *_action_* are things that can be called using the directory URL method. So for the above controller the url
http://www.example.com/mirrormed/index.php/main/messages/default/
would call
function actionDefault_view()
http://www.example.com/mirrormed/index.php/main/messages/sent/
would call
function actionSent_view()
etc. etc....
The so the first term of the *_action_* format is the name that goes in the URL. The second name is the ACL permission for the function. So there are two permissions on the above system, "view" and "add", if you had "view" permissions then you could call the following functions...
function actionDefault_view() {
function actionInbox_view() {
function actionSent_view() {
function actionSearch_view() {
but unless you had the "add" permission you could not do
function actionNew_add() {
This documentation is now current for the new syntax. In case you see the old syntax it looks like
function default_action_view()
which is equivalent to..
function actionDefault_view()
in the new syntax
POST and GET within MirrorMed
There are three ways to do GETs and POSTS in MirrorMed.
The $_GET and $_POST from php work fine. php predefined variables is where they are documented.
Second is the POST and GET objects. These are objects that clean up the interface to $_POST and $_GET. They are part of the default controller (defined in celini/controllers/Controller.class.php) and they are created by the filteredPost and filteredGet functions on the main celini class (celni/includes/Celini.class.php). These functions mask the functions found in clini/includes/clniFilter.class.php. The functions are
exists - tests if a value has been set or not
get - Getting a value with basic security, htmlentities and magic_quotes
getRaw - This will return a raw value, while filtering away any PHP do-gooding such as magic_quotes_gpc.
getTyped - pass in the $key and a $type and the resulting value will be forced to that type ie getTyped('patient_id','int')
set - mimic a full request, by manually setting values in _GET or _POST
keys - returns an array of keys for the GET or POST
So if I wanted to get the patient_id passed in as a get I would write
$patient_id = $this->GET->getTyped('patient_id','int')
or
$patient_id = $this->GET->get('patient_id')
The third way only works for GET. The functions on a controller can be written with arguments. like this.
function actionDefault_view($patient_id,$encounter_id)
Then if you do you URL like so
http://WHATEVER/mirrormed/index.php/main/MY_CONTROLLER/Default?patient_id=1111111&encounter_id=2222222
celini magically sets the arguments appropriately. The problem with this is that is order based and not name based. Which meanst that
http://WHATEVER/mirrormed/index.php/main/MY_CONTROLLER/Default?a=1111111&b=2222222
Would still set patient_id to 1111111 and encounter_id to 2222222. Also
http://WHATEVER/mirrormed/index.php/main/MY_CONTROLLER/Default?encounter_id=2222222&patient_id=111111
would populate the function with patient_id = 2222222 and encounter_id = 11111111 respectively. So the best and safest way to access GET and POST is using the $this->POST and $this->GET objects as described above.
Setting Messages
The default template found in local/templates/main/general_list.html has a space for messages. To set a message in this space you simply call the following function anywhere in your program
$this->messages->addMessage('This text will appear in yellow above the other Controller generated content');
require and require_once
PHP has a require function that allows other php code to be imported. However to use it you have to know where the code to be required lives which does not work with dynamically loaded modules. In the new codebase to require another class you do this...
$loader->requireOnce('includes/Grid.class.php');
$loader->requireOnce('controllers/C_SecondaryPractice.class.php');
Every module will have its own includes or lib directory but the "loader" object knows to look in all of these directories to find the code.
There are many libraries in celini that can be accessed this way. For instance to open a PEAR library, you might use..
$loader->requireOnce('lib/PEAR/HTML/AJAX/Serializer/JSON.php');
take a look under /celini/lib/PEAR for which PEAR libraries are pre-loaded with celini.
Templates
Note that in the controller functions often end with
return $this->fetch(Cellini::getTemplatePath("/messages/" . $this->template_mod . "_default.html"));
Almost always $this->template_mod resolves to "main" but it could also be "mobile" or something. Not sure how to use this effectively, but by using it you can have a totally separate class of interfaces. (like for a mobile phone)
Anyways this ends up loading the /messages/main_default.html Smarty template. The smarty project is well documented . In any case, to add a variable to the template scope, use
$this->assign('variable_name',$variable_value);
This will allow you to get whatever is in $variable_value in the Smarty template by calling
{$variable_name}
If you have an object or an array, you should assign it by reference, like this...
$this->assign_by_ref('object_name',$object);
Which allows you do things like
{$object_name->function_call()}
in the Smarty script.
Per Module Configuration
The current method of doing per-module configuration is documented in this clearhealth forum post

