Groups Application Framework

From OLPC
Revision as of 18:05, 22 June 2009 by Sj (talk | contribs) (link)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

The groups application ( currently under development at http://one.mogidi.net/en/ ) was developed using PHP (version 5 is a requirement) utilizing a fully object oriented design. Several patterns were followed in the development of the site's framework and is wrapped in a flexible and extensible MVC (Model-View-Controller) system.

The application's underlying framework (which "wires" everything together) follows an Inversion of Control/Dependency Injection system (IoC/DI) which means the application can quite easily accommodate new functionality without ever having to touch or even be aware of the controller code.

This document will describe how to extend the application by creating a new "action" or "module."

The IoC/DI container system is called phpBoing and is a separate open source project (for those familiar with the Spring framework will hopefully recognize the significance of the name.)

Application Context

The central file that "wires" all of the components of the site together is the applicationContext.xml (residing in webroot/applicationContext.xml)

In this file you will notice many "bean" (yes, beans, no this isn't Java, its PHP) definitions, these represent individual modules or components that make up each piece of functionality of the site. Beans are logically divided into several types:

actions
whose bean names typically start with a "/"
DAOs
Data Access Objects - these beans handle the CRUD functions and are "injected" as dependencies into action beans
utility
beans such as 'appConfig' and 'datasource' and other behind-the-scenes modules the application uses indirectly

A bean definition has two key parts, 'id' and 'class':

<bean id="some_name" class="some.package.Class"/>

The 'id' is simply a way to identify your bean within the internal registry of beans. If you do not provide an ID one will be automatically generated for you, but in creating action classes, you MUST provide an ID, and it MUST map to the URI of your action.

This is an important distinction. Action classes (defined as beans) have a one-to-one mapping to URIs. For example, there is a URI to handle logins: '/login' There is a bean who's ID is: '/login'

Beans also MUST inject a "product" property:

<bean id="/some-action" class="some.package.FooAction">
	<property name="product">
		<bean id="jsonProduct" class="org.laptop.community.products.JsonProduct"/>
	</property>
</bean>

The above example shows that the class 'FooAction' injects a product called 'jsonProduct.' Products are objects that action classes produce. There are many pre-built products that should satisfy most, if not all, requirements for a typical web application. Currently there are:

JsonProduct
produces valid json output, useful for ajax calls
HtmlRedirectProduct
executes a javascript redirect based on a URL fed as a parameter


Example:

<bean id="/some-action" class="some.package.FooAction">
	<property name="product">
		<bean class="org.laptop.community.products.HtmlRedirectProduct">
			<property name="content"><value>/some-other-url</value></property>
		</bean>
	</property>
</bean>

You'll notice in the above example I did not provide an 'id' attribute for the product bean definition. This is OK because this is only used internally.

PermanentRedirectProduct
- sends a permanent HTTP redirect based on a URL fed as a parameter

Example:

<bean id="/some-action" class="some.package.FooAction">
	<property name="product">
		<bean class="org.laptop.community.products.PermanentRedirectProduct">
			<property name="content"><value>/some-other-url</value></property>
		</bean>
	</property>
</bean>
PlainTextProduct
- returns plain text

Example:

<bean id="/some-action" class="some.package.FooAction">
	<property name="product">
		<bean class="org.laptop.community.products.PlainTextProduct"/>
	</property>
</bean>

As you can see some products, such as the plain text product, does not require any additional properties.

TemplateDrivenProduct
- this utilizes the smarty template engine to merge PHP objects with a smarty template to generate HTML output. The name of the smarty template to use is provided as a property.

Example:

<bean id="/some-action" class="some.package.FooAction">
	<property name="product">
		<bean class="org.laptop.community.products.TemplateDrivenProduct">
			<property name="content">
                              <value>some-smarty-template.tpl</value>
                       </property>
			<property name="templateEngine">
				<ref local="templateEngine">
			</property>
		</bean>
	</property>
</bean>

You will notice that the TemplateDrivenProduct requires an additional property, one of 'templateEngine.' We do not assume that smarty is the templating mechanism. In fact, we've provided a template engine interface so that templating systems can be swapped or combined within the same system. Its just up to the developer to create an implementation of the libs/i_product.php interface.

You'll also notice that the TemplateEngine property is "injected" using a REF element (short for "reference.") That means this bean "templateEngine" is defined elsewhere within this application's context XML file.

You can also use a reference of:

<ref bean="some_bean_name"/>

This means that the bean that is being referenced lives in another context XML, which must be included using:

<import resource="some_other_file.xml"/>

Action Classes

As mentioned in the section above, action classes are PHP classes that implement the org.laptop.community.interfaces.Action interface, accept a product and perform some kind of operation based on a URI requested by a user.

The best way to describe an action class is to show an example action:

include_once(CLASSPATH."/org/laptop/community/interfaces/Action.php");
include_once(CLASSPATH."/org/laptop/community/actions/AbstractAction.php");
include_once(CLASSPATH."/org/laptop/community/daos/interfaces/UserDao.php");
include_once(CLASSPATH."/org/laptop/community/models/User.php");

class FooAction extends AbstractAction implements Action {
	private $product;
	private $userDao;
	
	public function execute($args) {
		$username = $args['username'];
		$password = $args['password'];
		
		$userObject = $this->userDao->authenticate($username,$password);
		
		$this->product->setTemplateObject("user_object",$userObject);
		
		return $this->product;
	}
	
	public function setProduct($product) {
		$this->product=$product;	
	}	
	
	public function setUserDao($dao) {
		$this->userDao=$dao;
	}
}

This is a very simplistic example, and doesn't actually do anything interesting. This is just to show you the basic anatomy of an action class. We'll take it line by line:

  • the first four lines include a few PHP files. The first include is for the Action interface. The other include, for AbstractAction.php simply defines an empty abstract class that each action may extend. Currently this abstract class does nothing, but is there should the need ever arise where any "heavy lifting" need to be done that shouldn't be done by a concrete class.
  • The next line we define our class, extending the abstract action and telling PHP we will be implementing a single interface.
  • Next we define two private variables, which will hold pointers to our product and in this example, the user data access object.
  • We then provide an implementation of the execution function (which is part of the Action interface.) An action class MUST implement this function or the controller will fail.
  • Moving along we pull two theoretical parameters that get passed in as an associative array (which is actually the $_REQUEST object.)
  • We then maybe will call a method on our user data access object (in this example we do a authentication, which returns a user object.)
  • We then, as the example shows, call a function on the product called setTemplateObject, which takes two arguments, a string that represents the "key" in which smarty uses when generating its symbol table and the actual object. This basically says that in our smarty template, we will have a key of "user_object" which will hold a reference to our actual user object. That way, in our template we can do things like:
Hello, your email is: {$user_object->email}

  • The last thing our execute function must do is return a reference to our product. The controller (which invokes our beans) expects a product back so that it can present content back to the browser.

Using the above action class example, here's what its XML definition would look like:

<bean id="/foo-action" class="org.laptop.community.actions.FooAction">
	<property name="product">
		<bean class="org.laptop.community.products.TemplateDrivenProduct">
			<property name="content">
				<value>foo-action-result.tpl</value>
			</property>
			<property name="templateEngine">
				<ref local="templateEngine"/>
			</property>
		</bean>
	</property>
	<property name="userDao">
		<ref local="userDao"/>
	</property>
</bean>

There you can start to see the power and flexibility of this system in that if you wanted to change what this action does, you could, for example, change the product to the JsonProduct, and in your action just change this line:

$this->product->setTemplateObject("user_object",$userObject);

to:

$this->product->setContent($userObject);

and the browser would be presented with a nice json string.

The same could be done by swapping out template engines, or even alternate implementation of the user dao, so long as each implements a common interface (which this system depends on heavily) then no code changes ever have to be made to modify key behaviors.

Also of note is that there are several objects that are available to you in your action classes, such as the global $appConfig object, which is defined in applicationContext.xml.

System philosophy

The goal of the system is truly to "write to interfaces" and not worry so much about the implementations.