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

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

/**
 * PorteManyToManyListTransient
 * 
 * @package    Porte
 * @subpackage plugin
 * @author     David Worms info(at)adaltas.com
 * @copyright  2008 Adaltas
 */
class PorteManyToManyListTransient{
	
	/**
	 * Note, except the last two lines (the class name which could be provided as argument)
	 * code is identical to PorteManyToManyListPersisted::setRecords
	 * 
	 * @return PorteRecord Current record provided as 2nd argument
	 * @param $property String Name of the property being accessed
	 * @param $record PorteRecord Current record
	 * @param $assocRecord mixed (string,int,array,PorteRecord) Record to be associated with the current record
	 */
	public static function setRecords($property,$record,$assocRecords){
		/*
		if(!empty($options['convert'])&&$record->attributes[$property]==$assocRecords&&!isset($record->associations[$property])){
			return $record;
		}
		*/
		$config = $record->porte->models->{$record->type}['properties'][$property];
		$assocRecords = new PorteIterator($record->porte->tables->{$config['has_many']['type']},$assocRecords);
		// Extract all the primary keys to be set
		$primaryKeysToSet = $assocRecords->getIdentifier();
		// we delete all the associations which are not provided as arguments
		$existingAssocRecords = $record->{'get'.PorteUtils::camelize($property)}();
		$toDeleteAssocRecords = array();
		$toSetAssocRecords = array();
		foreach($existingAssocRecords as $existingAssocRecord){
			if(!$existingAssocRecord->isNew()&&!in_array($existingAssocRecord->getIdentifier(),$primaryKeysToSet)){
				$toDeleteAssocRecords[] = $existingAssocRecord;
			}else if($existingAssocRecord->isNew()){
				$isPresent = false;
				foreach($assocRecords as $assocRecord){
					if($assocRecord===$existingAssocRecord){
						$isPresent = true;
					}
				}
				if(!$isPresent){
					$toDeleteAssocRecords[] = $existingAssocRecord;
				}
			}
		}
		$existingAssocRecords->rewind();
		PorteManyToManyListTransient::deleteRecords($property,$record,$toDeleteAssocRecords);
		return PorteManyToManyListTransient::addRecords($property,$record,$assocRecords);
	}
	
	/**
	 * many-to-many without a join table from the transient side
	 * 
	 * @return PorteIterator Iterator containing the associated records
	 * @param $property String Name of the property being accessed
	 * @param $record PorteRecord
	 * @param $assocRecord PorteIterator Record to be associated with the current record
	 */
	public static function addRecord($property,$record,$assocRecord){
		return PorteManyToManyListPersisted::addRecord($property,$record,$assocRecord);
	}
	
	/**
	 * many-to-many without a join table from the transient side
	 * 
	 * @return PorteIterator Iterator containing the associated records
	 * @param $property String Name of the property being accessed
	 * @param $record PorteRecord
	 * @param $assocRecords PorteIterator Records to be associated with the current record
	 */
	public static function addRecords($property,$record,$assocRecords){
		$model = $record->porte->models->{$record->type};
		$config = $model['properties'][$property];
		$assocTable = $record->porte->tables->{$config['has_many']['type']};
		$assocRecords = new PorteIterator($assocTable,$assocRecords);
		if(!count($assocRecords)) return $record;
		if(!isset($record->associations[$property])){
			$record->associations[$property] = new PorteIterator(
				$assocTable,
				// Preload PorteIterator with primary_keys
				(isset($record->attributes[$property]))?explode(',',$record->attributes[$property]):array()
			);
		}
		foreach($assocRecords as $assocRecord){
			if(empty($assocRecord)||(!is_object($assocRecord)&&!is_int($assocRecord)&&!ctype_digit($assocRecord))){
				// make sure the array contains valid records
				continue;
			}else{
				$skip = false;
				// Make sure we do not create twice the same association
				foreach($record->associations[$property] as $registeredRecord){
					if($assocRecord instanceof PorteRecord){
						if($assocRecord->isNew() && $registeredRecord===$assocRecord){
							$skip = true;
							break;
						}else if(!$assocRecord->isNew()){
							if($registeredRecord instanceof PorteRecord && $registeredRecord->getIdentifier() == $assocRecord->getIdentifier()){
								$skip = true;
								break;
							}else if((is_int($registeredRecord)||is_string($registeredRecord)) && $registeredRecord == $assocRecord->getIdentifier()){
								$skip = true;
								break;
							}
						}
					}else if($registeredRecord instanceof PorteRecord && $registeredRecord->getIdentifier() == $assocRecord){
						$skip = true;
						break;
					}if($registeredRecord === $assocRecord){
						$skip = true;
						break;
					}
				}
				if(!$skip){
					$record->associations[$property]->array[] = $assocRecord;
					$assocProperty =  $model['properties'][$property]['has_many']['property'];
					PorteManyToManyListPersisted::addRecord($assocProperty,$assocRecord,$record);
				}
			}
		}
		return $record;
	}
	
