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";

	}

 
}
?>

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..

You should see the text

hello world from your Sexy Module!!!

appear beneath the MirrorMed menu

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

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