<?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());
}
}
?>