Open LMS Framework
The purpose of this document is to provide a brief orientation to the Open LMS Framework.
High Level Feature List
This section describes some of the features that are provided by the framework.
- Autoloading: the framework can automatically load its classes.
- Controllers: The "C" part of MVC, handles basic routing of requests, setup and output. There is a generic controller provided, plus ones geared towards blocks, modules and administrative pages.
- DB: Another layer of database abstraction that include tables, records, queues and table dumping.
- Exporting: An export handler that can export data to various formats. Currently supported formats are CSV, TSV, Excel and ODS.
- Formatting: Reusable and configurable formats that can transform data.
- Helper design pattern: A simple way to dynamically use library type classes. Key Helpers include:
- Buffer: Helps to buffer output.
- HTML: Helps generate HTML for routine tasks, like auto-complete.
- Load: Helps to load classes. Great for when you need to dynamically instantiate classes, like for plugins.
- Recent activity: Generate Moodle's recent activity list.
- Tag: Quick access to Tag.
- Test Web Services: Helps with testing web services in unit testing.
- Filtering: Easy way to display filtering options to a user which then generate SQL to limit results. The filtering options are persistent through the user's session.
- Notify: Easy session based notification system. Most common use-case: set the message, redirect and display the message.
- Heading: Assists with printing headings with or without help icon.
- Table: Tabular data display with sorting. The columns are configurable objects.
- Paging: Paging bar and SQL limits.
- Tabs: API wrapper over Moodle's tab API. Attempts to automate and simplify tab creation/management.
- Tag: HTML tag generation.
- Distributed locking: Provide a locking mechanism to ensure that only one process is performing a specific task at one time. Current back end is Redis.
- Model: Provide very basic abstract model classes.
- Model repository mapper: Abstract class for mapping models to the DB and visa versa. See http://martinfowler.com/eaaCatalog/dataMapper.html
- Plugin: Simple class for Sub-plugins.
- Preferences: user session preferences that are course and plugin aware.
- Report: Combines filtering, table, columns, formatting, exporting, paging and preferences for simple report generation.
- Server: A set of classes for providing a web service endpoint. Currently only the REST server is provided. Other classes involved are request validation and server response.
- Var: Static variable storage.
- Fixture: A fixture API for setting up and tearing down fixtures like users, courses, enrollments in unit tests.
- All of the framework's files are located inside of this local plugin.
The local/mr plugin install normally. To get additional features, follow these steps:
- Display framework documentation
- Add the following code to your config.php:
- This will display the following link: Site Administration > MR Framework > Docs
- Add the following code to your config.php:
Quick Start Guide
It is very easy to start using the Open LMS Framework. Just include the following line in the PHP script to automatically load any of the Open LMS Framework classes:
That's it! Now you can start using the framework:
$tag = new mr_html_tag(); echo $tag->b('Hello World!');
Framework Design Patterns
This section describes some of the reasoning behind the design of the major components of the framework.
Model-View-Controller (MVC) is a popular coding design pattern. From my understanding (Mark Nielsen's), it is popular because its design facilitates the re-use of code, which in turn reduces the size of the code base, which in turn results in multiple benefits through the life cycle of the software. The Open LMS Framework works off of this design pattern.
Controllers handle the application logic by responding to user actions, processing those actions (generally by using models) and then generating output to the user by means of views.
The Open LMS Framework provides the controller through the base mr_controller class. The controller is responsible for routing HTTP requests, security checks and general variable setup. Each controller endpoint is responsible for whatever processing that needs to occur and, if needed, setup and return the view for sending to the user.
Advantages of controllers:
- Stores application logic.
- Easy to create a consistent look and feel throughout a plugin.
- Easier to implement security restrictions. Through a single method, a controller can protect all of its actions from unprivileged users. This can be a blanket restriction or fine grained access for each action.
- Controllers allow one to focus on the task at hand instead of all the overhead of generating a secure and full featured Moodle page.
- Controllers help to organize code into smaller functional snippets instead of large files with very large if/else if and switch statements that tend to produce security holes and nasty bugs.
- Automatic setup of local/mr/framework classes and of plugin variables like instance records, contexts, etc.
- Reduces code duplication in plugins.
Explanation of controller execution in the framework
Enough abstract, let's dig into what the framework's controller is really doing. All HTTP requests come in through a single point. The way that the request gets routed all depends on the request's parameters. The controller parameter determines which controller to use. The action parameter determines which function to call within the controller. The defaults are, controller='default' and action='view' . In order for the controller to handle the various actions, it must implement a method with the following naming scheme: public function ACTION_action() where ACTION is the value of the action parameter. If the action method returns a string, then the controller will print the header and footer around the string, otherwise the controller won't do anything after the action method is called. Returning void or false is useful for when you really have to customize output or want to avoid printing header/footer altogether (e.g. web service response). Of course, all security checks are executed before the action method to make sure the user is logged in and has the appropriate capabilities. For more information on the controller, read local/mr/docs, view the source code of local/mr/framework/controller/*.
Views handle the presentation and do not handle any complex processing or business logic.
With the introduction of renderers in Moodle 2.0, the Open LMS Framework can now use renderers for generating the view. Though, not every controller endpoint may need a specific renderer method, it is beneficial to write renderers as they can be re-used and overridden by theme developers. The framework will provide some default renderers that is easily accessible through the controller and Moodle API.
Advantages of views (AKA Moodle renderers):
- Stores presentation.
- Separates logic/processing code from display code.
- Re-usable renderings.
- Easier to customize the rendering via another plugin or theme.
Models handle the business logic by processing data and storing business rules within them.
Models house your application's data so that one can more easily guarantee the correctness of the data. Models contain no display logic, but rather, only logic for accessing, setting and validating data. It is encouraged that plugins have their own model directory to house plugin specific model classes.
Advantages of models:
- Stores business logic. EG: valid values for widget's foo property.
- Easier to test and modify.
- Helps to reduce code duplication.
The helper design pattern was borrowed from Zend Framework's Action Helpers. Zend's manual has a great description of the purpose of helpers:
Action Helpers aim to minimize the necessity to extend the abstract Action Controller in order to inject common Action Controller functionality.
So, in other words, the helpers provide an easy way to re-use code throughout the application without having to overly extend classes, which could potentially lead to accidentally overriding a function used by the parent class (e.g. a new version of the parent class comes out and implements a new function that you have implemented in your child class).
The implementation of helpers in the Open LMS Framework does not follow Zend's implementation exactly, but rather it has been simplified and generalized. So, the helper design in the Open LMS Framework can be used outside of controllers so that the whole application can reuse the code in the helpers. This means that the helpers are now like a helper library that is easy to access, loads dynamically and requires very little setup.
Helpers do not replace models or other classes with clear purpose
The point of Helpers is not to replace models or other classes, but to replace files like /path/to/plugin/locallib.php and to provide functionality across classes in your plugin.
Advantages of helpers over traditional libraries:
- The number one use and advantage of helpers is in controllers. This allows the controller to remain skinny by just having methods related to routing, security, etc.
- Helpers replace the need for a /path/to/plugin/locallib.php which can be massive, unorganized and uses more memory because it has to be loaded all at once every time any of the methods are used.
- Helpers are treated as singletons, so their state remains constant throughout the execution of your script. This means that any setup happens only once, caching is easy, your helper can have a state.
- No name space issues. The helper's class is based on the location in the code base and its name. No more, block_crazy_block_name_now_we_get_to_function_name() type function definitions.
- Dynamically loads. Class files are included and classes are instantiated only for helpers that are used. This is better than loading a 10,000 line library file every time.
- Easy setup and it's the same for every helper. Just include the framework's bootstrap.php, make an instance of the helper and you are good to go. No more require_once for every library or class file at the top of every PHP script in which they are used.
- May not have to extend parent classes in order to add your own set of methods. Store them all in the helper instead. EG: instead of extending the base controller class to make a new parent class with new methods, add methods to helpers that your controllers can then access.
- Easily share code between drastically varying classes.
- Easily share code between plugins et al (e.g. two plugins may share a common set of methods but the rest do not; store those method in a helper instead of mucking up the base plugin class definition).
For more information on helpers, read the framework's PHPDocs, view the source code of local/mr/framework/helper/*.
A lot of the framework implements a fluent interface when it makes sense. A fluent interface is when you can string multiple function calls together for a single object. This is achieved by each function returning the object itself, e.g. return $this; or return new some_nifty_class(). So, a fluent interface can look like this:
Also, if the function calls are long, you can break it out onto separate lines:
$object->foo(...) ->bar(...) ->baz(...);
The above is equivalent to the following, which doesn't take advantage of the fluent interface. (Note: each method still returns the $object instance, but it's just ignored now.)
$object->foo(...); $object->bar(...); $object->baz(...);
Advantages of a fluent interface:
- Increases readability of code.
- Reduces amount of code.
- Allows for really neat functionality, e.g. the helpers use this to dynamically load objects, thus giving one access to the objects methods.
- It's really cool! /snort
Disadvantages of a fluent interface:
- Since there are multiple function calls in one line of code and if there is an error in the middle of the fluent interface calls, then it could increase the difficulty of debugging where exactly the problem lies.
Class Naming Conventions
The class naming conventions used by the Open LMS Framework also mimics Framework's class naming conventions. The class name is derived by the class file's directory path and file name. Directories are separated by underscores and everything is lowercase. One exception is blocks, where it is reasonable to use block instead of blocks. Example:
- mod/modname/model/foo.php maps to mod_modname_model_foo
- blocks/blockname/form/edit.php maps to blocks_blockname_form_edit or block_blockname_form_edit
Advantages to using this class naming convention:
- Reduce the chance of name space collisions.
- When looking at the class name, one can easily derive where the class definition file is located.
- Classes and their files can be more easily dynamically loaded. See local/mr/framework/helper/load.php which loads classes and their files.
Using this naming convention in your plugin will make it easier to use all of the features of the framework.
Sometimes it makes sense to have plugins inside of your plugin. The framework uses mr_helper_load::plugin(...) method to load single or multiple base plugin classes. In order to take advantage of this functionality, your sub-plugins must be organized in a specific way:
- The above is the base plugin class where path can be any directory depth.
- The above is the actual plugin where path matches the path for the base and name is the plugin's name.
Note that the above uses path which means you can have plugins at any directory depth which in turn means, your sub-plugins can have sub-sub-plugins if so desired. Example of common plugin layouts:
- modxuser/class.php (Extends plugin/base/class.php)
- course/class.php (Extends plugin/base/class.php)
- xml/class.php (Extends plugin/export/base/class.php)
- csv/class.php (Extends plugin/export/base/class.php)
- user/class.php (Extends plugin/report/base/class.php)
- class/class.php (Extends plugin/report/base/class.php)
Advantages of sub-plugins
- Easy iterative functionality. EG: loop over all your plugins, each get to execute their own unique code.
- Convenient way to group files that relate. EG: The base class and form classes.
- Supports drop in code that runs automatically.
For more information on plugins, read the framework's PHPDocs on mr_helper_load.
Below is the recommended way to setup the structure of a plugin that wants to make use of the Open LMS Framework. Please note that the below is not all or nothing, but rather each can be implemented individually and as needed.
|view.php||Endpoint for the controller, so all URLs go to this file. Using view.php is consistent with Moodle's coding standards.||mr_controller|
|controller/||Store controller classes in this directory.||mr_controller|
|controller/default.php||This is the default controller, not necessary, but makes for short default URLs: wwww/plugin/path/view.php routes to this controller's view action.||mr_controller|
|helper/||Store helper classes in this directory. The helper will search here for helpers first and then in local/mr/framework/helper/.||mr_helper|
|plugin/||Store any sub-plugins in this directory. This is for organizational/consistency purposes.||None|
|plugin/path/base/class.php||The base plugin class where path can be any directory depth.||mr_helper_load|
|plugin/path/name/class.php||The actual plugin where path matches the path for the base and name is the plugin's name.||mr_helper_load|
|lib/||Store library classes in this directory. This is for organizational/consistency purposes only.||None|
|model/||Store any model classes in this directory. This is for organizational/consistency purposes only.||mr_model_abstract (Extension not required)|
|repository/||Store any model repository mappers in this directory. This is for organizational/consistency purposes only.||mr_repository_abstract (Extension not required)|
|form/||Store any Moodle form classes in this directory. This is for organizational/consistency purposes only.||None|
|cli/||Store any CLI scripts in this directory. This is for organizational/consistency purposes only.||None|
|report/||Store plugin report classes that extend mr_report_abstract.||mr_report_abstract|
The Open LMS Framework requires the installation of a single plugin:
- Open LMS Framework - unzip the contents into /wwwroot/local
About the Contributors
This plugin was contributed by the Open LMS Product Development team. Open LMS is an education technology company dedicated to bringing excellent online teaching to institutions across the globe. We serve colleges and universities, schools and organizations by supporting the software that educators use to manage and deliver instructional content to learners in virtual classrooms.