Location: PHPKode > projects > ArasPhp Framework > ArasPhp-0.03/core/libs/Model.php
<?php

/***************************************************************
 * ARASPHP WEB DEVELOPING FRAMEWORK
 * 
 * Website:	www.arasphp.org
 * Author:	Arturo López Pérez
 *			hide@address.com
 * Version: 	0.02
 ***************************************************************
 *
 * This file it's part of ArasPhp Web developing framework.
 *
 * This project is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This project is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
* Model
* Base to all app models.
* Can handle ANY table dinamically. It can
*
* 1) Fetch information as object
* 2) Insert
* 3) Update
* 4) Delete
*
* It handles flexible auto-validation
*
* And many more useful things =)
*
* (!)You can put your own shared methods in /core/AppModel.php
*/

abstract class Model extends ErrorLogger {

	// Validation errors
	var $validationErrors = NULL;
	
	// Possible relationships
	var $possibleRelationships = array("hasMany","hasOne","belongsTo");
	
	/**
	* CONSTRUCTOR
	* It has two ways
	* 1) From an Id. It will fetch all it's attributes from the database
	* 2) From an assoacciative array.
	*
	* @param arg. Can be numeric or array
	*/
		
	function __construct($arg) {
	
		/*
		* CASE 1: SELF-CONSTRUCT FROM THE DATABASE
		* $arg is numeric
		*/
	
		if(is_numeric($arg))
		{
			// Our id
			$this->id = $arg;
			
			// Assign our out attributes
			self::refresh();
			
			// Out!
			return;
		
		}
		
		/*
		* CASE 2: ASSOCIATIVE ARRAY RECEIVED TO CONSTRUCT A NEW OBJECT
		* $arg is associative array.
		*
		* > Parameters >= required and <= required+optional
		* > All REQUIRED fields must be provided
		* > OPTIONAL fields may or may not be provided
		*/
		
		if(is_array($arg))
		{
			// Our fields
			$required = self::getRequiredFields();
	 		$optional = self::getOptionalFields();
	 		$forbidden = self::getForbiddenFields();
	 		
	 		/*
	 		* CHECK 1: Are the number of parameters correct?
	 		*/
	 		
	 		if(!(count($arg) >= count($required) &&
	 			count($arg) <= (count($required) + count($optional))))
	 		{
	 			parent::logError("Number of fields passed do not fit the model requirements.");
	 			return;
	 		}
	 		
	 		
	 		/*
	 		* CHECK 2: Are all required parameters satisfied?
	 		*/
	 		
			foreach($required as $requiredField)
			{
				$satisfied = false;
					
				// Is this required parameter in our array?
				foreach($arg as $field => $value)
				{
					if($requiredField == $field)
					{
						$satisfied = true;
					}
				}
				
				// Outa here, if satisfied is still false, this parameter is not satisfied
				if($satisfied == false)
				{
					parent::logError("Missing field `$requiredField` for model `" . get_class($this) . "`");
				}
			}	 	
			
			if(parent::failed())
				return;	
				
			/*
			* CHECK 3: Are the missing parameters to validate optional parameters?
			* Any unknown parameter will cause an error
			*/
			
			foreach($arg as $field => $value)
			{
				// A parameter can only be a required or optional
				if(!(in_array($field, $required) || in_array($field, $optional)))
				{
					parent::logError("Unknown field `$field` for model `" . get_class($this) . "`");
				}
				
			}
			if(parent::failed())
				return;	
				
			/*
			* CHECK 4: Is there any forbidden field provided?
			*/
			foreach($arg as $field => $value)
			{
				if(in_array($field, $forbidden))
				{
					parent::logError("Forbidden field `$field` provided for model `" . get_class($this) . "`");
				}
				
			}
			if(parent::failed())
				return;	
						
	 		
			/*
			* We got here!
			* Everything good so far
			* Let's self assign
			*/
			foreach($arg as $key => $value)
			{
		    		$this->$key = $value;
			}	
			
			// If desired by the model, we validate ourselves.
			if($this->autoValidation == true)
				self::validate();		
			
			// Everthing cool. Out!
			return;
		}
		
		// We get here in case arg passed to the constructor is not numeric or array
		parent::logError("Passed parameters does not fit any of the valid constructor arguments. Only and integer or an assoaciative array are allowed.");
			
	} // CONSTRUCTOR END
	
	
	/*
	* DATABASE QUERIES OPERATION WITH THE MODEL
	* > Insert
	* > Update
	* > Delete
	*
	* Uses helpers methods:
	* > getUpdateQuery
	* > getInsertQuery
	* > getDeleteQuery
	*/ 
	
