Location: PHPKode > projects > Porte > porte-0.2.2/src/plugins/associations/ManyToManyListPersisted.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
 * This class implement a many-to-many strategy with a column storing list of foreign keys
 * 
 * @package    Porte
 * @subpackage plugin
 * @author     David Worms info(at)adaltas.com
 * @copyright  2008 Adaltas
 */
class PorteManyToManyListPersisted{

	/**
	 * Note, except the last two lines (the class name which could be provided as argument)
	 * code is identical to PorteManyToManyListTransient::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,$options=array()){
		if(!empty($options['convert'])&&array_key_exists($property,$record->attributes)&&$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();
		if(count($toDeleteAssocRecords))
		PorteManyToManyListPersisted::deleteRecords($property,$record,$toDeleteAssocRecords);
		return PorteManyToManyListPersisted::addRecords($property,$record,$assocRecords);
	}
	
	/**
	 * many-to-many with a column storing list of foreign keys
	 * 
	 * @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 addRecord($property,$record,$assocRecord){
		return self::addRecords($property,$record,$assocRecord);
	}
	
	/**
	 * many-to-many with a join table
	 * 
	 * @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){
		$config = $record->porte->models->{$record->type}['properties'][$property];
		$assocTable = $record->porte->tables->{$config['has_many']['type']};
		$assocRecords = new PorteIterator($assocTable,$assocRecords);
		if(!count($assocRecords)) return $record;
		if(!array_key_exists($property,$record->associations)&&!$record->isNew()) $record->load($record->getIdentifier(),array('force'=>true));
		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){
			$skip = false;
			// Make sure we do not create twice the same association
			foreach($record->associations[$property] as $registeredRecord){
					if($assocRecord->isNew() && $registeredRecord===$assocRecord){
						$skip = true;
						break;
					}else if(!$assocRecord->isNew()&&$registeredRecord->getIdentifier() == $assocRecord->getIdentifier()){
						$skip = true;
						break;
					}
			}
			$record->associations[$property]->rewind();
			if(!$skip){
				/** todo: following code in an attempt to update association reference
				if($assocRecord instanceof PorteRecord){
					$config = $record->model->properties->$property;
					$assocModel = $record->porte->models->get($config['has_many']['type']);
					$assocProperty = $config['has_many']['property'];
					if(!isset($assocRecord->associations[$assocProperty]))
						$assocRecord->associations[$assocProperty] = 
							new PorteIterator($assocModel,
								isset($assocRecord->attributes[$assocProperty])?$assocRecord->attributes[$assocProperty]:array());
					$assocRecord->associations[$assocProperty]->array[] = $record;
				}
				*/
				$record->associations[$property]->array[] = $assocRecord;
			}
		}
		return $record;
	}
	
	/**
	 * many-to-many with a join table
	 * 
	 * @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};
		$propertyModel = $model['properties'][$property];
		$assocModel = $record->porte->models->{$propertyModel['has_many']['type']};
		$assocTable = $record->porte->tables->{$assocModel['type']};
		if(isset($record->associations[$property])){
			return $record->associations[$property];
		}else if($record->isNew()){
			return $record->associations[$property] = new PorteIterator($assocTable,array());
		}else{
			$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(`'.$assocModel['database'].'`.`'.$assocModel['table'].'`.`'.$assocModel['primary_key'].'`, `'.$model['database'].'`.`'.$model['table'].'`.`'.$propertyModel['field'].'`)';
			return $record->associations[$property] = $assocTable->find($options);
		}
	}
	
	public static function deleteRecord($property,$record,$assocRecord=array()){
		self::deleteRecords($property,$record,$assocRecord);
	}
	
	/**
	 * Note, at the moment, we do not re-index the internal iterator array after unsetting a record
	 * 
	 * @return null
	 * @param $property string
	 * @param $record PorteRecord
	 * @param $deletedRecords Object[optional]
	 */
	public static function deleteRecords($property,PorteRecord $record,$deletedRecords=array()){
		$record->events->connect('record_save_before',array('PorteManyToManyListPersisted','deleteRecords_saveBefore'));
		$config = $record->porte->models->{$record->type}['properties'][$property];
		$assocTable = $record->porte->tables->{$config['has_many']['type']};
		if(empty($deletedRecords)){
			if(!isset($record->associations[$property])){
				$record->associations[$property] = new PorteIterator($assocTable);
			}else{
				$record->associations[$property]->arrayToDelete = $record->associations[$property]->array;
				$record->associations[$property]->array = array();
			}
			return $record;
		}
		if(is_string($deletedRecords)){
			$deletedRecords = explode(',',$deletedRecords);
		}else if(is_int($deletedRecords)){
			$deletedRecords = array(strval($deletedRecords));
		}else if(is_array($deletedRecords)){
			foreach($deletedRecords as $k=>$assocRecord){
				if(is_object($assocRecord)){
					$deletedRecords[$k] = strval($assocRecord->getIdentifier());
				}else if(is_int($assocRecord)||ctype_digit($assocRecord)){
					$deletedRecords[$k] = strval($assocRecord);
				}
			}
		}else if($deletedRecords instanceof PorteRecord){
			$deletedRecords = array($deletedRecords->getIdentifier());
		}
		if(isset($record->associations[$property])){
			$assocRecords = &$record->associations[$property];
			while($assocRecord = $assocRecords->current()){
				if(in_array($assocRecord->getIdentifier(),$deletedRecords)){
					if(!isset($assocRecords->arrayToDelete)) $assocRecords->arrayToDelete = array();
					$assocRecords->arrayToDelete[] = $assocRecord;
					$assocRecords->shift();
				}else{
					$assocRecords->next();
				}
			}
			$assocRecords->rewind();
		}
		return $record;
	}
	
	public static function deleteRecords_saveBefore(PorteRecord $record,&$circular){
		foreach($record->associations as $property=>$assocRecords){
			$idsToDelete = array();
			if(isset($assocRecords->arrayToDelete)){
				foreach($assocRecords->arrayToDelete as $v){
					if(is_string($v)){
						$idsToDelete[] = explode(',',$v);
					}else if(is_int($v)){
						$idsToDelete[] = strval($v);
					}else if($v instanceof PorteRecord){
						$idsToDelete[] = $v->getIdentifier();
					}
				}
				$record->attributes[$property] = implode(',',array_diff(explode(',',$record->attributes[$property]),$idsToDelete));
			}
		}
	}
	
	public static function countRecords($property,$record,$options=array()){
		$model = $record->porte->models->{$record->type};
		$propertyModel = $model['properties'][$property];
		$assocModel = $record->porte->models->{$model['properties'][$property]['has_many']['type']};
		//$assocTable = $record->porte->tables->{$assocModel['type']};
		$fks = explode(',',$record->attributes[$property]);
		$options = array_merge(array(
			'select'=>' count(*) ',
			'from'=>'`'.$assocModel['database'].'`.`'.$assocModel['table'].'`,`'.$model['database'].'`.`'.$model['table'].'`',
			'where'=>'1',
		),$options);
		$options['where'] .= ' AND `'.$model['database'].'`.`'.$model['table'].'`.`'.$model['primary_key'].'` = \''.$record->getIdentifier().'\' AND FIND_IN_SET(`'.$assocModel['database'].'`.`'.$assocModel['table'].'`.`'.$assocModel['primary_key'].'`, `'.$model['database'].'`.`'.$model['table'].'`.`'.$propertyModel['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