3 namespace DataAccess\DataMapper\Helpers
;
7 class AbstractPopulationHelper
10 const REFERENCE_FORWARD
= 1;
11 const REFERENCE_BACK
= 2;
12 const REFERENCE_SELF
= 3;
13 const QUERY_TYPE_UPDATE
= 'update';
14 const QUERY_TYPE_CREATE
= 'create';
16 static function getConstrutorArray($maps, $entity, $row, $db)
18 $constructors = array();
20 foreach($maps[$entity]['maps'] as $constructor => $mapsHelper)
22 switch(get_class($mapsHelper))
24 case 'DataAccess\DataMapper\Helpers\IntMapsHelper':
25 if(!empty($row[$mapsHelper->getColumnName()]) && (string)(int)$row[$mapsHelper->getColumnName()] != $row[$mapsHelper->getColumnName()]) throw new Exception('Expected numeric value.');
26 $constructors[$constructor] = (int)$row[$mapsHelper->getColumnName()];
28 case 'DataAccess\DataMapper\Helpers\VarcharMapsHelper':
29 $constructors[$constructor] = $row[$mapsHelper->getColumnName()];
31 case 'DataAccess\DataMapper\Helpers\VOMapsHelper':
32 case 'DataAccess\DataMapper\Helpers\VOArrayMapsHelper':
33 case 'DataAccess\DataMapper\Helpers\EntityMapsHelper':
34 case 'DataAccess\DataMapper\Helpers\EntityArrayMapsHelper':
35 $constructors[$constructor] = $mapsHelper->populate($maps, $db, $entity, $row);
42 static function generateUpdateSaveQuery($maps, $entity, $id, $db, &$queries = array(), $extraColumns = array())
44 $entityMapsIndex = self
::getMapsNameFromEntityObject($entity, $maps);
48 $query = sprintf('update %s set ', $maps[$entityMapsIndex]['table']);
50 $queryColumnNamesAndValues = array();
53 foreach($maps[$entityMapsIndex]['maps'] as $mapsHelper)
55 $accessor = $mapsHelper->getAccessor();
56 $property = $entity->{$accessor}();
58 //sometimes children objects will be null, e.g., the banner for a simfile
60 if(!is_null($property))
62 switch(get_class($mapsHelper))
64 case 'DataAccess\DataMapper\Helpers\VOMapsHelper':
65 //we have a vo. Determine which way the reference is
66 $voMapsIndex = self
::getMapsNameFromEntityObject($property, $maps);
67 $refDir = self
::getReferenceDirection(
68 $maps[$entityMapsIndex]['table'],
69 $maps[$voMapsIndex]['table'],
71 $mapsHelper->getTableName(),
76 // our table stores their ID, all we do is update
78 case self
::REFERENCE_FORWARD
:
79 $voTableId = self
::findVOInDB($maps, $property, $db);
83 $query .= sprintf('%s=%u, ',
84 strtolower($mapsHelper->getTableName() . '_id'),
87 // we have a forward reference to a value object.
88 // see if it exists first:
91 $queryColumnNamesAndValues[strtolower($mapsHelper->getTableName() . '_id')] = $voTableId;
93 //make a note that this field will need the id from another
94 self
::generateUpdateSaveQuery($maps, $property, NULL
, $db, $queries);
95 $queryColumnNamesAndValues[strtolower($mapsHelper->getTableName() . '_id')] = '%INDEX_REF_' . (count($queries)-1) . '%';
100 case self
::REFERENCE_SELF
:
101 //no need to find ids, but we need the
103 $columns = self
::resolveColumnNamesAndValues($maps, $property);
104 foreach($columns as $columnName=>$columnValue)
108 //TODO: logic to detemine what the value is? i.e., string, int etc?
109 $query .= sprintf('%s="%s", ',
114 //TODO: logic to detemine what the value is? i.e., string, int etc?
115 $queryColumnNamesAndValues[$columnName] = $db->quote($columnValue);
120 case self
::REFERENCE_BACK
:
121 $voId = self
::findVOInDB($maps,
124 array(strtolower($entityMapsIndex . '_id') => $id));
127 self
::generateUpdateSaveQuery($maps, $property, $voId, $db, $queries);
129 $extra = array(strtolower($entityMapsIndex . '_id') => '%MAIN_QUERY_ID%');
130 self
::generateUpdateSaveQuery($maps, $property, NULL
, $db, $queries, $extra);
137 // We should never update referenced entities, the db
138 // should always store an ID as a reference to them.
140 // In the case where we cannot find the entity in the database,
141 // throw an exception ?
142 case 'DataAccess\DataMapper\Helpers\EntityMapsHelper':
143 $subEntityMapsIndex = self
::getMapsNameFromEntityObject($property, $maps);
144 $refDir = self
::getReferenceDirection(
145 $maps[$entityMapsIndex]['table'],
146 $maps[$subEntityMapsIndex]['table'],
148 $mapsHelper->getTableName(),
153 // our table stores their ID, all we do is update
155 case self
::REFERENCE_FORWARD
:
156 if($property->getId())
158 // we exist in db already, update our reference
161 $query .= sprintf('%s=%u, ',
162 //strtolower($mapsHelper->getEntityName() . '_id'),
163 strtolower($mapsHelper->getTableName() . '_id'),
166 //not in db yet. make new ref
167 //$queryColumnNamesAndValues[strtolower($mapsHelper->getEntityName() . '_id')] = $property->getId();
168 $queryColumnNamesAndValues[strtolower($mapsHelper->getTableName() . '_id')] = $property->getId();
171 // The entity we care about references an entity that
172 // has not yet been saved.
174 // TODO: Should we _try_ to save it? Or should
175 // it be enforced that entites already exist in the db?
176 // In the case of something like referencing a user entity,
177 // then for sure the user should already be saved because
178 // it makes no sense to assign a user at the time of simfile
179 // upload, they should have already completed the process.
180 // but could there be other entities where it does make sense
181 // for them to be created at the time of something else ?
182 throw new Exception(sprintf(
183 'Could not find referenced entity, %s, in the database. Has it been saved yet?',
184 $mapsHelper->getEntityName()));
189 case 'DataAccess\DataMapper\Helpers\IntMapsHelper':
192 //easy case, plain values in our table.
193 $query .= sprintf('%s=%u, ',
194 $mapsHelper->getColumnName(),
197 if(is_bool($property))
199 $property = ($property) ?
'1' : '0';
201 $queryColumnNamesAndValues[$mapsHelper->getColumnName()] = $property;
204 case 'DataAccess\DataMapper\Helpers\VarcharMapsHelper':
206 //easy case, plain values in our table.
207 $query .= sprintf('%s="%s", ',
208 $mapsHelper->getColumnName(),
211 $queryColumnNamesAndValues[$mapsHelper->getColumnName()] = $db->quote($property);
216 // I am making a bit of an assumption here. In my mind it only
217 // makes sense for an array of VOs to be stored in a different
218 // table in the DB since the main row can't possibly store
219 // different objects.
221 // in that regard, the way this works is that mapVOArrayToIds
222 // simply queries the DB and returns the VO ids in order then
223 // I assume that the object also has them in the same order
224 // (which it will if it is pulled out by this mapper.
226 // in the case of setting up a new entity, the VOs should never
227 // exist in the first place, so we just make them.
228 case 'DataAccess\DataMapper\Helpers\VOArrayMapsHelper':
229 case 'DataAccess\DataMapper\Helpers\EntityArrayMapsHelper':
230 if($id && isset($property[0]))
232 // If we assume that all elements in the array are the same then
233 // we can just use the first one to figure out which maps entry to use
235 $subEntityMapsIndex = self
::getMapsNameFromEntityObject($property[0], $maps);
236 //TODO: I think this function will work with Entities too, but I should probably rename it at some point
237 $voIds = self
::mapVOArrayToIds($maps[$subEntityMapsIndex]['table'],
238 array(strtolower($entityMapsIndex . '_id'), $id),
241 foreach($property as $index => $propertyArrayElement)
243 //XXX: I wanted this to only run on VOs, not entities. But there's a problem with that.
244 //when creating a pack, the simfile entities need to reference the pack, and the only way for
245 //that to happen is here. What I do instead is check that the entity has an id (which implies
246 //it has already been created and save) if it is a IDivineEntity. If it doesn't, complain.
247 //this ensures consistent behaviour with other parts of this mapper.
248 if($property instanceof \Domain\Entities\IDivineEntity
&& !$property->getId())
250 throw new Exception(sprintf(
251 'Could not find referenced entity, %s, in the database. Has it been saved yet?',
252 $mapsHelper->getEntityName()));
255 $extra = array(strtolower($entityMapsIndex . '_id') => $id);
256 if(isset($voIds[$index]))
258 self
::generateUpdateSaveQuery($maps, $propertyArrayElement, $voIds[$index], $db, $queries, $extra);
260 self
::generateUpdateSaveQuery($maps, $propertyArrayElement, NULL
, $db, $queries, $extra);
266 foreach($property as $propertyArrayElement)
268 //XXX: I wanted this to only run on VOs, not entities. But there's a problem with that.
269 //when creating a pack, the simfile entities need to reference the pack, and the only way for
270 //that to happen is here. What I do instead is check that the entity has an id (which implies
271 //it has already been created and save) if it is a IDivineEntity. If it doesn't, complain.
272 //this ensures consistent behaviour with other parts of this mapper.
273 if($property instanceof \Domain\Entities\IDivineEntity
&& !$property->getId())
275 throw new Exception(sprintf(
276 'Could not find referenced entity, %s, in the database. Has it been saved yet?',
277 $mapsHelper->getEntityName()));
280 // TODO: TRICKY! Since this is a back-reference, it
281 // needs the ID of the object we're trying to save
283 $extra = array(strtolower($entityMapsIndex . '_id') => '%MAIN_QUERY_ID%');
284 self
::generateUpdateSaveQuery($maps, $propertyArrayElement, NULL
, $db, $queries, $extra);
293 $queryColumnNamesAndValues = @$queryColumnNamesAndValues ?
: array();
294 $queries[] = array('id' => $id, 'prepared' => $query, 'table' => $maps[$entityMapsIndex]['table'], 'columns' => $queryColumnNamesAndValues);
296 $queryColumnNamesAndValues = array_merge($queryColumnNamesAndValues, $extraColumns);
297 $queries[] = array('table' => $maps[$entityMapsIndex]['table'], 'columns' => $queryColumnNamesAndValues);
303 static private function getMapsNameFromEntityObject($entity, $maps)
305 //todo maybe check that $entity is vo or entity
307 $classname = get_class($entity);
308 foreach ($maps as $entityName => $map)
310 if($map['class'] == $classname)
317 static private function getReferenceDirection($tableA, $tableB, $nameA, $nameB, $db)
319 //TODO: check if tables are the same and return a constant for that
320 //echo '!!! ' . $tableA . ' needs ' . $nameB . ' : ' . $tableB . ' needs ' . $nameA . ' !!!<br />';
321 $dbName = $db->query('select database()')->fetchColumn();
322 if($tableA === $tableB)
324 return self
::REFERENCE_SELF
;
327 // first look in table A for a reference to B
328 $statement = $db->prepare(sprintf(
329 'SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`="%s" AND `TABLE_NAME`="%s"',
333 $statement->execute();
334 $rows = $statement->fetchAll();
338 foreach($rows as $row)
340 if($row['COLUMN_NAME'] == strtolower($nameB . '_id'))
342 return self
::REFERENCE_FORWARD
;
346 // now look in table b for a reference to a
347 $statement = $db->prepare(sprintf(
348 'SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`="%s" AND `TABLE_NAME`="%s"',
352 $statement->execute();
353 $rows = $statement->fetchAll();
355 foreach($rows as $row)
357 if($row['COLUMN_NAME'] == strtolower($nameA . '_id'))
359 return self
::REFERENCE_BACK
;
364 // can use this when we reference a VO
365 static public function findVOInDB($maps, $VO, $db, $extraColumns = array())
367 $mapsIndex = self
::getMapsNameFromEntityObject($VO, $maps);
368 $table = $maps[$mapsIndex]['table'];
370 $columns = array_merge(self
::resolveColumnNamesAndValues($maps, $VO), $extraColumns);
372 $query = "SELECT * FROM $table where ";
374 foreach($columns as $columnName => $columnValue)
376 $columnValue = $db->quote($columnValue);
377 $query .= sprintf('%s=%s AND ', $columnName, str_replace('"', '\"', $columnValue));
380 $query = substr($query, 0, -4);
381 $statement = $db->prepare($query);
382 $statement->execute();
383 $row = $statement->fetch();
388 // this will figure out what columns belong to an entity and
389 // map the column names to the current entity values
390 static public function resolveColumnNamesAndValues($maps, $entity, $originalTable = null
, &$columnNamesAndValues = array())
392 $mapsIndex = self
::getMapsNameFromEntityObject($entity, $maps);
394 // This is the name of the table that the current object
395 // we are looking at belongs to. We need to compare this
396 // to original table to decide what to do.
397 $currentTable = $maps[$mapsIndex]['table'];
401 // this will be the table that the VO we care about is stored in
402 // we check all future values to make sure they belong to this table.
403 // on the first pass we pull it out, and then on subsequent passes
404 // it should come in through the function call.
405 $originalTable = $currentTable;
408 foreach($maps[$mapsIndex]['maps'] as $mapsHelper)
410 switch(get_class($mapsHelper))
412 case 'DataAccess\DataMapper\Helpers\VOMapsHelper':
413 $accessor = $mapsHelper->getAccessor();
414 $VO = $entity->{$accessor}();
415 self
::resolveColumnNamesAndValues($maps, $VO, $originalTable, $columnNamesAndValues);
417 case 'DataAccess\DataMapper\Helpers\VarcharMapsHelper':
418 case 'DataAccess\DataMapper\Helpers\IntMapsHelper':
421 if($currentTable == $originalTable)
423 //It also keeps values in our table. Saving.
424 $accessor = $mapsHelper->getAccessor();
425 $value = $entity->{$accessor}();
427 $columnNamesAndValues[$mapsHelper->getColumnName()] = $value;
429 //It does not store values in our table
430 //TODO: Should I try check if our table references the id of the record in the other table?
436 return $columnNamesAndValues;
439 // When we have VO arrays, it should be the case that there is another
440 // table that references us. If we assume entries are in the db in the
441 // same order they are in the array, it should be possible to map them up.
442 // This way we can update existing VO entries instead of deleting and
443 // making new ones. And if there are VOs where we can't get an ID, that
444 // means we have to make a new one.
446 // Assumption: Array contains entries of all the same type
447 static public function mapVOArrayToIds($voTable, $columnToMatch, $db)
449 $query = sprintf('SELECT id from %s WHERE %s=%u',
454 $statement = $db->prepare($query);
455 $statement->execute();
456 $rows = $statement->fetchAll();
460 foreach($rows as $row)
469 //go off and resolve all column names for this table recursively
470 //do a check to make sure the VO we are investigating is in the same table
471 // -if it is: good, record its column name and value
472 // -if it isn't, we need to find this next VO in the db and record its ID as our column value
473 // -assuming a forward reference (IE our table has a column called voname_id.
474 // -if it doesn't, then we don't have to worry about it