	/**
	* The insert method inserts a new row in the database and fetches it's id to his own id attribute
	* Checks that related before and after methods exists, if not, error and out.
	*/
	
		public function insert()
		{
			// Populate date_created  and time_stamp
		 	self::populateDateCreated();
		 	self::populateTimeStamp();
		 	
		 	// We check that beforeInsert() and afterInsert() methods exist
		 	$traceBefore = self::executeRelatedMethods($this->beforeInsert, true);
			$traceAfter = self::executeRelatedMethods($this->afterInsert, true);
			
			if($traceBefore == false || $traceAfter == false)
			{
				parent::logError("The insertion hasn't been performed due related methods errors.");
				return;
			}
			
			// If we got here we will execute the before methods
			self::executeRelatedMethods($this->beforeInsert);
			
			// If any of those is false, the insertion will not be executed
		 	
			$result = mysql_query(self::getInsertQuery());
			
			if(mysql_error())
			{
				// We add error log here
				parent::logError("Insert into $this->table failed! Database said: " . mysql_error());
				
			} else {
			
				// If no mysql_error and everything is cool...
				$this->id = mysql_insert_id();
				
				// Execute the after methods
				self::executeRelatedMethods($this->afterInsert);
			}
		
			
		}
		
	/**
	 * This method is a bit tricky. It will execute or simulate the execution of a bunch of methods passed in array
	 * They will be used for beforeInsert, afterInsert, beforeUpdate, beforeDelete and so on methods...
	 *
	 * @param simulate enabled will just return wether the methods exists or not
	 * @return	true -> everything ok 
	 *		false -> some method does not exist 
	 */
	 
	 	private function executeRelatedMethods($methodsCollection, $simulate = false)
	 	{
	 		// If we have something to execute
	 		if(isset($methodsCollection) && is_array($methodsCollection))
	 		{
	 			// Then we check they all exist before executing any of them
	 			foreach($methodsCollection as $method)
	 			{
	 				if(!method_exists($this, $method))
	 				{
	 					parent::logError("The method `$method` does not exist for the model `" . get_class($this) . "`");
	 				}
	 			}
	 			
	 			// If any errors ocurred, we are out
	 			if(parent::failed()) { 
	 			
	 				return false; 
	 				
	 			} else {

					// If no error has been thrown and only simulation was asked, return true
					if($simulate == true) { return true; }	 				
	 				
	 			}
	 			
	 			// Got here! Anything failed. Now let's execute them
	 			foreach($methodsCollection as $method)
	 			{
	 				$this->$method();
	 			}
	 			
	 			// Everything executed
	 			return true;	
	 			
	 		// If there is nothing to execute, return true as everything is cool anyway
	 		} else {
	 		
	 			return true;
	 		
	 		}
	 		
	 	}
	
	 /**
	 * Update all it's data
	 * Updates every row of itself with the stored values in the object
	 * Executes beforeUpdate and afterUpdate methods as well
	 */
	 
		 public function update()
		 {
		 	// Populate date_modify if exists
		 	self::populateDateModify();
		 	
		 	// We check that beforeUpdate() and afterUpdate() methods exist
		 	$traceBefore = self::executeRelatedMethods($this->beforeUpdate, true);
			$traceAfter = self::executeRelatedMethods($this->afterUpdate, true);
			
			if($traceBefore == false || $traceAfter == false)
			{
				parent::logError("The update hasn't been performed due related methods errors.");
				return;
			}
			
			// If we got here we will execute the before methods
			self::executeRelatedMethods($this->beforeUpdate);
		 	
		 	$result = mysql_query(self::getUpdateQuery());
		 		
		 	if(mysql_error())
			{
				// We add error log here
				parent::logError("Update $this->table failed! Database said: " . mysql_error());
			} else {
			
				// Execute the after methods
				self::executeRelatedMethods($this->afterUpdate);			
				
			}
		 }
	 
	
	/**
	 * Deletes it's row from the database
	 */
	 
