Groups Application Framework: Difference between revisions
Blinder.dave (talk | contribs) No edit summary |
(link) |
||
(4 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
==Introduction== |
==Introduction== |
||
The groups application was developed using PHP (version 5 is a requirement) utilizing a fully |
The [[groups.laptop.org|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. |
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. |
||
Line 14: | Line 14: | ||
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: |
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 '<code>appConfig</code>' and '<code>datasource</code>' and other behind-the-scenes modules the application uses indirectly |
|||
A bean definition has two key parts, 'id' and 'class': |
A bean definition has two key parts, 'id' and 'class': |
||
Line 35: | Line 35: | ||
The above example shows that the class '<code>FooAction</code>' injects a product called '<code>jsonProduct</code>.' 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: |
The above example shows that the class '<code>FooAction</code>' injects a product called '<code>jsonProduct</code>.' 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: |
''Example'': |
||
<bean id="/some-action" class="some.package.FooAction"> |
<bean id="/some-action" class="some.package.FooAction"> |
||
Line 50: | Line 51: | ||
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. |
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: |
''Example'': |
||
<bean id="/some-action" class="some.package.FooAction"> |
<bean id="/some-action" class="some.package.FooAction"> |
||
Line 62: | Line 63: | ||
</bean> |
</bean> |
||
;PlainTextProduct: - returns plain text |
|||
Example: |
''Example'': |
||
<bean id="/some-action" class="some.package.FooAction"> |
<bean id="/some-action" class="some.package.FooAction"> |
||
Line 74: | Line 75: | ||
As you can see some products, such as the plain text product, does not require any additional properties. |
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: |
''Example'': |
||
<bean id="/some-action" class="some.package.FooAction"> |
<bean id="/some-action" class="some.package.FooAction"> |
||
Line 99: | Line 100: | ||
<ref bean="some_bean_name"/> |
<ref bean="some_bean_name"/> |
||
This means that the bean that is being referenced lives in another context XML |
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"/> |
<import resource="some_other_file.xml"/> |
||
) |
|||
==Action Classes== |
==Action Classes== |
||
Line 140: | Line 140: | ||
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: |
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 <code>AbstractAction.php</code> 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 first four lines include a few PHP files. The first include is for the Action interface. The other include, for <code>AbstractAction.php</code> 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. |
* 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. |
* 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. |
* 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 <code>$_REQUEST</code> object.) |
* Moving along we pull two theoretical parameters that get passed in as an associative array (which is actually the <code>$_REQUEST</code> 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 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 <code>setTemplateObject</code>, 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 "<code>user_object</code>" which will hold a reference to our actual user object. That way, in our template we can do things like: |
* We then, as the example shows, call a function on the product called <code>setTemplateObject</code>, 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 "<code>user_object</code>" 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} |
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. |
* 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: |
Using the above action class example, here's what its XML definition would look like: |
||
Line 190: | Line 190: | ||
Also of note is that there are several objects that are available to you in your action classes, such as the global <code>$appConfig</code> object, which is defined in <code>applicationContext.xml</code>. |
Also of note is that there are several objects that are available to you in your action classes, such as the global <code>$appConfig</code> object, which is defined in <code>applicationContext.xml</code>. |
||
== System philosophy == |
|||
The goal of the system is truly to "write to interfaces" and not worry so much about the implementations. |
The goal of the system is truly to "write to interfaces" and not worry so much about the implementations. |
||
[[category:projects]] |
|||
[[category:contributors]] |
Latest revision as of 22:05, 22 June 2009
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.