PradoSoft

Profile Module tutorial

From PRADO Wiki

This article is a draft under early authorship and is rapidly changing. Please consider suggesting your changes on the discussion page.

Contents

Introduction

This tutorial will explain how to create a custom module for Prado. The module we are building is a profile module. In essence it's a database which holds key / value pairs linked to a user ID. This module can be used to allow personalization of your applications. The desired usage for a module like this is to be able to do the following :



$this->Profile['background_color'] = "#22EA33";

$myColor = $this->Profile['background_color'];



To achieve this kind of functionality we need a database where we can store the key -> user / value pairs, and we need to override PHPs magic methods.



The module consists of two classes. One interface called IProfileManager and one implementation called ProfileManager. The reason for having a interface is that the implementation we are creating here is based on SQL Lite as storage engine, while one would probably implement a MySQL / PostgreSQL version for real-world useage.





ProfileManager Interface


The interface defines the skeleton of our profile module. The real magic in this module is overriding PHPs "magic" methods __get and __set. By doing this we allow ourselves to access the profile values in a very nice way.


IProfileManager.php source


 
 
<?php
 
 interface IProfileManager{
 
	function init($config);
 
	function __destruct();
 
	function getDbFile();
 
	function setDbFile($value);
 
	function __set($key, $value);
 
	function __get($key);
 
}
 
?>
 


Implementation

Our implementation is based on the SQLLite cache module that ships with Prado. This module should really have both a MySQL implementation and a PostGre implementation, but since SQLLite works almost anywhere it's a safe bet and a good place to start. A XML implementation can also be made without much hassle.


The real "magic" happens in setKey and getKey. As you can see the userID is retrieved from the user module:

$user = $this->getApplication()->getUser();

$userID = $user->userID;


This is done to allow storage pr user basis. A nice feature would be to allow anonymous users to store settings based on their IP address or something similar.


Note that we are using a fairly "uncommon" SQL method called REPLACE. It will update the row if the key exists, and insert if it does'nt.


ProfileManagerSQLLite.php source

 
 
<?php
 
class ProfileManager extends TModule implements IProfileManager{
 
	/**
	 * name of the table storing profile data
	 */
	const PROFILE_TABLE='profile';
 
	
	/**
	 * extension of the db file name
	 */
	const DB_FILE_EXT='.db';
	
 
	/**
	 * @var bool flag to mark init of module
	 */
	private $_initialized = false;
 
 
 
	/**
	 * @var config for module. not used at the moment
	 */
	private $_data;
 
	
 
	/**
	 * @var SQLiteDatabase the sqlite database instance
	 */
 
	private $_db = null;
 
	
	/**
	 * @var string the database file name
	 */
	private $_file = null;
 
	
	/**
	 * Initializes this module.
	 * This method is required by the IModule interface.
	 * @param TXmlElement configuration for this module, can be null
	 */
	public function init($config){	
		if(!function_exists('sqlite_open'))
			throw new TConfigurationException('sqlitecache_extension_required');
 
		if($this->_file===null)
 
			$this->_file=$this->getApplication()->getRuntimePath().'/sqlite.profile';
 
		$error = '';
 
		if(($this->_db=new SQLiteDatabase($this->_file,0666,$error)) === false)
 
			throw new TConfigurationException('sqlitecache_connection_failed',$error);
 
		if(($res=$this->_db->query('SELECT * FROM sqlite_master WHERE tbl_name=\''.self::PROFILE_TABLE.'\' AND type=\'table\' LIMIT 1')) != false){
 
			if($res->numRows()===0){
 
				if($this->_db->query('CREATE TABLE '.self::PROFILE_TABLE.' (key varchar(128),value varchar(128),userid int(11), PRIMARY KEY (userid,key))') === false)
 
					throw new TConfigurationException('sqlitecache_table_creation_failed',sqlite_error_string(sqlite_last_error()));
 
			}
 
		}
 
		else{
 
			throw new TConfigurationException('sqlitecache_table_creation_failed',sqlite_error_string(sqlite_last_error()));
 
		}
 
		$this->loadUserData($config); // loads the config if needed
 
		$this->_initialized = true;
 
		
 
		parent::init($config);
 
	}
 