		 public function delete()
		 {
		 
		 	// We check that beforeDelete() and afterDelete() methods exist
		 	$traceBefore = self::executeRelatedMethods($this->beforeDelete, true);
			$traceAfter = self::executeRelatedMethods($this->afterDelete, true);
			
			if($traceBefore == false || $traceAfter == false)
			{
				parent::logError("The delete hasn't been performed due related methods errors.");
				return;
			}
			
			// If we got here we will execute the before methods
			self::executeRelatedMethods($this->beforeDelete);
			
			$result = mysql_query(self::getDeleteQuery());
			
			if(mysql_error())
			{
				// We add error log here
				parent::logError("Deleting on $this->table failed! Database said: " . mysql_error());
				
			} else {
			
				// We delete dependant models if any
				self::cascadingDelete();
				
				// Execute the after methods
				self::executeRelatedMethods($this->afterDelete);			
				
			}
		 }
	 
	 /**
	 * @Return the insert query for this model
	 */
	 
		 protected function getInsertQuery()
		 {
		 	// First part of the query
		 	$tempQuery = "INSERT INTO `$this->table` SET ";
		 	
		 	// Second part, the fields values stuff
		 	$tempQuery = $tempQuery . self::getFieldsSql(true, false);
	
		 	return $tempQuery;
		 }	
	
	 /**
	 * @Return the update query for this model
	 */
	 
		 protected function getUpdateQuery()
		 {
		 	// First part of the query
		 	$tempQuery = "UPDATE `$this->table` SET ";
		 	
		 	// Second part, the fields values stuff
		 	$tempQuery = $tempQuery . self::getFieldsSql(false, true);
		 	
		 	// Finally, conditions
		 	$tempQuery = $tempQuery . " WHERE id=". $this->id;
		 	
		 	return $tempQuery;
		 }
	 
	  /**
	 * @Return the delete query for this model
	 */
	 
		 protected function getDeleteQuery()
		 {
		 	// First part of the query
		 	$tempQuery = "DELETE FROM `$this->table` WHERE id = '$this->id'";
		 	
		 	return $tempQuery;
		 }
	 
	 
	 /**
	 * Creates a part of all Sql statements. field1 = 'value1', field2 = 'value2' and so on
	 *
	 * @return <field> = '<value>' string to use in sql queries like save or insert
	 * It will not do anything with it's own id and table name
	 * It will trim all values for best database performance
	 */
	 
		 protected function getFieldsSql($insert = false, $update = false)
		 {
		 	$ignoreFields = array("id");
		 	
		 	// If is for insert, we will ignore UPDATE_POPULATED
		 	if($insert == true) { $ignoreFields[] = UPDATE_POPULATED; }
		 	
		 	// If is for update, we will ignore DATE_POPULATED AND TIMESTAMP_POPULATED
		 	if($update == true)
		 	{ 
		 		$ignoreFields[] = DATE_POPULATED;
				$ignoreFields[] = TIMESTAMP_POPULATED;
			}
		 	
		 	// Create sql list of fields and values
		 	foreach($this as $key => $val) {
		
	   			if(in_array($key, self::getFields()) && !(in_array($key, $ignoreFields)))
	   			{
	   				/* 
	   				* When inserting TIMESTAMP_POPULATED we must not put ' ' among the value
	   				* So we'll just have to ask every time
	   				*/
	   				if($key != TIMESTAMP_POPULATED)
	   				{
	   					$returnSql = $returnSql . $key . "='" . trim($val) . "',";
	   				} else {
	   					$returnSql = $returnSql . $key . "=" . trim($val) . ",";
	   				}
	   			}
	   		}
	   		
	   		// We return a new string without the last coma
	   		$returnSql = substr($returnSql, 0, strlen($returnSql)-1);
			
			return $returnSql;	
		 }
		 
		 
	/**
	* Deletes all hasMany and hasOne entries that depends on this one
	*/
	
