Location: PHPKode > projects > Porte > porte-0.2.2/src/plugins/Associations.php
<?php

/**
 * Copyright (c) 2008, SARL Adaltas. All rights reserved.
 * Code licensed under the BSD License:
 * http://porte.adaltas.com/en/developer/license.html
 */

/**
 * PorteAssociations
 *
 * Add "one-to-one", "one-to-many" and "many-to-many" associations.
 * 
 * Each association has the freedom to use the storing arrays "attributes" and "associations"
 * as they wish.
 * 		many-to-many with list: 
 * 			Use both arrays.
 * 			The "attribute" part is a string list of foreign keys (integers) and
 * 			is only use on load. Since by nature it can only store save records, it can not be use when
 * 			adding or setting non saved records.
 * 			The "association" part is the one we work on. If does not exist, we initialize it from the 
 * 			"attribute" one.
 * 			On load, "attributes" represent the list of associations, later, the "associations" represent
 * 			those associations and is only made of a PorteIterator of records.
 * 
 * @package    Porte
 * @subpackage plugin
 * @author     David Worms info(at)adaltas.com
 * @copyright  2008 Adaltas
 */
class PorteAssociations{
	
	/**
	 * Enrich a property model with associations.
	 * Listen to the "model_property_before" event.
	 * 
	 * @return mixed Boolean true if property is an association
	 * @param PorteModels $models
	 * @param string $type
	 * @param string $property
	 */
	public static function _modelPropertyBefore(PorteModels $models,$type,$property){
		$treated = false;
		$config = $models->{$type}['properties'][$property];
		if(!empty($config['belongs_to'])){
			self::_modelPropertyBefore_belongsTo($models,$type,$property);
			$treated = true;
		}else if(!empty($config['has_many'])){
			self::_modelPropertyBefore_HasMany($models,$type,$property);
			$treated = true;
		}else if(!empty($config['has_one'])){
			PorteOneToOneHasOne::setProperty($models,$type,$property);
			$treated = true;
		}
		if($treated){
			/*
			$args = array($properties,$property,&$config);
			$action = 'associations_property_after';
			$return = PorteEvents::call($properties->model->type,$action,$args);
			return !is_null($return)?$return:true;
			*/
			return true;
		}
	}
	
	/**
	 * Enrich a property model if a "belongs_to" keywork is defined.
	 * 
	 * @return null
	 * @param PorteModels $models
	 * @param string $type
	 * @param string $property
	 */
	public static function _modelPropertyBefore_belongsTo(PorteModels $models,$type,$property){
		$model = $models->{$type};
		$config = $model['properties'][$property];
		if(empty($config['field'])) $config['field'] = $property;
		$relatedModel = null;
		$config['type'] = 'int';
		$config['foreign_key'] = true;
		$config['lazy'] = true;
		if(is_string($config['belongs_to'])){
			$config['belongs_to'] = array('type'=>$config['belongs_to']);
		}else if($config['belongs_to']===true){
			$config['belongs_to'] = array();
		}else if($config['belongs_to']===false){
			return;
		}else if(!is_array($config['belongs_to'])){
			throw new PorteException('Key "belongs_to" is expected to be a string, an array or a boolean value');
		}
		// Take care of type
		if(!isset($config['belongs_to']['type'])){
			if(isset($config['belongs_to']['class'])&&$config['belongs_to']['class']!='PorteRecord'){
				$relatedModel = $config['belongs_to']['class'];
				if(!class_exists($relatedModel)) throw new PorteException('Error setting belongs_to property "'.$property.'" of type "'.$model['type'].'", class not found: "'.$relatedModel.'"');
				$relatedModel = $models->get($relatedModel);
				$config['belongs_to']['type'] = $relatedModel['type'];
			}else{
				$config['belongs_to']['type'] = PorteUtils::underscore($property);
			}
		}
		// Deal with model
		if(!$relatedModel){
			$relatedModel = $models->get($config['belongs_to']['type']);
		}
		// Deal with class
		if(!isset($config['belongs_to']['class'])){
			$config['belongs_to']['class'] = $relatedModel['class'];
		}
		$relatedProperty = null;
		if(empty($config['belongs_to']['property'])){
			// has_one
			$relatedProperty = $model['type'];
			if(isset($relatedModel['properties'][$relatedProperty])){
				$config['belongs_to']['property'] = $relatedProperty;
			}else{
				// has_many
				$relatedProperty = PorteUtils::toPlural($relatedProperty);
				if(isset($relatedModel['properties'][$relatedProperty])){
					$config['belongs_to']['property'] = $relatedProperty;
				}else{
					$config['belongs_to']['property'] = false;
				}
			}
		}else if(is_string($config['belongs_to']['property'])){
			$relatedProperty = $config['belongs_to']['property'];
		}else{
			throw new PorteException('Invalid property "'.$config['belongs_to']['property'].'"');
		}
		//if(isset($relatedModel['properties'][$relatedProperty])){
			$models->{$type}['properties'][$property] = $config;
		//}
		// Deals with methods
		$camelizedProperty = PorteUtils::camelize($property);
		if(isset($relatedModel['properties'][$relatedProperty]['has_one'])){
			PorteModel::addMethod($models,$type,'get'.$camelizedProperty,$property,array('PorteOneToOneBelongsTo','getRecord'));
			PorteModel::addMethod($models,$type,'set'.$camelizedProperty,$property,array('PorteOneToOneBelongsTo','setRecord'));
			PorteModel::addMethod($models,$type,'delete'.$camelizedProperty,$property,array('PorteOneToOneBelongsTo','deleteRecord'));
		}else{
			PorteModel::addMethod($models,$type,'get'.$camelizedProperty,$property,array('PorteOneToManyBelongsTo','getRecord'));
			PorteModel::addMethod($models,$type,'set'.$camelizedProperty,$property,array('PorteOneToManyBelongsTo','setRecord'));
		}
	}
	
