1 |
<?php
|
2 |
/*
|
3 |
* $Id: UnitOfWork.php 3195 2007-11-20 14:26:42Z romanb $
|
4 |
*
|
5 |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
6 |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
7 |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
8 |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
9 |
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
10 |
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
11 |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
12 |
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
13 |
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
14 |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
15 |
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
16 |
*
|
17 |
* This software consists of voluntary contributions made by many individuals
|
18 |
* and is licensed under the LGPL. For more information, see
|
19 |
* <http://www.phpdoctrine.com>.
|
20 |
*/
|
21 |
Doctrine::autoload('Doctrine_Connection_Module');
|
22 |
/**
|
23 |
* Doctrine_Connection_UnitOfWork
|
24 |
*
|
25 |
* @package Doctrine
|
26 |
* @subpackage Connection
|
27 |
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
28 |
* @link www.phpdoctrine.com
|
29 |
* @since 1.0
|
30 |
* @version $Revision: 3195 $
|
31 |
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
32 |
*/
|
33 |
class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module
|
34 |
{
|
35 |
/**
|
36 |
* buildFlushTree
|
37 |
* builds a flush tree that is used in transactions
|
38 |
*
|
39 |
* The returned array has all the initialized components in
|
40 |
* 'correct' order. Basically this means that the records of those
|
41 |
* components can be saved safely in the order specified by the returned array.
|
42 |
*
|
43 |
* @param array $tables an array of Doctrine_Table objects or component names
|
44 |
* @return array an array of component names in flushing order
|
45 |
*/
|
46 |
public function buildFlushTree(array $tables)
|
47 |
{
|
48 |
$tree = array();
|
49 |
foreach ($tables as $k => $table) {
|
50 |
|
51 |
if ( ! ($table instanceof Doctrine_Table)) {
|
52 |
$table = $this->conn->getTable($table, false);
|
53 |
}
|
54 |
$nm = $table->getComponentName();
|
55 |
|
56 |
$index = array_search($nm, $tree);
|
57 |
|
58 |
if ($index === false) {
|
59 |
$tree[] = $nm;
|
60 |
$index = max(array_keys($tree));
|
61 |
}
|
62 |
|
63 |
$rels = $table->getRelations();
|
64 |
|
65 |
// group relations
|
66 |
|
67 |
foreach ($rels as $key => $rel) {
|
68 |
if ($rel instanceof Doctrine_Relation_ForeignKey) {
|
69 |
unset($rels[$key]);
|
70 |
array_unshift($rels, $rel);
|
71 |
}
|
72 |
}
|
73 |
|
74 |
foreach ($rels as $rel) {
|
75 |
$name = $rel->getTable()->getComponentName();
|
76 |
$index2 = array_search($name,$tree);
|
77 |
$type = $rel->getType();
|
78 |
|
79 |
// skip self-referenced relations
|
80 |
if ($name === $nm) {
|
81 |
continue;
|
82 |
}
|
83 |
|
84 |
if ($rel instanceof Doctrine_Relation_ForeignKey) {
|
85 |
if ($index2 !== false) {
|
86 |
if ($index2 >= $index)
|
87 |
continue;
|
88 |
|
89 |
unset($tree[$index]);
|
90 |
array_splice($tree,$index2,0,$nm);
|
91 |
$index = $index2;
|
92 |
} else {
|
93 |
$tree[] = $name;
|
94 |
}
|
95 |
|
96 |
} elseif ($rel instanceof Doctrine_Relation_LocalKey) {
|
97 |
if ($index2 !== false) {
|
98 |
if ($index2 <= $index)
|
99 |
continue;
|
100 |
|
101 |
unset($tree[$index2]);
|
102 |
array_splice($tree,$index,0,$name);
|
103 |
} else {
|
104 |
array_unshift($tree,$name);
|
105 |
$index++;
|
106 |
}
|
107 |
} elseif ($rel instanceof Doctrine_Relation_Association) {
|
108 |
$t = $rel->getAssociationFactory();
|
109 |
$n = $t->getComponentName();
|
110 |
|
111 |
if ($index2 !== false)
|
112 |
unset($tree[$index2]);
|
113 |
|
114 |
array_splice($tree, $index, 0, $name);
|
115 |
$index++;
|
116 |
|
117 |
$index3 = array_search($n, $tree);
|
118 |
|
119 |
if ($index3 !== false) {
|
120 |
if ($index3 >= $index)
|
121 |
continue;
|
122 |
|
123 |
unset($tree[$index]);
|
124 |
array_splice($tree, $index3, 0, $n);
|
125 |
$index = $index2;
|
126 |
} else {
|
127 |
$tree[] = $n;
|
128 |
}
|
129 |
}
|
130 |
}
|
131 |
}
|
132 |
return array_values($tree);
|
133 |
}
|
134 |
|
135 |
/**
|
136 |
* saves the given record
|
137 |
*
|
138 |
* @param Doctrine_Record $record
|
139 |
* @return void
|
140 |
*/
|
141 |
public function saveGraph(Doctrine_Record $record)
|
142 |
{
|
143 |
$conn = $this->getConnection();
|
144 |
|
145 |
$state = $record->state();
|
146 |
if ($state === Doctrine_Record::STATE_LOCKED) {
|
147 |
return false;
|
148 |
}
|
149 |
|
150 |
$record->state(Doctrine_Record::STATE_LOCKED);
|
151 |
|
152 |
$conn->beginTransaction();
|
153 |
$saveLater = $this->saveRelated($record);
|
154 |
|
155 |
$record->state($state);
|
156 |
|
157 |
if ($record->isValid()) {
|
158 |
$event = new Doctrine_Event($record, Doctrine_Event::RECORD_SAVE);
|
159 |
|
160 |
$record->preSave($event);
|
161 |
|
162 |
$record->getTable()->getRecordListener()->preSave($event);
|
163 |
$state = $record->state();
|
164 |
|
165 |
if ( ! $event->skipOperation) {
|
166 |
switch ($state) {
|
167 |
case Doctrine_Record::STATE_TDIRTY:
|
168 |
$this->insert($record);
|
169 |
break;
|
170 |
case Doctrine_Record::STATE_DIRTY:
|
171 |
case Doctrine_Record::STATE_PROXY:
|
172 |
$this->update($record);
|
173 |
break;
|
174 |
case Doctrine_Record::STATE_CLEAN:
|
175 |
case Doctrine_Record::STATE_TCLEAN:
|
176 |
|
177 |
break;
|
178 |
}
|
179 |
}
|
180 |
|
181 |
$record->getTable()->getRecordListener()->postSave($event);
|
182 |
|
183 |
$record->postSave($event);
|
184 |
} else {
|
185 |
$conn->transaction->addInvalid($record);
|
186 |
}
|
187 |
|
188 |
$state = $record->state();
|
189 |
|
190 |
$record->state(Doctrine_Record::STATE_LOCKED);
|
191 |
|
192 |
foreach ($saveLater as $fk) {
|
193 |
$alias = $fk->getAlias();
|
194 |
|
195 |
if ($record->hasReference($alias)) {
|
196 |
$obj = $record->$alias;
|
197 |
|
198 |
// check that the related object is not an instance of Doctrine_Null
|
199 |
if ( ! ($obj instanceof Doctrine_Null)) {
|
200 |
$obj->save($conn);
|
201 |
}
|
202 |
}
|
203 |
}
|
204 |
|
205 |
// save the MANY-TO-MANY associations
|
206 |
$this->saveAssociations($record);
|
207 |
|
208 |
$record->state($state);
|
209 |
|
210 |
$conn->commit();
|
211 |
|
212 |
return true;
|
213 |
}
|
214 |
|
215 |
/**
|
216 |
* saves the given record
|
217 |
*
|
218 |
* @param Doctrine_Record $record
|
219 |
* @return void
|
220 |
*/
|
221 |
public function save(Doctrine_Record $record)
|
222 |
{
|
223 |
$event = new Doctrine_Event($record, Doctrine_Event::RECORD_SAVE);
|
224 |
|
225 |
$record->preSave($event);
|
226 |
|
227 |
$record->getTable()->getRecordListener()->preSave($event);
|
228 |
|
229 |
if ( ! $event->skipOperation) {
|
230 |
switch ($record->state()) {
|
231 |
case Doctrine_Record::STATE_TDIRTY:
|
232 |
$this->insert($record);
|
233 |
break;
|
234 |
case Doctrine_Record::STATE_DIRTY:
|
235 |
case Doctrine_Record::STATE_PROXY:
|
236 |
$this->update($record);
|
237 |
break;
|
238 |
case Doctrine_Record::STATE_CLEAN:
|
239 |
case Doctrine_Record::STATE_TCLEAN:
|
240 |
// do nothing
|
241 |
break;
|
242 |
}
|
243 |
}
|
244 |
|
245 |
$record->getTable()->getRecordListener()->postSave($event);
|
246 |
|
247 |
$record->postSave($event);
|
248 |
}
|
249 |
|
250 |
/**
|
251 |
* deletes given record and all the related composites
|
252 |
* this operation is isolated by a transaction
|
253 |
*
|
254 |
* this event can be listened by the onPreDelete and onDelete listeners
|
255 |
*
|
256 |
* @return boolean true on success, false on failure
|
257 |
*/
|
258 |
public function delete(Doctrine_Record $record)
|
259 |
{
|
260 |
if ( ! $record->exists()) {
|
261 |
return false;
|
262 |
}
|
263 |
$this->conn->beginTransaction();
|
264 |
|
265 |
$event = new Doctrine_Event($record, Doctrine_Event::RECORD_DELETE);
|
266 |
|
267 |
$record->preDelete($event);
|
268 |
|
269 |
$table = $record->getTable();
|
270 |
|
271 |
$table->getRecordListener()->preDelete($event);
|
272 |
|
273 |
$state = $record->state();
|
274 |
|
275 |
$record->state(Doctrine_Record::STATE_LOCKED);
|
276 |
|
277 |
$this->deleteComposites($record);
|
278 |
|
279 |
if ( ! $event->skipOperation) {
|
280 |
$record->state(Doctrine_Record::STATE_TDIRTY);
|
281 |
if ($table->getOption('joinedParents')) {
|
282 |
|
283 |
foreach ($table->getOption('joinedParents') as $parent) {
|
284 |
$parentTable = $table->getConnection()->getTable($parent);
|
285 |
|
286 |
$this->conn->delete($parentTable, $record->identifier());
|
287 |
}
|
288 |
}
|
289 |
$this->conn->delete($table, $record->identifier());
|
290 |
|
291 |
$record->state(Doctrine_Record::STATE_TCLEAN);
|
292 |
} else {
|
293 |
// return to original state
|
294 |
$record->state($state);
|
295 |
}
|
296 |
|
297 |
$table->getRecordListener()->postDelete($event);
|
298 |
|
299 |
$record->postDelete($event);
|
300 |
|
301 |
$table->removeRecord($record);
|
302 |
|
303 |
$this->conn->commit();
|
304 |
|
305 |
return true;
|
306 |
}
|
307 |
|
308 |
/**
|
309 |
* @todo Description. See also the todo for deleteMultiple().
|
310 |
*/
|
311 |
public function deleteRecord(Doctrine_Record $record)
|
312 |
{
|
313 |
$ids = $record->identifier();
|
314 |
$tmp = array();
|
315 |
|
316 |
foreach (array_keys($ids) as $id) {
|
317 |
$tmp[] = $id . ' = ? ';
|
318 |
}
|
319 |
|
320 |
$params = array_values($ids);
|
321 |
|
322 |
$query = 'DELETE FROM '
|
323 |
. $this->conn->quoteIdentifier($record->getTable()->getTableName())
|
324 |
. ' WHERE ' . implode(' AND ', $tmp);
|
325 |
|
326 |
|
327 |
return $this->conn->exec($query, $params);
|
328 |
}
|
329 |
|
330 |
/**
|
331 |
* deleteMultiple
|
332 |
* deletes all records from the pending delete list
|
333 |
*
|
334 |
* @return void
|
335 |
* @todo Refactor. Maybe move to the Connection class? Sometimes UnitOfWork constructs
|
336 |
* queries itself and sometimes it leaves the sql construction to Connection.
|
337 |
* This should be changed.
|
338 |
*/
|
339 |
public function deleteMultiple(array $records)
|
340 |
{
|
341 |
foreach ($this->delete as $name => $deletes) {
|
342 |
$record = false;
|
343 |
$ids = array();
|
344 |
|
345 |
// Note: Why is the last element's table identifier checked here and then
|
346 |
// the table object from $deletes[0] used???
|
347 |
if (is_array($deletes[count($deletes)-1]->getTable()->getIdentifier()) &&
|
348 |
count($deletes) > 0) {
|
349 |
$table = $deletes[0]->getTable();
|
350 |
$query = 'DELETE FROM '
|
351 |
. $this->conn->quoteIdentifier($table->getTableName())
|
352 |
. ' WHERE ';
|
353 |
|
354 |
$params = array();
|
355 |
$cond = array();
|
356 |
foreach ($deletes as $k => $record) {
|
357 |
$ids = $record->identifier();
|
358 |
$tmp = array();
|
359 |
foreach (array_keys($ids) as $id) {
|
360 |
$tmp[] = $table->getColumnName($id) . ' = ? ';
|
361 |
}
|
362 |
$params = array_merge($params, array_values($ids));
|
363 |
$cond[] = '(' . implode(' AND ', $tmp) . ')';
|
364 |
}
|
365 |
$query .= implode(' OR ', $cond);
|
366 |
|
367 |
$this->conn->execute($query, $params);
|
368 |
} else {
|
369 |
foreach ($deletes as $k => $record) {
|
370 |
$ids[] = $record->getIncremented();
|
371 |
}
|
372 |
// looks pretty messy. $record should be already out of scope. ugly php behaviour.
|
373 |
// even the php manual agrees on that and recommends to unset() the last element
|
374 |
// immediately after the loop ends.
|
375 |
$table = $record->getTable();
|
376 |
if ($record instanceof Doctrine_Record) {
|
377 |
$params = substr(str_repeat('?, ', count($ids)), 0, -2);
|
378 |
|
379 |
$query = 'DELETE FROM '
|
380 |
. $this->conn->quoteIdentifier($record->getTable()->getTableName())
|
381 |
. ' WHERE '
|
382 |
. $table->getColumnName($table->getIdentifier())
|
383 |
. ' IN(' . $params . ')';
|
384 |
|
385 |
$this->conn->execute($query, $ids);
|
386 |
}
|
387 |
}
|
388 |
}
|
389 |
}
|
390 |
|
391 |
/**
|
392 |
* saveRelated
|
393 |
* saves all related records to $record
|
394 |
*
|
395 |
* @throws PDOException if something went wrong at database level
|
396 |
* @param Doctrine_Record $record
|
397 |
*/
|
398 |
public function saveRelated(Doctrine_Record $record)
|
399 |
{
|
400 |
$saveLater = array();
|
401 |
foreach ($record->getReferences() as $k => $v) {
|
402 |
$rel = $record->getTable()->getRelation($k);
|
403 |
|
404 |
$local = $rel->getLocal();
|
405 |
$foreign = $rel->getForeign();
|
406 |
|
407 |
if ($rel instanceof Doctrine_Relation_ForeignKey) {
|
408 |
$saveLater[$k] = $rel;
|
409 |
} else if ($rel instanceof Doctrine_Relation_LocalKey) {
|
410 |
// ONE-TO-ONE relationship
|
411 |
$obj = $record->get($rel->getAlias());
|
412 |
|
413 |
// Protection against infinite function recursion before attempting to save
|
414 |
if ($obj instanceof Doctrine_Record && $obj->isModified()) {
|
415 |
$obj->save($this->conn);
|
416 |
|
417 |
/** Can this be removed?
|
418 |
$id = array_values($obj->identifier());
|
419 |
|
420 |
foreach ((array) $rel->getLocal() as $k => $field) {
|
421 |
$record->set($field, $id[$k]);
|
422 |
}
|
423 |
*/
|
424 |
}
|
425 |
}
|
426 |
}
|
427 |
|
428 |
return $saveLater;
|
429 |
}
|
430 |
|
431 |
/**
|
432 |
* saveAssociations
|
433 |
*
|
434 |
* this method takes a diff of one-to-many / many-to-many original and
|
435 |
* current collections and applies the changes
|
436 |
*
|
437 |
* for example if original many-to-many related collection has records with
|
438 |
* primary keys 1,2 and 3 and the new collection has records with primary keys
|
439 |
* 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then
|
440 |
* save new associations to 4 and 5
|
441 |
*
|
442 |
* @throws Doctrine_Connection_Exception if something went wrong at database level
|
443 |
* @param Doctrine_Record $record
|
444 |
* @return void
|
445 |
*/
|
446 |
public function saveAssociations(Doctrine_Record $record)
|
447 |
{
|
448 |
foreach ($record->getReferences() as $k => $v) {
|
449 |
$rel = $record->getTable()->getRelation($k);
|
450 |
|
451 |
if ($rel instanceof Doctrine_Relation_Association) {
|
452 |
$v->save($this->conn);
|
453 |
|
454 |
$assocTable = $rel->getAssociationTable();
|
455 |
foreach ($v->getDeleteDiff() as $r) {
|
456 |
$query = 'DELETE FROM ' . $assocTable->getTableName()
|
457 |
. ' WHERE ' . $rel->getForeign() . ' = ?'
|
458 |
. ' AND ' . $rel->getLocal() . ' = ?';
|
459 |
|
460 |
$this->conn->execute($query, array($r->getIncremented(), $record->getIncremented()));
|
461 |
}
|
462 |
|
463 |
foreach ($v->getInsertDiff() as $r) {
|
464 |
$assocRecord = $assocTable->create();
|
465 |
$assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r);
|
466 |
$assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record);
|
467 |
|
468 |
$this->saveGraph($assocRecord);
|
469 |
}
|
470 |
}
|
471 |
}
|
472 |
}
|
473 |
|
474 |
/**
|
475 |
* deletes all related composites
|
476 |
* this method is always called internally when a record is deleted
|
477 |
*
|
478 |
* @throws PDOException if something went wrong at database level
|
479 |
* @return void
|
480 |
*/
|
481 |
public function deleteComposites(Doctrine_Record $record)
|
482 |
{
|
483 |
foreach ($record->getTable()->getRelations() as $fk) {
|
484 |
if ($fk->isComposite()) {
|
485 |
$obj = $record->get($fk->getAlias());
|
486 |
if ($obj instanceof Doctrine_Record &&
|
487 |
$obj->state() != Doctrine_Record::STATE_LOCKED) {
|
488 |
$obj->delete($this->conn);
|
489 |
}
|
490 |
}
|
491 |
}
|
492 |
}
|
493 |
|
494 |
/**
|
495 |
* saveAll
|
496 |
* persists all the pending records from all tables
|
497 |
*
|
498 |
* @throws PDOException if something went wrong at database level
|
499 |
* @return void
|
500 |
*/
|
501 |
public function saveAll()
|
502 |
{
|
503 |
// get the flush tree
|
504 |
$tree = $this->buildFlushTree($this->conn->getTables());
|
505 |
|
506 |
// save all records
|
507 |
foreach ($tree as $name) {
|
508 |
$table = $this->conn->getTable($name);
|
509 |
|
510 |
foreach ($table->getRepository() as $record) {
|
511 |
$this->save($record);
|
512 |
}
|
513 |
}
|
514 |
|
515 |
// save all associations
|
516 |
foreach ($tree as $name) {
|
517 |
$table = $this->conn->getTable($name);
|
518 |
|
519 |
foreach ($table->getRepository() as $record) {
|
520 |
$this->saveAssociations($record);
|
521 |
}
|
522 |
}
|
523 |
}
|
524 |
|
525 |
/**
|
526 |
* updates given record
|
527 |
*
|
528 |
* @param Doctrine_Record $record record to be updated
|
529 |
* @return boolean whether or not the update was successful
|
530 |
*/
|
531 |
public function update(Doctrine_Record $record)
|
532 |
{
|
533 |
$event = new Doctrine_Event($record, Doctrine_Event::RECORD_UPDATE);
|
534 |
|
535 |
$record->preUpdate($event);
|
536 |
|
537 |
$table = $record->getTable();
|
538 |
|
539 |
$table->getRecordListener()->preUpdate($event);
|
540 |
|
541 |
if ( ! $event->skipOperation) {
|
542 |
$identifier = $record->identifier();
|
543 |
|
544 |
if ($table->getOption('joinedParents')) {
|
545 |
$dataSet = $this->formatDataSet($record);
|
546 |
|
547 |
$component = $table->getComponentName();
|
548 |
|
549 |
$classes = $table->getOption('joinedParents');
|
550 |
$classes[] = $component;
|
551 |
|
552 |
foreach ($record as $field => $value) {
|
553 |
if ($value instanceof Doctrine_Record) {
|
554 |
if ( ! $value->exists()) {
|
555 |
$value->save();
|
556 |
}
|
557 |
$record->set($field, $value->getIncremented());
|
558 |
}
|
559 |
}
|
560 |
|
561 |
foreach ($classes as $class) {
|
562 |
$parentTable = $this->conn->getTable($class);
|
563 |
|
564 |
$this->conn->update($this->conn->getTable($class), $dataSet[$class], $identifier);
|
565 |
}
|
566 |
} else {
|
567 |
$array = $record->getPrepared();
|
568 |
|
569 |
$this->conn->update($table, $array, $identifier);
|
570 |
}
|
571 |
$record->assignIdentifier(true);
|
572 |
}
|
573 |
|
574 |
$table->getRecordListener()->postUpdate($event);
|
575 |
|
576 |
$record->postUpdate($event);
|
577 |
|
578 |
return true;
|
579 |
}
|
580 |
|
581 |
/**
|
582 |
* inserts a record into database
|
583 |
*
|
584 |
* @param Doctrine_Record $record record to be inserted
|
585 |
* @return boolean
|
586 |
*/
|
587 |
public function insert(Doctrine_Record $record)
|
588 |
{
|
589 |
// listen the onPreInsert event
|
590 |
$event = new Doctrine_Event($record, Doctrine_Event::RECORD_INSERT);
|
591 |
|
592 |
$record->preInsert($event);
|
593 |
|
594 |
$table = $record->getTable();
|
595 |
|
596 |
$table->getRecordListener()->preInsert($event);
|
597 |
|
598 |
if ( ! $event->skipOperation) {
|
599 |
if ($table->getOption('joinedParents')) {
|
600 |
$dataSet = $this->formatDataSet($record);
|
601 |
|
602 |
$component = $table->getComponentName();
|
603 |
|
604 |
$classes = $table->getOption('joinedParents');
|
605 |
$classes[] = $component;
|
606 |
|
607 |
foreach ($classes as $k => $parent) {
|
608 |
if ($k === 0) {
|
609 |
$rootRecord = new $parent();
|
610 |
|
611 |
$rootRecord->merge($dataSet[$parent]);
|
612 |
|
613 |
$this->processSingleInsert($rootRecord);
|
614 |
} else {
|
615 |
foreach ((array) $rootRecord->identifier() as $id => $value) {
|
616 |
$dataSet[$parent][$id] = $value;
|
617 |
}
|
618 |
|
619 |
$this->conn->insert($this->conn->getTable($parent), $dataSet[$parent]);
|
620 |
}
|
621 |
}
|
622 |
} else {
|
623 |
$this->processSingleInsert($record);
|
624 |
}
|
625 |
}
|
626 |
|
627 |
$table->addRecord($record);
|
628 |
|
629 |
$table->getRecordListener()->postInsert($event);
|
630 |
|
631 |
$record->postInsert($event);
|
632 |
|
633 |
return true;
|
634 |
}
|
635 |
|
636 |
/**
|
637 |
* @todo DESCRIBE WHAT THIS METHOD DOES, PLEASE!
|
638 |
*/
|
639 |
public function formatDataSet(Doctrine_Record $record)
|
640 |
{
|
641 |
$table = $record->getTable();
|
642 |
|
643 |
$dataSet = array();
|
644 |
|
645 |
$component = $table->getComponentName();
|
646 |
|
647 |
$array = $record->getPrepared();
|
648 |
|
649 |
foreach ($table->getColumns() as $columnName => $definition) {
|
650 |
$fieldName = $table->getFieldName($columnName);
|
651 |
if (isset($definition['primary']) && $definition['primary']) {
|
652 |
continue;
|
653 |
}
|
654 |
|
655 |
if (isset($definition['owner'])) {
|
656 |
$dataSet[$definition['owner']][$fieldName] = $array[$fieldName];
|
657 |
} else {
|
658 |
$dataSet[$component][$fieldName] = $array[$fieldName];
|
659 |
}
|
660 |
}
|
661 |
|
662 |
return $dataSet;
|
663 |
}
|
664 |
|
665 |
/**
|
666 |
* @todo DESCRIBE WHAT THIS METHOD DOES, PLEASE!
|
667 |
*/
|
668 |
public function processSingleInsert(Doctrine_Record $record)
|
669 |
{
|
670 |
$fields = $record->getPrepared();
|
671 |
|
672 |
if (empty($fields)) {
|
673 |
return false;
|
674 |
}
|
675 |
|
676 |
$table = $record->getTable();
|
677 |
$identifier = (array) $table->getIdentifier();
|
678 |
|
679 |
$seq = $record->getTable()->sequenceName;
|
680 |
|
681 |
if ( ! empty($seq)) {
|
682 |
$id = $this->conn->sequence->nextId($seq);
|
683 |
$seqName = $table->getIdentifier();
|
684 |
$fields[$seqName] = $id;
|
685 |
|
686 |
$record->assignIdentifier($id);
|
687 |
}
|
688 |
|
689 |
$this->conn->insert($table, $fields);
|
690 |
|
691 |
if (empty($seq) && count($identifier) == 1 && $identifier[0] == $table->getIdentifier() &&
|
692 |
$table->getIdentifierType() != Doctrine::IDENTIFIER_NATURAL) {
|
693 |
|
694 |
if (strtolower($this->conn->getName()) == 'pgsql') {
|
695 |
$seq = $table->getTableName() . '_' . $identifier[0];
|
696 |
}
|
697 |
|
698 |
$id = $this->conn->sequence->lastInsertId($seq);
|
699 |
|
700 |
if ( ! $id) {
|
701 |
throw new Doctrine_Connection_Exception("Couldn't get last insert identifier.");
|
702 |
}
|
703 |
|
704 |
$record->assignIdentifier($id);
|
705 |
} else {
|
706 |
$record->assignIdentifier(true);
|
707 |
}
|
708 |
}
|
709 |
}
|