	protected function cascadingDelete()
	{
		// For relationship possible
		foreach($this->possibleRelationships as $relationship)
		{
			// If this relationship is declared and it's not belongsTo
			if(isset($this->$relationship) && $this->relationship != "belongsTo")
			{
				// Inside it may be lots of relationships of this type
				foreach($this->$relationship as $local)
				{
					// If depends index is declared 
					if(isset($local['depends']))
					{
						// If it's value it's true
						if($local['depends'] == true)
						{
							// We request all depending models
							$colection = $this->getRelated($local['className']);
							
							// If is not null we delete all of their entries
							if($colection != NULL) 
							{
								foreach($colection as $object)
								{
									$object->delete();
								}
							}
						}
					}
				}
			}
		}
	}
	
	// END DATABASE QUERIES OPERATION WITH THE MODEL
		 
	 /*
	 * DATES AND TIMESTAMP POPULATION FUNCTIONS
	 *
	 * They manage the auto-population feature of this framework
	 */
	 
	 /**
	 * Populates date_modify just before an update action
	 */
	 
		 protected function populateDateModify()
		 {
		 	if(self::field_exists(UPDATE_POPULATED))
		 	{
		 		// If is not today
		 		$this->date_modify = date("Y-m-d");	 		
		 	}
		 }
	 
	  /**
	 * Populates date_created just before an insert action
	 */
	 
		 public function populateDateCreated()
		 {
		 	if(self::field_exists(DATE_POPULATED))
		 	{
		 		$this->date_created = date("Y-m-d");	 		
		 	}
		 }
	 
	  /**
	 * Populates timestamp just before an insert action
	 * (!) It will use mysql constant CURRENT_TIMESTAMP
	 */
	 
		 protected function populateTimeStamp()
		 {
		 	if(self::field_exists(TIMESTAMP_POPULATED))
		 	{
		 		// If is not today
		 		$this->time_stamp = "CURRENT_TIMESTAMP";	 		
		 	}
		 }
		 
	// END OF AUTO-POPULATING FUNCTIONS
	 
	 /*
	 * TABLE'S SCHEMA FUNCTIONS
	 *
	 * They stand to provide information about all fields of the model's associated table
	 * Field Name | Field type | Null | Not_null and so on
	 * So the model can work in consequence and avoid errors
	 */
	 
	 /**
	 * Creates an associative array of all fields in our database
	 * Storing in each index fields name, field type and null/not null indicator
	 * array['fieldname'] -> array2['type'] = whatever, array2['null'] = bool
	 */
	 
		 public function getSchema()
		 {
		 	$resource = mysql_list_fields(BBDD, $this->table);
	 		$fields = mysql_num_fields($resource);
	 		
	 		for($it=0; $it < $fields; $it++)
	 		{
	 			$schema[mysql_field_name($resource, $it)] = array(
	 			
	 							"type" => mysql_field_type($resource, $it),
	 							"null"	=> $this->isNull(mysql_field_flags($resource, $it))
	 					
	 			);
	 		}
	 		
	 		return $schema;
		 }
	 
	 /**
	 * Returns wether a field may or not be NULL
	 *@param string mysql flag
	 *@return bool
	 */
		 private function isNull($string)
		 {
		 	 //If we find the string not_null then is not null if not is null
		 	$array = explode(" ", $string);
		 	
		 	// We look for not_null in those
		 	foreach($array as $portion)
		 	{
		 		if($portion == NOT_NULL_FLAG)
		 		{
		 			return false;
		 		}
		 	}
		 	
		 	// If we get here, this field is null
		 	return true;
		 }
	 
	 /**
	 * Gets the list of field names of the table
	 * @returns array with field names of the table
	 */
	 