	/**
	 * Enrich a property model if a "has_many" keywork is defined.
	 * 
	 * @return null
	 * @param PorteModels $models
	 * @param string $type
	 * @param string $property
	 */
	public static function _modelPropertyBefore_HasMany(PorteModels $models,$type,$property){
		$model = $models->{$type};
		$config = $model['properties'][$property];
		$relatedModel = null;
		$config['lazy'] = true;
		// Take care of has_many (key or value)
		if(is_string($config['has_many']))
			$config['has_many'] = array('type'=>PorteUtils::toSingular($config['has_many']));
		if(!is_array($config['has_many']))
			$config['has_many'] = array();
		// Deal with type
		if(empty($config['has_many']['type'])){
			if(!empty($config['has_many']['class'])&&$config['has_many']['class']!='PorteRecord'){
				$relatedModel = $config['has_many']['class'];
				if(!class_exists($relatedModel)){
					throw new PorteException('Attempt to set an association to an inexistant class \''.$relatedModel->class.'\'.');
				}
				$relatedModel = $models->get(new $relatedModel());
				$config['has_many']['type'] = $relatedModel['type'];
			}else if(!empty($config['has_many']['table'])){ 
				$config['has_many']['type'] = PorteUtils::underscore(PorteUtils::toSingular($config['has_many']['table']));
			}else{
				$config['has_many']['type'] = PorteUtils::underscore(PorteUtils::toSingular($property));
			}
		}
		// Deal with model
		if(!$relatedModel){
			if($config['has_many']['type']==$model['type']){
				$relatedModel = &$model;
			}else{
				$relatedModel = $models->get($config['has_many']['type']);
			}
		}
		// Deal with class
		if(!isset($config['has_many']['class'])){
			$config['has_many']['class'] = $relatedModel['class'];
		}
		if(empty($config['has_many']['property'])){
			if(isset($relatedModel['properties'][$model['type']])){
				$config['has_many']['property'] = $model['type'];
			}else if(isset($relatedModel['properties'][PorteUtils::toPlural($model['type'])])){
				$config['has_many']['property'] = PorteUtils::toPlural($model['type']);
			}else{
				throw new PorteException('Could not determine associated property: "'.$model['type'].'" or "'.PorteUtils::toPlural($model['type']).'" on type "'.$relatedModel['type'].'"');
			}
		}
		$relatedProperty = $config['has_many']['property'];
		// Association apply between records of same type
		if($relatedModel['type']==$model['type']){
			$relatedConfig = $config;
		// Association apply between different types of records
		}else{
			if(!isset($relatedModel['properties'][$relatedProperty])){
				throw new PorteException('Property "'.$relatedProperty.'" not defined in model of type "'.$relatedModel['type'].'"');
			}
			$relatedConfig = $relatedModel['properties'][$relatedProperty];
		}
		// Deal with the transient side of a one-to-many association
		if(!empty($relatedConfig['belongs_to'])){
			$config['transient'] = true;
			$models->{$type}['properties'][$property] = $config;
			/*
			if(!isset($relatedModel['properties'][$relatedProperty])){
				$relatedModel['properties']->get($relatedProperty);
			}
			*/
			$camelizedProperty = PorteUtils::camelize($property);
			PorteModel::addMethod($models,$type,'add'.PorteUtils::camelize(PorteUtils::toSingular($property)),$property,array('PorteOneToManyHasMany','addRecord'));
			PorteModel::addMethod($models,$type,'add'.$camelizedProperty,$property,array('PorteOneToManyHasMany','addRecords'));
			PorteModel::addMethod($models,$type,'get'.$camelizedProperty,$property,array('PorteOneToManyHasMany','getRecords'));
			PorteModel::addMethod($models,$type,'delete'.$camelizedProperty,$property,array('PorteOneToManyHasMany','deleteRecords'));
			PorteModel::addMethod($models,$type,'count'.$camelizedProperty,$property,array('PorteOneToManyHasMany','countRecords'));
		// Deal with many-to-many associations
		}else if(!empty($relatedConfig['has_many'])){
			if(!empty($config['transient'])&&!empty($relatedConfig['transient'])){
			//if both side define a transient property, throw exception
				throw new PorteException('Invalid Model: two related properties can not both be set as transient'); 
			}else if((!empty($config['transient'])||!empty($relatedConfig['transient']))&&!isset($relatedConfig['has_many']['join'])){
			// if one side define a transient property, list strategy
				if(empty($config['transient'])){
					if(empty($config['type'])) $config['type'] = 'text';
					if(empty($config['field'])) $config['field'] = $property;
				}
				$models->{$type}['properties'][$property] = $config;
				//$model['properties']->$property = $config;
				/*
				if(!isset($relatedModel['properties']->$relatedProperty)){
					$relatedModel['properties']->get($relatedProperty);
				}
				*/
				$camelizedProperty = PorteUtils::camelize($property);
				$singularCamelizedProperty = PorteUtils::toSingular($camelizedProperty);
				PorteModel::addMethod($models,$type,'add'.$singularCamelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'addRecord'));
				PorteModel::addMethod($models,$type,'add'.$singularCamelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'addRecord'));
				PorteModel::addMethod($models,$type,'add'.$camelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'addRecords'));
				PorteModel::addMethod($models,$type,'get'.$camelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'getRecords'));
				PorteModel::addMethod($models,$type,'set'.$camelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'setRecords'));
				PorteModel::addMethod($models,$type,'delete'.$singularCamelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'deleteRecord'));
				PorteModel::addMethod($models,$type,'delete'.$camelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'deleteRecords'));
				PorteModel::addMethod($models,$type,'count'.$camelizedProperty,$property,array('PorteManyToManyList'.(empty($config['transient'])?'Persisted':'Transient'),'countRecords'));
			}else{
			// if none side define a transient, join strategy
				$config['transient'] = true;
				if(!isset($config['has_many']['join'])||!is_array($config['has_many']['join'])){
					$config['has_many']['join'] = array();
				}
				if(empty($config['has_many']['join']['database'])){
					$config['has_many']['join']['database'] = ($model['database']<$relatedModel['database'])?$model['database']:$relatedModel['database'];
				}
				if(empty($config['has_many']['join']['table'])){
					$config['has_many']['join']['table'] = ($model['table']<$relatedModel['table'])?$model['table'].'_'.$relatedModel['table']:$relatedModel['table'].'_'.$model['table'];
				}
				if(empty($config['has_many']['join']['field'])){
					if($relatedModel['type']==$model['type']){
						/*
						if(!isset($relatedModel['properties'][$relatedProperty])){
							$config['has_many']['join']['field'] = array($model['type'].'_left',$model['type'].'_right');
						}
						*/
						$config['has_many']['join']['field'] = array($model['type'].'_left',$model['type'].'_right');
					}else{
						$config['has_many']['join']['field'] = $model['type'];
					}
				}
				$pkConfig = $model['properties'][$model['primary_key']];
				$config['has_many']['join']['type'] = $pkConfig['type'];
				$config['has_many']['join']['length'] = $pkConfig['length'];
				if(empty($config['has_many']['join']['engine'])) $config['has_many']['join']['engine'] = $model['engine'];
				if(empty($config['has_many']['join']['encoding'])) $config['has_many']['join']['encoding'] = $model['encoding'];
				//print_r($config);
				$models->{$type}['properties'][$property] = $config;
				//$properties->$property = $config;
				/*
				if(!isset($relatedModel->properties->$relatedProperty)){
					$relatedModel->properties->get($relatedProperty);
				}
				*/
				// Deals with methods
				$camelizedProperty = PorteUtils::camelize($property);
				$singularCamelizedProperty = PorteUtils::toSingular($camelizedProperty);
				PorteModel::addMethod($models,$type,'set'.$singularCamelizedProperty,$property,array('PorteManyToManyJoin','setRecord'));
				PorteModel::addMethod($models,$type,'set'.$camelizedProperty,$property,array('PorteManyToManyJoin','setRecords'));
				PorteModel::addMethod($models,$type,'add'.$singularCamelizedProperty,$property,array('PorteManyToManyJoin','addRecord'));
				PorteModel::addMethod($models,$type,'add'.$camelizedProperty,$property,array('PorteManyToManyJoin','addRecords'));
				PorteModel::addMethod($models,$type,'get'.$camelizedProperty,$property,array('PorteManyToManyJoin','getRecords'));
				PorteModel::addMethod($models,$type,'delete'.$singularCamelizedProperty,$property,array('PorteManyToManyJoin','deleteRecord'));
				PorteModel::addMethod($models,$type,'delete'.$camelizedProperty,$property,array('PorteManyToManyJoin','deleteRecords'));
				PorteModel::addMethod($models,$type,'count'.$camelizedProperty,$property,array('PorteManyToManyJoin','countRecords'));
			}
		}
	}
	
	/**
	 * Create the join table in many-to-many associations
	 * Listen to the "table_update_after" event.
	 * 
	 * @return null
	 * @param PorteTable $table
	 * @param array $existingField
	 */
	public static function _tableUpdateAfter(PorteTable $table,array $existingFields){
		$properties = $table->porte->models->{$table->type}['properties'];
    	foreach($properties as $property=>$config){
    		if(!isset($config['has_many'])) continue;
    		// Create join table in used in many to many association
    		if(isset($config['has_many']['join'])){
    			$joinDb = $config['has_many']['join']['database'];
    			$joinTable = $config['has_many']['join']['table'];
				$primaryKey = 'id';
				if(!$table->porte->connection->tableExists($joinDb,$joinTable)){
					$table->porte->connection->tableCreate(array('database'=>$joinDb,'table'=>$joinTable,'primary_key'=>$primaryKey,'encoding'=>$config['has_many']['join']['encoding'],'engine'=>$config['has_many']['join']['engine']));
				}
				// Clean the join table with unused fields
				$query = 'SHOW COLUMNS FROM `'.$joinDb.'`.`'.$joinTable.'`;';
				$existingFields = array();
				$statement = $table->porte->query($query);
				if(is_array($config['has_many']['join']['field'])){
					$assocFields = $config['has_many']['join']['field'];
				}else{
					$assocConfig = $table->porte->models->{$config['has_many']['type']}['properties'][$config['has_many']['property']];
					$assocFields = array($config['has_many']['join']['field'],$assocConfig['has_many']['join']['field']);
				}
				while($row = $statement->fetch()){
					$field = $row['Field'];
					if(!array_key_exists($field,$assocFields)&&$field!=$primaryKey){
						// if a field is not in our object, drop it
						$query = 'ALTER TABLE `'.$joinDb.'`.`'.$joinTable.'` DROP `'.$field.'`;';
						$table->porte->exec($query);
						//throw new PorteException('Error while atering table: '.$query);
					}else{
						$existingFields[] = $field;
					}
				}
				$statement->closeCursor();
				foreach($assocFields as $field){
					if(!in_array($field,$existingFields)){
						$query = array();
						$query['Base'] = 'ALTER TABLE `'.$joinDb.'`.`'.$joinTable.'` ADD `'.$field.'` ';
						$query['Type'] = 'int';
						$query['Null'] = 'NOT NULL';
						$query = implode(' ',array_values($query));
						$table->porte->exec($query);
					}
				}
    		}
    	}
	}
	
	/**
	 * Update record associations before the record is saved.
	 * Listen to the "record_save_before" event.
	 * 
	 * @return null
	 * @param PorteRecord $record
	 * @param array $circular
	 */
	public static function _recordSaveBefore(PorteRecord $record,array $circular){
		$model = $record->porte->models->{$record->type};
		foreach($record->associations as $property=>$assocRecords){
			if(isset($model['properties'][$property])){
				$config = $model['properties'][$property];
				if(!empty($config['has_many'])){
					if(isset($config['has_many']['join'])){
						// nothing to do, we deal with the join table later
					}else if(!empty($config['transient'])){
						// nothing to do, we deal with transient type later
					}else{
						// many-to-many list persisted or one-to-many belongs_to
						$existingFks = $assocRecords->save($circular)->getIdentifier();
						$record->attributes[$property] = implode(',',$existingFks);
					}
				}else if(!empty($config['belongs_to'])){
					if(is_null($assocRecords)){
						$record->attributes[$property] = null;
					}else{
						$assocRecords->save($circular);
						$record->attributes[$property] = $assocRecords->getIdentifier();
					}
				}
			}
		}
	}
	
	/**
	 * Update record associations after the record is saved.
	 * Listen to the "record_save_after" event.
	 * It loops through each associations, and then through each values in the associations. Those 
	 * values may be int primary keys or instances of PorteRecord.
	 * 
	 * @return null
	 * @param PorteRecord $record
	 * @param array $circular
	 */
	public static function _recordSaveAfter(PorteRecord $record,array $circular){
		$model = $record->porte->models->{$record->type};
		$properties = array_keys($record->associations);
		while(list(,$property) = each($properties)){
			if(isset($model['properties'][$property])){
				$config = $model['properties'][$property];
				if(isset($config['has_many'])){
					$assocConfig = $record->porte->models->{$config['has_many']['type']}['properties'][$config['has_many']['property']];
					if(isset($config['has_many']['join'])){
						PorteManyToManyJoin::save($property,$record,$circular);
					}else if(isset($assocConfig['belongs_to'])){
						// nothing yet
						PorteManyToManyListTransient::save($property,$record,$circular);
					}else if(!empty($config['transient'])){
						PorteManyToManyListTransient::save($property,$record,$circular);
					}
				}else if(isset($config['has_one'])){
					if(isset($record->associations[$property]))
						$record->associations[$property]->save($circular);
				//}else if(array_key_exists('belongs_to',$config)){
				}else if(isset($config['belongs_to'])){
					$assocModel = $record->porte->models->{$config['belongs_to']['type']};
					// Condition because an association can be defined from only one side
					if(isset($assocModel['properties'][$config['belongs_to']['property']])){
						$assocConfig = $assocModel['properties'][$config['belongs_to']['property']];
						if(array_key_exists('has_one',$assocConfig)){
							if(!is_null($record->associations[$property])) $record->associations[$property]->save($circular);
						}
					}
				}
			}
		}
	}
	
	/**
	 * Update record associations before the record is deleted.
	 * Listen to the "record_delete_before" event.
	 * 
	 * @return null
	 * @param PorteRecord $record
	 */
	public static function _recordDeleteBefore(PorteRecord $record){
		$model = $record->porte->models->{$record->type};
		while(list($property,$config) = each($model['properties'])){
		//foreach($model['properties'] as $property=>$config){
			if(isset($config['has_many'])){
				if(!empty($config['has_many']['join'])){
					$primaryKeyValue = $record->getIdentifier();
					$joinDb = $config['has_many']['join']['database'];
					$joinTable = $config['has_many']['join']['table'];
					$joinField = $config['has_many']['join']['field'];
					if(is_array($config['has_many']['join']['field'])){
						$query = 'DELETE `'.$joinDb.'`.`'.$joinTable.'`.* FROM `'.$joinDb.'`.`'.$joinTable.'` WHERE `'.$joinDb.'`.`'.$joinTable.'`.`'.$joinField[0].'` = \''.$primaryKeyValue.'\' OR `'.$joinDb.'`.`'.$joinTable.'`.`'.$joinField[1].'` = \''.$primaryKeyValue.'\'';
					}else{
						$query = 'DELETE `'.$joinDb.'`.`'.$joinTable.'`.* FROM `'.$joinDb.'`.`'.$joinTable.'` WHERE `'.$joinDb.'`.`'.$joinTable.'`.`'.$joinField.'` = \''.$primaryKeyValue.'\'';
					}
					$record->porte->exec($query);
					//throw new PorteException('Error while deleting associated references: '.$query);
				}else if(!empty($config['transient'])){
				// many-to-many with list strategy
					$primaryKeyValue = $record->getIdentifier();
					$assocModel = $record->porte->models->{$config['has_many']['type']};
					$assocConfig = $assocModel['properties'][$config['has_many']['property']];
					$assocRecords = $record->porte->tables->{$assocModel['type']}->find('FIND_IN_SET( :pk ,`'.$assocConfig['field'].'`)',array('pk'=>$primaryKeyValue));
					foreach($assocRecords as $assocRecord){
						$assocRecord->attributes[$config['has_many']['property']] = implode(',',array_diff(explode(',',$assocRecord->attributes[$config['has_many']['property']]),array($primaryKeyValue)));
						$assocRecord->save();
					}
					$assocRecords->rewind();
				}
			}else if(isset($config['belongs_to'])){
			// one-to-many on one side & one-to-one
				if(isset($record->associations[$property])){
					$assocRecord = $record->associations[$property];
					$assocModel = $record->porte->models->{$config['belongs_to']['type']};
					$assocConfig = $assocModel['properties'][$config['belongs_to']['property']];
					//if(array_key_exists('has_one',$assocConfig)){
						$assocRecord->associations[$config['belongs_to']['property']] = null;
					//}
				}
			}else if(isset($config['has_one'])){
			// one-to-one on transient side
				if(isset($record->associations[$property])){
					$assocRecord = $record->associations[$property];
					//$assocModel = $record->porte->models->get($config['has_one']['type']);
					//$assocConfig = $assocModel->properties->$config['has_one']['property'];
					//if(array_key_exists('has_one',$assocConfig)){
					$assocRecord->associations[$config['has_one']['property']] = null;
					//}
				}
			}
		}
		//reset($model['properties']);
	}
	
	/**
	 * Create a new PorteRecord instance.
	 * 
	 * @return 
	 * @param PorteRecord $record
	 * @param array $config
	 * @param string $type
	 * @param mixed $arg
	 */
	public static function makeRecord(PorteRecord $record,array $config,$type,$arg){
		// if argument is null and if the field accept null value, ok
		if(is_null($arg)){
			if(!empty($config['not_null']))
				throw new PorteException('Invalid Arguments: argument is null');
			$arg = null;
		// if argument is null and if the field does not accept null value, not ok	
		}else if(is_object($arg)){
			if(!$config[$type]['class']) throw new Exception();
			if(!$arg instanceof $config[$type]['class'])
				throw new InvalidArgumentException('Provided argument not an instance of "'.$config[$type]['class'].'", "'.get_class($arg).'" instead');
			if(!isset($arg->porte)) $arg->setPorte($record->porte);
		}else if((int)$arg>0){
			$arg = $record->porte->tables->get($config[$type]['type'])->load($arg);
		}else if(is_array($arg)){
			if(empty($arg)){
				$arg = null;
			}else{
				$arg = $record->porte->tables->get($config[$type]['type'])->load($arg);
			}
		}else{
			$arg = null;
		}
		return $arg;
	}
	
}

PorteEvents::connect('model_property_before',array('PorteAssociations','_modelPropertyBefore'));
PorteEvents::connect('table_update_after',array('PorteAssociations','_tableUpdateAfter'));
PorteEvents::connect('record_save_before',array('PorteAssociations','_recordSaveBefore'));
PorteEvents::connect('record_save_after',array('PorteAssociations','_recordSaveAfter'));
PorteEvents::connect('record_delete_before',array('PorteAssociations','_recordDeleteBefore'));

?>
Return current item: Porte