	/**
	 * many-to-many without a join table from the transient side
	 * 
	 * @return PorteIterator Iterator containing the associated records
	 * @param $property String Name of the property being accessed
	 * @param $record PorteRecord
	 * @param $options array[optional] Optional array
	 */
	public static function getRecords($property,$record,$options=array()){
		$model = $record->porte->models->{$record->type};
		$config = $model['properties'][$property];
		//$config = $record->model->properties->$property;
		$assocModel = $record->porte->models->{$config['has_many']['type']};
		$assocTable = $record->porte->tables->{$assocModel['type']};
		if(isset($record->associations[$property])){
			// if record store association, it means we already look for relations in database
			$assocRecords = $record->associations[$property];
			foreach($assocRecords as $k=>$value){
				if(is_int($value)||ctype_digit($value)){
					$assocRecords->array[$k] = 
						$assocTable->load($value);
				}
			}
			$assocRecords->rewind();
			return $assocRecords;
		}else if($record->isNew()){
			// if record does not have a primary key, there is no need to find relations in database
			return $record->associations[$property] = new PorteIterator($assocTable,array());
		}else{
			$assocConfig = $assocModel['properties'][$config['has_many']['property']];
			// todo: make sure this does not go one step up
			$fks = isset($record->attributes[$property])?explode(',',$record->attributes[$property]):array();
			$primaryKey =  $model['primary_key'];
			$primaryKeyValue =  $record->getIdentifier();
			$options = array_merge(array(
				'select'=>'`'.$assocModel['database'].'`.`'.$assocModel['table'].'`.*',
				'from'=>'`'.$assocModel['database'].'`.`'.$assocModel['table'].'`,`'.$model['database'].'`.`'.$model['table'].'`',
				'where'=>'1 ',
			),$options);
			$options['where'] .= ' AND `'.$model['database'].'`.`'.$model['table'].'`.`'.$primaryKey.'` = \''.$primaryKeyValue.'\' AND FIND_IN_SET(`'.$model['database'].'`.`'.$model['table'].'`.`'.$primaryKey.'`, `'.$assocModel['database'].'`.`'.$assocModel['table'].'`.`'.$assocConfig['field'].'`)';
			return $record->associations[$property] = $assocTable->find($options);
		}
	}
	
	public static function deleteRecord($property,$record,$assocRecord=array()){
		self::deleteRecords($property,$record,$assocRecord);
	}
	
	/**
	 * Since we are deleting from the transient side, we need to load each
	 * associations and call their remove method. Then, when we call the save
	 * method, the assoc will themselves be saved and the assoc removed at the 
	 * same time.
	 * 
	 * When deleting on a new transient record, there is no need to load assoc
	 * since they can't reference a new saved record.
	 * 
	 * @return 
	 * @param $property Object
	 * @param $record Object
	 * @param $assocRecords Object
	 */
	public static function deleteRecords($property,$record,$assocRecords){
		// we loop through each existing assoc and if not provided as argument,
		// call the delete method which also save the assoc /* in set ? then we add the new assoc */
		$config = $record->porte->models->{$record->type}['properties'][$property];
		$assocRecords = new PorteIterator($record->porte->tables->get($config['has_many']['type']),$assocRecords);
		$assocTable = $record->porte->tables->get($config['has_many']['type']);
		$assocProperty = $config['has_many']['property'];
		$assocRecordIds = array();
		foreach($assocRecords as $assocRecord){
			PorteManyToManyListPersisted::deleteRecords($assocProperty,$assocRecord,$record);
			if(!$assocRecord->isNew()) $assocRecordIds[] = $assocRecord->getIdentifier();
		}
		if(!array_key_exists($property,$record->associations)){
			PorteManyToManyListTransient::getRecords($property,$record);
		}
		
		/*
		$assocRecords = $record->associations[$property];
		while($assocRecord = $assocRecords->current()){
			if(
				(!$assocRecord->isNew()&&in_array($assocRecord->getIdentifier(),$assocRecordIds)) ||
				($assocRecord->isNew()&&in_array($assocRecord,$assocRecords->array))
			){
				if(!$assocRecord->isNew()){
					if(!isset($assocRecords->arrayToDelete)) $assocRecords->arrayToDelete = array();
					$assocRecords->arrayToDelete[] = $assocRecord;
				}
				$assocRecords->shift();
			}else{
				$assocRecords->next();
			}
		}
		$assocRecords->rewind();
		*/
		$hum = $record->associations[$property]->array;
		foreach($hum as $key=>$assoc){
			if(
				(!$assoc->isNew()&&in_array($assoc->getIdentifier(),$assocRecordIds)) ||
				($assoc->isNew()&&in_array($assoc,$assocRecords->array))
			){
				if(!$assoc->isNew()){
					if(!isset($record->associations[$property]->arrayToDelete)) $record->associations[$property]->arrayToDelete = array();
					$record->associations[$property]->arrayToDelete[] = $assoc;
				}
				unset($hum[$key]);
			}
		}
		$record->associations[$property]->array = array_values($hum);
		$record->associations[$property]->rewind();
		return $record;
	}
	
	public static function save($property,$record,&$circular){
		//$config = $record->porte->models->{$record->type}['properties'][$property];
		$assocRecords = $record->associations[$property];
		foreach($assocRecords as $assocRecord){
			$assocRecord->save($circular);
		}
		$assocRecords->rewind();
		if(isset($assocRecords->arrayToDelete)){
			foreach($assocRecords->arrayToDelete as $assocRecord){
				$assocRecord->save($circular);
			}
		}
	}
	
	public static function countRecords($property,$record,$options=array()){
		$model = $record->porte->models->{$record->type};
		$assocModel = $record->porte->models->{$model['properties'][$property]['has_many']['type']};
		$assocPropertyModel = $assocModel['properties'][$model['properties'][$property]['has_many']['property']];
		//$assocTable = $record->porte->tables->{$assocModel['type']};
		$options = array_merge(array(
			'select'=>' count(*) ',
			'from'=>'`'.$assocModel['database'].'`.`'.$assocModel['table'].'`',
			'where'=>'1',
		),$options);
		$options['where'] .= ' AND FIND_IN_SET(\''.$record->getIdentifier().'\', `'.$assocModel['database'].'`.`'.$assocModel['table'].'`.`'.$assocPropertyModel['field'].'`)';
		// todo: why are we not calling find without execute?
		return intval($record->porte->query($record->table->find(array_merge($options,array('return_sql'=>true,'limit'=>null))))->fetchColumn());
	}
	
}

?>
Return current item: Porte