		 public function getFields()
		 {
		 	$resource = mysql_list_fields(BBDD, $this->table);
	 		$fields = mysql_num_fields($resource);
	 		
	 		for($it=0; $it < $fields; $it++)
	 		{
	 			$schema[] = mysql_field_name($resource, $it);
	 		}
	 		
	 		return $schema;
		 }
	 
	 /**
	 * Returns an array of forbidden fields to be set up manually when creating a new instance
	 * If forbiddenFields is declared we will add it here =)
	 * @return array forbidden fields to be set up manually
	 */
	 
		 public function getForbiddenFields()
		 {
		 	$forbidden = array(
						"id",
						DATE_POPULATED, 
						UPDATE_POPULATED, 
						TIMESTAMP_POPULATED
						);
						
			// If ignoredFields is set then we will add them here
		 	if(isset($this->forbiddenFields) && is_array($this->forbiddenFields))
		 	{
		 		$forbidden = array_keys(array_flip(array_merge($forbidden, $this->forbiddenFields)));
		 	}
						
			return $forbidden;
		}
	 
	 /**
	 * Returns an array of optional fields to be supplied to the constructor
	 * If ignoredFields is set, they will be added here
	 * @return array optional fields for the constructor
	 * Those that are NULL and are NOT forbidden
	 */
	 
		 public function getOptionalFields()
		 {	
		 	// To give back
		 	$optional = array();
		 	
		 	// Forbidden fields
		 	$forbidden = self::getForbiddenFields();
		 	
		 	// It will return a
		 	$schema = self::getSchema();
		 	
		 	// We will add to the optional array all those that are NULL and are NOT in $forbidden
		 	foreach($schema as $field => $info)
		 	{
		 		if($info['null'] == true && in_array($field, $forbidden) == false)
		 		{
		 			$optional[] = $field;
		 		}
		 	}
		 	
		 	// If ignoredFields is set then we will add them here
		 	if(isset($this->ignoredFields) && is_array($this->ignoredFields))
		 	{
		 		$optional = array_keys(array_flip(array_merge($optional, $this->ignoredFields)));
		 	}
		 	
		 	return $optional;
		 	
		 }
	 
	 /**
	 * Returns the  bunch of fields required by the constructor to create a new instance
	 * If $this->ignoredFields is declared, it will not add them to the required fields array
	 * @return array of required fields to provide to the constructor
	 * ($All - $optional) - $forbidden
	 */
	 
		 public function getRequiredFields()
		 {
		 	$all = self::getFields();
		 	$optional = self::getOptionalFields();
		 	$forbidden = self::getForbiddenFields();
		 	
		 	$required = array_diff($all, $optional);
		 	$required = array_diff($required, $forbidden);
		 	$required = array_keys(array_flip($required));
		 	
		 	// If ignoredFields is set then we remove them from the required array
		 	if(isset($this->ignoredFields) && is_array($this->ignoredFields))
		 	{
		 		$required = array_keys(array_flip(array_diff($required, $this->ignoredFields)));
		 	}
		 		
		 	return $required;
		 	
		 }
	 
	 /**
	 * Checks in received field exists in our table schema
	 * @returns boolean
	 */
	 
		 public function field_exists($field)
		 {
		 	if(in_array($field, self::getFields()))
		 	{
		 		return true;
		 	}
		 	
		 	return false;
		 }
		 
	// DATABASE SCHEMA FUNCTIONS END
	
	/*
	* VALIDATION FUNCTIONS
	* Validate accordding to the validateRules array
	* Generate an array of validateErrors()
	*/
	
	/**
	* Primary validation function
	* Check validation for each field declared in array validationRules[]
	*/
	
