Development

/branches/1.3/lib/plugins/sfPropelPlugin/lib/addon/sfPropelData.class.php

You must first sign up to be able to contribute.

root/branches/1.3/lib/plugins/sfPropelPlugin/lib/addon/sfPropelData.class.php

Revision 23810, 15.7 kB (checked in by Kris.Wallsmith, 4 years ago)

[1.3] set svn:eol-style property to native and svn:keywords property to Id on all .php files

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * This class is the Propel implementation of sfData.  It interacts with the data source
13  * and loads data.
14  *
15  * @package    symfony
16  * @subpackage propel
17  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
18  * @version    SVN: $Id$
19  */
20 class sfPropelData extends sfData
21 {
22   protected
23     $deletedClasses = array(),
24     $con            = null;
25
26   /**
27    * Loads data from a file or directory into a Propel data source
28    *
29    * @see sfPropelData::loadData()
30    *
31    * @param mixed   $directoryOrFile  A file or directory path or an array of files or directories
32    * @param string  $connectionName   The Propel connection name, default 'propel'
33    *
34    * @throws Exception If the database throws an error, rollback transaction and rethrows exception
35    */
36   public function loadData($directoryOrFile = null, $connectionName = 'propel')
37   {
38     $files = $this->getFiles($directoryOrFile);
39
40     // load map classes
41     $this->loadMapBuilders();
42     $this->dbMap = Propel::getDatabaseMap($connectionName);
43
44     // wrap all database operations in a single transaction
45     $this->con = Propel::getConnection($connectionName);
46     try
47     {
48       $this->con->beginTransaction();
49
50       $this->doDeleteCurrentData($files);
51
52       $this->doLoadData($files);
53
54       $this->con->commit();
55     }
56     catch (Exception $e)
57     {
58       $this->con->rollBack();
59       throw $e;
60     }
61   }
62
63   /**
64    * Implements the abstract loadDataFromArray method and loads the data using the generated data model.
65    *
66    * @param array   $data  The data to be loaded into the data source
67    *
68    * @throws Exception If data is unnamed.
69    * @throws sfException If an object defined in the model does not exist in the data
70    * @throws sfException If a column that does not exist is referenced
71    */
72   public function loadDataFromArray($data)
73   {
74     if ($data === null)
75     {
76       // no data
77       return;
78     }
79
80     foreach ($data as $class => $datas)
81     {
82       $class = trim($class);
83
84       $tableMap = $this->dbMap->getTable(constant(constant($class.'::PEER').'::TABLE_NAME'));
85
86       $column_names = call_user_func_array(array(constant($class.'::PEER'), 'getFieldNames'), array(BasePeer::TYPE_FIELDNAME));
87
88       // iterate through datas for this class
89       // might have been empty just for force a table to be emptied on import
90       if (!is_array($datas))
91       {
92         continue;
93       }
94
95       foreach ($datas as $key => $data)
96       {
97         // create a new entry in the database
98         if (!class_exists($class))
99         {
100           throw new InvalidArgumentException(sprintf('Unknown class "%s".', $class));
101         }
102
103         $obj = new $class();
104
105         if (!$obj instanceof BaseObject)
106         {
107           throw new RuntimeException(sprintf('The class "%s" is not a Propel class. This probably means there is already a class named "%s" somewhere in symfony or in your project.', $class, $class));
108         }
109
110         if (!is_array($data))
111         {
112           throw new InvalidArgumentException(sprintf('You must give a name for each fixture data entry (class %s).', $class));
113         }
114
115         foreach ($data as $name => $value)
116         {
117           if (is_array($value) && 's' == substr($name, -1))
118           {
119             // many to many relationship
120             $this->loadMany2Many($obj, substr($name, 0, -1), $value);
121
122             continue;
123           }
124
125           $isARealColumn = true;
126           try
127           {
128             $column = $tableMap->getColumn($name);
129           }
130           catch (PropelException $e)
131           {
132             $isARealColumn = false;
133           }
134
135           // foreign key?
136           if ($isARealColumn)
137           {
138             if ($column->isForeignKey() && null !== $value)
139             {
140               $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
141               if (!isset($this->object_references[$relatedTable->getPhpName().'_'.$value]))
142               {
143                 throw new InvalidArgumentException(sprintf('The object "%s" from class "%s" is not defined in your data file.', $value, $relatedTable->getPhpName()));
144               }
145               $value = $this->object_references[$relatedTable->getPhpName().'_'.$value]->getByName($column->getRelatedName(), BasePeer::TYPE_COLNAME);
146             }
147           }
148
149           if (false !== $pos = array_search($name, $column_names))
150           {
151             $obj->setByPosition($pos, $value);
152           }
153           else if (is_callable(array($obj, $method = 'set'.sfInflector::camelize($name))))
154           {
155             $obj->$method($value);
156           }
157           else
158           {
159             throw new InvalidArgumentException(sprintf('Column "%s" does not exist for class "%s".', $name, $class));
160           }
161         }
162         $obj->save($this->con);
163
164         // save the object for future reference
165         if (method_exists($obj, 'getPrimaryKey'))
166         {
167           $this->object_references[Propel::importClass(constant(constant($class.'::PEER').'::CLASS_DEFAULT')).'_'.$key] = $obj;
168         }
169       }
170     }
171   }
172
173   /**
174    * Loads many to many objects.
175    *
176    * @param BaseObject $obj               A Propel object
177    * @param string     $middleTableName   The middle table name
178    * @param array      $values            An array of values
179    */
180   protected function loadMany2Many($obj, $middleTableName, $values)
181   {
182     $middleTable = $this->dbMap->getTable($middleTableName);
183     $middleClass = $middleTable->getPhpName();
184     foreach ($middleTable->getColumns()  as $column)
185     {
186       if ($column->isForeignKey() && constant(constant(get_class($obj).'::PEER').'::TABLE_NAME') != $column->getRelatedTableName())
187       {
188         $relatedClass = $this->dbMap->getTable($column->getRelatedTableName())->getPhpName();
189         break;
190       }
191     }
192
193     if (!isset($relatedClass))
194     {
195       throw new InvalidArgumentException(sprintf('Unable to find the many-to-many relationship for object "%s".', get_class($obj)));
196     }
197
198     $setter = 'set'.get_class($obj);
199     $relatedSetter = 'set'.$relatedClass;
200
201     foreach ($values as $value)
202     {
203       if (!isset($this->object_references[$relatedClass.'_'.$value]))
204       {
205         throw new InvalidArgumentException(sprintf('The object "%s" from class "%s" is not defined in your data file.', $value, $relatedClass));
206       }
207
208       $middle = new $middleClass();
209       $middle->$setter($obj);
210       $middle->$relatedSetter($this->object_references[$relatedClass.'_'.$value]);
211       $middle->save();
212     }
213   }
214
215   /**
216    * Clears existing data from the data source by reading the fixture files
217    * and deleting the existing data for only those classes that are mentioned
218    * in the fixtures.
219    *
220    * @param array $files The list of YAML files.
221    *
222    * @throws sfException If a class mentioned in a fixture can not be found
223    */
224   protected function doDeleteCurrentData($files)
225   {
226     // delete all current datas in database
227     if (!$this->deleteCurrentData)
228     {
229       return;
230     }
231
232     rsort($files);
233     foreach ($files as $file)
234     {
235       $data = sfYaml::load($file);
236
237       if ($data === null)
238       {
239         // no data
240         continue;
241       }
242
243       $classes = array_keys($data);
244       foreach (array_reverse($classes) as $class)
245       {
246         $class = trim($class);
247         if (in_array($class, $this->deletedClasses))
248         {
249           continue;
250         }
251
252         // Check that peer class exists before calling doDeleteAll()
253         if (!class_exists(constant($class.'::PEER')))
254         {
255           throw new InvalidArgumentException(sprintf('Unknown class "%sPeer".', $class));
256         }
257
258         call_user_func(array(constant($class.'::PEER'), 'doDeleteAll'), $this->con);
259
260         $this->deletedClasses[] = $class;
261       }
262     }
263   }
264
265   /**
266    * Loads all map builders.
267    *
268    * @throws sfException If the class cannot be found
269    */
270   protected function loadMapBuilders()
271   {
272     $dbMap = Propel::getDatabaseMap();
273     $files = sfFinder::type('file')->name('*TableMap.php')->in(sfProjectConfiguration::getActive()->getModelDirs());
274     foreach ($files as $file)
275     {
276       $omClass = basename($file, 'TableMap.php');
277       if (class_exists($omClass) && is_subclass_of($omClass, 'BaseObject'))
278       {
279         $tableMapClass = basename($file, '.php');
280         $dbMap->addTableFromMapClass($tableMapClass);
281       }
282     }
283   }
284
285   /**
286    * Dumps data to fixture from one or more tables.
287    *
288    * @param string $directoryOrFile   The directory or file to dump to
289    * @param mixed  $tables            The name or names of tables to dump (or all to dump all tables)
290    * @param string $connectionName    The connection name (default to propel)
291    */
292   public function dumpData($directoryOrFile, $tables = 'all', $connectionName = 'propel')
293   {
294     $dumpData = $this->getData($tables, $connectionName);
295
296     // save to file(s)
297     if (!is_dir($directoryOrFile))
298     {
299       file_put_contents($directoryOrFile, sfYaml::dump($dumpData, 3));
300     }
301     else
302     {
303       $i = 0;
304       foreach ($tables as $tableName)
305       {
306         if (!isset($dumpData[$tableName]))
307         {
308           continue;
309         }
310
311         file_put_contents(sprintf("%s/%03d-%s.yml", $directoryOrFile, ++$i, $tableName), sfYaml::dump(array($tableName => $dumpData[$tableName]), 3));
312       }
313     }
314   }
315
316   /**
317    * Returns data from one or more tables.
318    *
319    * @param  mixed  $tables           name or names of tables to dump (or all to dump all tables)
320    * @param  string $connectionName   connection name
321    *
322    * @return array  An array of database data
323    */
324   public function getData($tables = 'all', $connectionName = 'propel')
325   {
326     $this->loadMapBuilders();
327     $this->con = Propel::getConnection($connectionName);
328     $this->dbMap = Propel::getDatabaseMap($connectionName);
329
330     // get tables
331     if ('all' === $tables || null === $tables)
332     {
333       $tables = array();
334       foreach ($this->dbMap->getTables() as $table)
335       {
336         $tables[] = $table->getPhpName();
337       }
338     }
339     else if (!is_array($tables))
340     {
341       $tables = array($tables);
342     }
343
344     $dumpData = array();
345
346     $tables = $this->fixOrderingOfForeignKeyData($tables);
347     foreach ($tables as $tableName)
348     {
349       $tableMap = $this->dbMap->getTable(constant(constant($tableName.'::PEER').'::TABLE_NAME'));
350       $hasParent = false;
351       $haveParents = false;
352       $fixColumn = null;
353       foreach ($tableMap->getColumns() as $column)
354       {
355         $col = strtolower($column->getName());
356         if ($column->isForeignKey())
357         {
358           $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
359           if ($tableName === $relatedTable->getPhpName())
360           {
361             if ($hasParent)
362             {
363               $haveParents = true;
364             }
365             else
366             {
367               $fixColumn = $column;
368               $hasParent = true;
369             }
370           }
371         }
372       }
373
374       if ($haveParents)
375       {
376         // unable to dump tables having multi-recursive references
377         continue;
378       }
379
380       // get db info
381       $resultsSets = array();
382       if ($hasParent)
383       {
384         $resultsSets[] = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $fixColumn);
385       }
386       else
387       {
388         $in = array();
389         foreach ($tableMap->getColumns() as $column)
390         {
391           $in[] = strtolower($column->getName());
392         }
393         $stmt = $this->con->query(sprintf('SELECT %s FROM %s', implode(',', $in), constant(constant($tableName.'::PEER').'::TABLE_NAME')));
394
395         $resultsSets[] = $stmt->fetchAll(PDO::FETCH_ASSOC);
396         $stmt->closeCursor();
397         unset($stmt);
398       }
399
400       foreach ($resultsSets as $rows)
401       {
402         if(count($rows) > 0 && !isset($dumpData[$tableName]))
403         {
404           $dumpData[$tableName] = array();
405
406           foreach ($rows as $row)
407           {
408             $pk = $tableName;
409             $values = array();
410             $primaryKeys = array();
411             $foreignKeys = array();
412
413             foreach ($tableMap->getColumns() as $column)
414             {
415               $col = strtolower($column->getName());
416               $isPrimaryKey = $column->isPrimaryKey();
417
418               if (null === $row[$col])
419               {
420                 continue;
421               }
422
423               if ($isPrimaryKey)
424               {
425                 $value = $row[$col];
426                 $pk .= '_'.$value;
427                 $primaryKeys[$col] = $value;
428               }
429
430               if ($column->isForeignKey())
431               {
432                 $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
433                 if ($isPrimaryKey)
434                 {
435                   $foreignKeys[$col] = $row[$col];
436                   $primaryKeys[$col] = $relatedTable->getPhpName().'_'.$row[$col];
437                 }
438                 else
439                 {
440                   $values[$col] = $relatedTable->getPhpName().'_'.$row[$col];
441
442                   $values[$col] = strlen($row[$col]) ? $relatedTable->getPhpName().'_'.$row[$col] : '';
443                 }
444               }
445               elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator()))
446               {
447                 // We did not want auto incremented primary keys
448                 $values[$col] = $row[$col];
449               }
450             }
451
452             if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0))
453             {
454               $values = array_merge($primaryKeys, $values);
455             }
456
457             $dumpData[$tableName][$pk] = $values;
458           }
459         }
460       }
461     }
462
463     return $dumpData;
464   }
465
466   /**
467    * Fixes the ordering of foreign key data, by outputting data a foreign key depends on before the table with the foreign key.
468    *
469    * @param array $classes The array with the class names.
470    */
471   public function fixOrderingOfForeignKeyData($classes)
472   {
473     // reordering classes to take foreign keys into account
474     for ($i = 0, $count = count($classes); $i < $count; $i++)
475     {
476       $class = $classes[$i];
477       $tableMap = $this->dbMap->getTable(constant(constant($class.'::PEER').'::TABLE_NAME'));
478       foreach ($tableMap->getColumns() as $column)
479       {
480         if ($column->isForeignKey())
481         {
482           $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
483           $relatedTablePos = array_search($relatedTable->getPhpName(), $classes);
484
485           // check if relatedTable is after the current table
486           if ($relatedTablePos > $i)
487           {
488             // move related table 1 position before current table
489             $classes = array_merge(
490               array_slice($classes, 0, $i),
491               array($classes[$relatedTablePos]),
492               array_slice($classes, $i, $relatedTablePos - $i),
493               array_slice($classes, $relatedTablePos + 1)
494             );
495
496             // we have moved a table, so let's see if we are done
497             return $this->fixOrderingOfForeignKeyData($classes);
498           }
499         }
500       }
501     }
502
503     return $classes;
504   }
505
506   protected function fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in = null)
507   {
508     $sql = sprintf('SELECT * FROM %s WHERE %s %s',
509                    constant(constant($tableName.'::PEER').'::TABLE_NAME'),
510                    strtolower($column->getName()),
511                    null === $in ? 'IS NULL' : 'IN ('.$in.')');
512     $stmt = $this->con->prepare($sql);
513
514     $stmt->execute();
515
516     $in = array();
517     while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
518     {
519       $in[] = "'".$row[strtolower($column->getRelatedColumnName())]."'";
520       $resultsSets[] = $row;
521     }
522
523     if ($in = implode(', ', $in))
524     {
525       $resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in);
526     }
527
528     return $resultsSets;
529   }
530 }
531
Note: See TracBrowser for help on using the browser.