	/**
 
	 * Destructor.
 
	 * Disconnect the db connection.
 
	 */
 
	public function __destruct(){
 
		$this->_db = null;
 
	}
 
	
 
	/**
 
	 * @return string database file path (in namespace form)
 
	 */
 
	public function getDbFile(){
 
		return $this->_file;
 
	}
 
	
 
	/**
 
	 * @param string database file path (in namespace form)
 
	 * @throws TInvalidOperationException if the module is already initialized
 
	 * @throws TConfigurationException if the file is not in proper namespace format
 
	 */
 
	public function setDbFile($value){
 
		if($this->_initialized)
 
			throw new TInvalidOperationException('sqlitecache_dbfile_unchangeable');
 
		else if(($this->_file=Prado::getPathOfNamespace($value,self::DB_FILE_EXT)) === null)
 
			throw new TConfigurationException('sqlitecache_dbfile_invalid',$value);
 
	}
 
	
 
	/**
 
	 * Parses the config and stores it. This is not used at the moment.
 
	 * @param TXmlElement configuration for this module, can be null
 
	 */
 
	private function loadUserData($xmlNode){
 
		$this->_data = null; // not used
 
	}
 
	
 
	/**
 
	* Get a stored key from the database
 
	* @param string the key to retrieve
 
	 * @return string the value stored for the key
 
	 */
 
	private function getKey($key){
 
		$user = $this->getApplication()->getUser(); 
 
		$userID = $user->userID;
 
		if (!$userID)
 
			return null;
 
		$sql='SELECT value FROM '.self::PROFILE_TABLE.' WHERE key=\''.$key.'\' AND userid ='.$userID.';';
 
		
 
		if(($ret=$this->_db->query($sql)) != false && ($row=$ret->fetch(SQLITE_ASSOC))!== false)
 
			return $row['value'];
 
		else
 
			return false;
 
	}
 
	
 
	/**
 
	* Store a key in the database
 
	* @param string the key to store
 
	* @param string the value  to store
 
	 */
 
	private function setKey($key,$value){
 
		$user = $this->getApplication()->getUser(); 
 
		$userID = $user->userID;
 
		if (!$userID)
 
			return null;
 
		$sql='REPLACE INTO '.self::PROFILE_TABLE.' VALUES(\''.$key.'\',\''.$value.'\','.$userID.')';
 
		return $this->_db->query($sql) !== false;
 
	}
 
	
 
	/**
 
	 * stores a key / value pair
 
	* @param string the key to store
 
	* @param string the value  to store
 
	 */
 
	public function __set($key, $value){
 
		// dirty hack for overriding __set. id and dbfile are set in application config, hence set here. if additional params are supplied in appconf this breaks >:(
 
		if ($key == "id" || $key == "DbFile"){
 
			parent::__set($key, $value);
 
		}
 
		else{
 
			$this->setKey($key, $value);
 
		}
 
	}
 
	
 
	/**
 
	 * retrieve a value from a key
 
	 * @param string the key to retrieve
 
	 */
 
	public function __get($key){
 
		return $this->getKey($key);
 
	}
 
}
 
?>
 

Usage

The usage of the module is pretty straight forward. It needs to be defined in application.xml to be active.

Define in application.xml

We need to give our module a access name, a database file and a class. The access name will be the module name we request when we use it in code. The class just tells us which implementation we would like to use. In theory the implementation class can be changed just by changing the class name here (if you make sure both implementations follow the same interface and both passes the tests).


<module id="profile" DbFile="Application.Data.profile" class="Application.Common.ProfileManager">


Using the profile manager module from code

$this->Profile['background_color'] = "#22EA33";

$this->pnlFoo->BackgroundColor = $this->Profile['background_color'];

Future work

  • expose connection string in module definition
  • expose table name

- implement mysql support

Personal tools
Your user name:

Your password:

MediaWiki