	public function validate()
	{
		// If no array declared nothing to do
		if(!isset($this->validationRules))
			return;
			
		/*
		* This validator should perform validation set up by the user
		* Each $field should satisfy $value
		*/
		
		// We need a new instance of validator
		$validator = new Validator();
		
		// We'll have to clear up the last validation if any so it can be performed several times
		self::clearValidationErrors();		
		
		// Proceed
		foreach($this->validationRules as $field => $instructions)
		{
			/*
			* $field is the field to validate
			* $instructions is an array with two indexes. (method name, throw message)
			* 1) Check that method specified works, if not, we should log an error.
			* 2) Execute the method, if false, store validateError
			*/
			
			/*
			* 1) Method exists?
			*/
			if(!method_exists($validator, $instructions[0]))
	 		{
	 			self::addValidationError("Could not validate field `$field`! The method `$instructions[0]` does not exist in class ` " . get_class($validator) . "`");
	 		} else {
	 		
		 		/*
		 		* 2) Executin' baby yeah
		 		* Loading validator error if fails
		 		*/
		 		 if($validator->$instructions[0]($this->$field) == false)
		 		 {
		 		 	self::addValidationError($instructions[1]);
		 		 }
	 		
	 		}			 
			 
		}
		
		// Cleaning up trash
		unset($validator);
	}
	
	/**
	* Adds an error to validationErrors[]
	* @param string error
	*/
	public function addValidationError($string)
	{
		$this->validationErrors[] = $string;
	}
	
	/**
	* Clears up validationErrors
	* @param string error
	*/
	private function clearValidationErrors()
	{
		$this->validationErrors = NULL;
	}
	
	/**
	* Returns wether validation is Ok or not
	* @return bool
	*/
	
	public function validationOk()
	{
		if($this->validationErrors != NULL)
		{
			return false;
		} else {
			return true;
		}
	}
	
	/**
	* Returns wether validation failed or not
	* @return bool
	*/
	
	public function validationFailed()
	{
		if($this->validationErrors == NULL)
		{
			return false;
		} else {
			return true;
		}
	}
	
	/**
	* @return array of validation errors
	* Usefull to pass it to dataErrors in the view and display it
	*/
	
	public function getValidationErrors()
	{
		return $this->validationErrors;
	}
	
	/**
	* Displays on screen all validation errors
	* Debug purposes only
	*/
	
	public function displayValidationErrors()
	{
		if(self::getValidationErrors() != NULL) 
		{	
			echo "<b>VALIDATION ERRORS</b><ul>";
			foreach($this->validationErrors as $error)
			{
				echo "<li>$error</li>";
			}
			echo "</ul>";
		} else {
			echo "<b>NO VALIDATION ERRORS</b>";
		}
	}
	
	/*
	* MODEL RELATIONSHIPS
	* Functions that allow the model to return related models if asked
	* Given a model, it will look for it in the ['className'] index of all possibleRelantionships
	*/
	
	/**
	* @param 		modelname String
	* @return		foreignKey of the relationship. Null if not declared	 	
	*/
	
	public function getRelationshipFK($requestedModel)
	{
		// First, we iterate all possible relationships
		foreach($this->possibleRelationships as $relationship)
		{
			// If this relationship is declared
			if(isset($this->$relationship))
			{
				// Inside it may be lots of relationships of this type
				foreach($this->$relationship as $local)
				{
					// If this relationship relates the requested model
					if($local['className'] == $requestedModel)
					{
						// Return the foreignKey
						return $local['foreignKey'];
					}
				}
				
			}
			
		}
		
		// If got here...it is not declared =) 
		return NULL;
	}
	
	/**
	* Gives requested related models
	* @return array of requested models related to our model or NULL
	* @param Model Requested
	* @param boolean justids
	* @param boolean justNumRows
	* @param boolean rowcount
	* @param boolean offset
	*/
	
