An MVC Framework
An MVC Framework

The Model Class


Additional Notes

This presentation has really only highlighted the Model class. I created it mostly to present the Cascading Setters/Getters and perhaps start a discussion about such. But the actual Model class is just full of good stuff.

Here's a rundown:

abstract class Model {

	function __construct($id = null, $flds='*') {}
	
	function md() {}
	
	function Init() {}

	function LoadFromResultSet( $result_set ) {}

	function Retrieve( $object_id = false, $flds = '*' ) {}

	function Refresh( $ignore_changes = true ) {}

	function getAll( $where = null, $orderby = null ) {}

	function getAllFlds( $flds, $where = null, $orderby = null ) {}
	
	function getAllCount( $where = null ) {}

	function getAllFromLink( $object, $orderby = null ) {}
	
	function getRelated( $classname ) {}
	
	function getAllFromQuery( $qry ) {}

	function QBE( $datamap = null ) {}
	
	function TextSearch( $datamap = null ) {}

	function Save() {}
	
	function _insert() {}

	function _update() {}

	function Delete($object_id = false) {}
	
	function InitLinks() {}
	
	function establishLinks( $linknames = array() ) {}
	
	function Link( $object ) {}
	
	function UnLink($object) {}

	function _addLink( $object ) {}
	
	function _rmlink( $object ) {}

	function deleteAllLinks() {}

	function syncLinks() {}

	function sess_serialize() {}
	
	function sess_unserialize() {}

	function getID() {}

	function getNewKey($value = '') {}
	
	function ClearID() {}

	function getData( $varname ) {}
	
	function getDataMap() {}

	function setData( $varname, $value, $protected = true ) {}
	
	function setDataFromArray( $indata ) {}

	function getDatamapFromRequest( $indata ) {}

	function setDataFromRequest( $indata ) {}

	function Validate( $exclude_flds = array() ) {}

	function ValidateField( $varname, $value ) {}

	function UpdateCalcFields() {}

	function CanCreate($user_id) {}
	
	function CanModify($user_id, $fld_name = null) {}
	
	function CanDelete($user_id) {}
	
	function CanQuery($user_id) {}
	
	function Clean() {}
	
	function Exists( $object_id = false ) {}

	function IsEmpty() {}
	
	static public function GetCreate( $model_type, $id = null ) {}
	
	function getObjectFieldPair( $complexname, $recursive = true ) {}
	
	function getFieldIdx( $complexname ) {}
	

Calculated and Aggregate Fields

Since the Standard Getters look for field specific getters, calculated/aggregate fields are easy to implement and easy to use. Create a function like GetMyCalcField() and then have your application call getData("MyCalcField"). That's it.

If your calculated/aggregate field comes from the database it's even easier. When you do a DB::Query() all of the fields that are returned by the Query are loaded into the DataMap. The getData() function will return this value even though the field name is not in the metadata. (This is not a bug, it's a feature. It had to be coded.)


Some Real World SQL

While there is a Model::getAll() function that can return a list of objects from the database, based on a specific query, you might not always want to use it. While it's a good framework catchall, it doesn't fit all use cases well. And in the real world there may be better ways.

Example:
You want an inventory list with the item name and quantity on hand. Let's say you only have a hundred (100) items in your inventory. Quantity on Hand is determined by summing all the quantities for every inventory transaction of an item. Let's say an average of ten (10) transactions per item.

It's expensive to create a hundred (100) objects and their linked inventory transaction objects (10 per item or 1000 objects). Then perform the math on each inventory object to determine it's exact quantity on hand. Very expensive when all you want is a simple list.

This is exactly a situation I faced. I resolved it by creating the following function in the Action class object for the Request:

class Inventory extends Action {

	⋮
	
	function list_fn() {
		$this->SetTemplate("list.html");
		$this->data["title"] = "Inventory Listing";
					
		$this->db_obj = new $this->db_obj_class;
		$grpname = $this->db_obj_class . "s";
		$qry = "SELECT item_no, name, units, inches_ratio, yds_ratio, panels_ratio, items_ratio,
					(SELECT SUM( CASE WHEN _trn_type = 'OUT' THEN ((0 - qty)/units_ratio) ELSE (qty/units_ratio) END) AS QoH
						 FROM invtrans WHERE _item_no = i.item_no) AS QtyOnHand
					FROM items i ORDER BY name;";
		$this->data[$grpname] = $this->db_obj->getAllFromQuery($qry);
		
		return true;
	}

	⋮
}

This function passes a specific SQL statement to Model::getAllFromQuery(), which is just a pass through to DB::Query(), takes the result set and populates some objects. The View class then takes the array of objects, merges them with the template and returns the response.

In this example, this function created only a hundred (100) objects not eleven hundred (1100) objects. As such was at least one order of magnitude (10 times) faster than the alternative. It took only fourteen (14) lines of code, which is easy to audit. And required no iterations or calculations to be performed.

The magic was in the Framework that passes through SQL statements. Here, we asked the database to do the heavy lifting for us, while avoiding numerous round trips to the database. And still returning Framework Objects we're familiar with.

This is preciously what I described in the 'Background'. I'm dealing in the abstract (List Inventory); the nitty gritty details are the SQL statement; almost transparently passed through; I tweaked just the 'List' function without having to reimplement anything; it's lightweight, which in this case, translates into fast. All without the risk of breaking the Framework.

To be fair, I could've done better. The SQL statement could have been a stored procedure. And instead of calling a Model function, I could have called DB::Query() directly, saved the result set in a DataMap and modified the template to use the DataMap. That would have been faster, farther outside of the lines and still no risk to the Framework. Either way, the point has been made.

TODOs

Relationship 'n:m' is not yet implemented. But I have a use case pending so it will be done.



Back to Top
Top

© 2012 and beyond Lawrence L Hovind - All Rights Reserved