	public function getRelated($requestedModel, 
							$justids = false, 
							$justNumRows = false, 
							$offset = NULL, 
							$rowcount = NULL)
	{
		// First, we iterate all possible relationships
		foreach($this->possibleRelationships as $relationship)
		{
			// If this relationship is declared
			if(isset($this->$relationship))
			{
				// Inside it may be lots of relationships of this type
				foreach($this->$relationship as $local)
				{
				
					// If this relationship relates the requested model
					if($local['className'] == $requestedModel)
					{
						// We attemp to load it
						$try = Core::loadModel($local['className']);
					
						// If failed loading the model we're out.
						if($try == false) 
							return NULL;
					
						// If foreignKey is empty, we are out
						if(Validator::isEmpty($local['foreignKey']))
							return NULL;
									
						/*
						* Query
						* 1) If relationship is belongsTo, the foreignKey value is in our object
						* 2) If not, the foreign key and the value should be queried to the related model
						*/
					
						/*
						* belongsTo relationship
						*/			
						if($relationship == "belongsTo")
						{
							// If the foreignKey does not exist in our object, return null.
							if(!self::field_exists($local['foreignKey']))
								return null;
							
							// If exist...we just create the object or get the id and return it
							if($justids == false)
							{
								$related[] = new $requestedModel($this->$local['foreignKey']);
							} else {
								$related[] = $this->$local['foreignKey'];
							}
							
							return $related;
						
							// (!)	No integrity check for belongsTo
							// 		So the user may know if some foreignKey is broken
							
						} else {
					
							/*
							* Other relationShip
							* We have to query the related models table with foreignkey = our object key
							*/
							
							// First we have to check that specified foreignKey exists on the related model
							$test = new $requestedModel(0);
							if(!$test->field_exists($local['foreignKey']))
								return NULL;
	
							// Everything cool. Query and return
							$related = Query::searchBy($local['foreignKey'], $this->id, $requestedModel, false, $justids, $offset, $rowcount);							
							
							// Will be NULL if no results
							return $related; 
							
						}
					}		
				}
			}
		}
		
		// If out here hasn't been any return performed
		return NULL;
	}
	
	/**
	* MODEL SEARCH-ENGINE RELATED METHODS 
	*/
	
	/**
	* @return if this model is searchable
	*/
	
	public function isSearchable()
	{
		if(isset($this->searchFields))
			return true;
			
		return false;
	}	
	
	
	 /*
	 * MISC STUFF
	 */
	 
	 /**
	 * It will refresh all of it's values
	 * Useful when having usually changing data
	 * (!) Used by the constructor
	 * @param id
	 */
	 	public function refresh()
	 	{
	 		// Our Id
	 		$id = $this->id;
	 		
	 		// Our query
	 		$result = mysql_query("SELECT * FROM `$this->table` WHERE id = '$id' ");
			
			// If no result
			if(mysql_num_rows($result)  < 1)
			{
				parent::logError("There is no row in table $this->table with ID = $id");
				
			} else {
			
				// Temp object
				$temp = mysql_fetch_object($result);
				
				// Self-asignement
				foreach($temp as $key => $value)
				{
		    			$this->$key = $value;
				}
				
			}
	 	}
	 
	 /**
	 * Displays up front a clear representation of the model's fields and values
	 * This should be used for debugging/developing only
	 */
	 
		 public function examine()
		 {
		 	foreach(self::getFields() as $key) {
		
	   			echo("<b>" . $key . "</b><br> " . $this->$key . "<br>");
	
	  		}
		 }
		 
	 /**
	 * SCAFFOLDING STUFF
	 * Provides information about scaffolding...foreign keys etc, etc xD
	 */
	 
	 /**
	 * @return which fields of the table schema might be foreign keys to be populated when scaffolding
	 */
	 
	 	public function getForeignFields()
	 	{
	 		// Fields
			$fields = array();	 		
	 		
	 		// We get the field list
	 		$schema = self::getFields();
	 		
	 		// Check if any follows the TABLENAME_id pattern. If it does, we append it to an array
	 		foreach($schema as $field)
	 		{
	 			// Segments
	 			$matches = explode("_", $field);
	 			
	 			// Amount
	 			$amount = count($matches) -1;
	 			
	 			if($amount > 0)
	 			{
	 				// Check if last part is 'id'...then it's a foreign field!
	 				if($matches[$amount] == "id")
	 				{
	 					// Now let's check if the model exists. Must be the first part of the name, capitalized.
	 					for($it = 0; $it < count($matches) -1; $it++)
	 					{
	 						$modelname = $modelname . $matches[$it];
	 					}
	 					$modelname = ucfirst($modelname);
	 					
	 					if(Core::modelExists($modelname))
	 						$fields[] = $field;
	 				}
	 			}
	 		}
	 		
	 		return $fields;
	 	}
			
}
	
?>
Return current item: ArasPhp Framework