Development

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

You must first sign up to be able to contribute.

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

Revision 21894, 15.7 kB (checked in by fabien, 5 years ago)

[1.2, 1.3] fixed propel:data-dump fails to dump columns with capital letters in the column name (closes #7126)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
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() && !is_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     $files = sfFinder::type('file')->name('*MapBuilder.php')->in(sfProjectConfiguration::getActive()->getModelDirs());
273     foreach ($files as $file)
274     {
275       $omClass = basename($file, 'MapBuilder.php');
276       if (class_exists($omClass) && is_subclass_of($omClass, 'BaseObject'))
277       {
278         $mapBuilderClass = basename($file, '.php');
279         $map = new $mapBuilderClass();
280         if (!$map->isBuilt())
281         {
282           $map->doBuild();
283         }
284       }
285     }
286   }
287
288   /**
289    * Dumps data to fixture from one or more tables.
290    *
291    * @param string $directoryOrFile   The directory or file to dump to
292    * @param mixed  $tables            The name or names of tables to dump (or all to dump all tables)
293    * @param string $connectionName    The connection name (default to propel)
294    */
295   public function dumpData($directoryOrFile, $tables = 'all', $connectionName = 'propel')
296   {
297     $dumpData = $this->getData($tables, $connectionName);
298
299     // save to file(s)
300     if (!is_dir($directoryOrFile))
301     {
302       file_put_contents($directoryOrFile, sfYaml::dump($dumpData, 3));
303     }
304     else
305     {
306       $i = 0;
307       foreach ($tables as $tableName)
308       {
309         if (!isset($dumpData[$tableName]))
310         {
311           continue;
312         }
313
314         file_put_contents(sprintf("%s/%03d-%s.yml", $directoryOrFile, ++$i, $tableName), sfYaml::dump(array($tableName => $dumpData[$tableName]), 3));
315       }
316     }
317   }
318
319   /**
320    * Returns data from one or more tables.
321    *
322    * @param  mixed  $tables           name or names of tables to dump (or all to dump all tables)
323    * @param  string $connectionName   connection name
324    *
325    * @return array  An array of database data
326    */
327   public function getData($tables = 'all', $connectionName = 'propel')
328   {
329     $this->loadMapBuilders();
330     $this->con = Propel::getConnection($connectionName);
331     $this->dbMap = Propel::getDatabaseMap($connectionName);
332
333     // get tables
334     if ('all' === $tables || is_null($tables))
335     {
336       $tables = array();
337       foreach ($this->dbMap->getTables() as $table)
338       {
339         $tables[] = $table->getPhpName();
340       }
341     }
342     else if (!is_array($tables))
343     {
344       $tables = array($tables);
345     }
346
347     $dumpData = array();
348
349     $tables = $this->fixOrderingOfForeignKeyData($tables);
350     foreach ($tables as $tableName)
351     {
352       $tableMap = $this->dbMap->getTable(constant(constant($tableName.'::PEER').'::TABLE_NAME'));
353       $hasParent = false;
354       $haveParents = false;
355       $fixColumn = null;
356       foreach ($tableMap->getColumns() as $column)
357       {
358         $col = strtolower($column->getName());
359         if ($column->isForeignKey())
360         {
361           $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
362           if ($tableName === $relatedTable->getPhpName())
363           {
364             if ($hasParent)
365             {
366               $haveParents = true;
367             }
368             else
369             {
370               $fixColumn = $column;
371               $hasParent = true;
372             }
373           }
374         }
375       }
376
377       if ($haveParents)
378       {
379         // unable to dump tables having multi-recursive references
380         continue;
381       }
382
383       // get db info
384       $resultsSets = array();
385       if ($hasParent)
386       {
387         $resultsSets[] = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $fixColumn);
388       }
389       else
390       {
391         $in = array();
392         foreach ($tableMap->getColumns() as $column)
393         {
394           $in[] = strtolower($column->getName());
395         }
396         $stmt = $this->con->query(sprintf('SELECT %s FROM %s', implode(',', $in), constant(constant($tableName.'::PEER').'::TABLE_NAME')));
397
398         $resultsSets[] = $stmt->fetchAll(PDO::FETCH_ASSOC);
399         $stmt->closeCursor();
400         unset($stmt);
401       }
402
403       foreach ($resultsSets as $rows)
404       {
405         if(count($rows) > 0 && !isset($dumpData[$tableName]))
406         {
407           $dumpData[$tableName] = array();
408
409           foreach ($rows as $row)
410           {
411             $pk = $tableName;
412             $values = array();
413             $primaryKeys = array();
414             $foreignKeys = array();
415
416             foreach ($tableMap->getColumns() as $column)
417             {
418               $col = strtolower($column->getName());
419               $isPrimaryKey = $column->isPrimaryKey();
420
421               if (is_null($row[$col]))
422               {
423                 continue;
424               }
425
426               if ($isPrimaryKey)
427               {
428                 $value = $row[$col];
429                 $pk .= '_'.$value;
430                 $primaryKeys[$col] = $value;
431               }
432
433               if ($column->isForeignKey())
434               {
435                 $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
436                 if ($isPrimaryKey)
437                 {
438                   $foreignKeys[$col] = $row[$col];
439                   $primaryKeys[$col] = $relatedTable->getPhpName().'_'.$row[$col];
440                 }
441                 else
442                 {
443                   $values[$col] = $relatedTable->getPhpName().'_'.$row[$col];
444
445                   $values[$col] = strlen($row[$col]) ? $relatedTable->getPhpName().'_'.$row[$col] : '';
446                 }
447               }
448               elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator()))
449               {
450                 // We did not want auto incremented primary keys
451                 $values[$col] = $row[$col];
452               }
453             }
454
455             if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0))
456             {
457               $values = array_merge($primaryKeys, $values);
458             }
459
460             $dumpData[$tableName][$pk] = $values;
461           }
462         }
463       }
464     }
465
466     return $dumpData;
467   }
468
469   /**
470    * Fixes the ordering of foreign key data, by outputting data a foreign key depends on before the table with the foreign key.
471    *
472    * @param array $classes The array with the class names.
473    */
474   public function fixOrderingOfForeignKeyData($classes)
475   {
476     // reordering classes to take foreign keys into account
477     for ($i = 0, $count = count($classes); $i < $count; $i++)
478     {
479       $class = $classes[$i];
480       $tableMap = $this->dbMap->getTable(constant(constant($class.'::PEER').'::TABLE_NAME'));
481       foreach ($tableMap->getColumns() as $column)
482       {
483         if ($column->isForeignKey())
484         {
485           $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
486           $relatedTablePos = array_search($relatedTable->getPhpName(), $classes);
487
488           // check if relatedTable is after the current table
489           if ($relatedTablePos > $i)
490           {
491             // move related table 1 position before current table
492             $classes = array_merge(
493               array_slice($classes, 0, $i),
494               array($classes[$relatedTablePos]),
495               array_slice($classes, $i, $relatedTablePos - $i),
496               array_slice($classes, $relatedTablePos + 1)
497             );
498
499             // we have moved a table, so let's see if we are done
500             return $this->fixOrderingOfForeignKeyData($classes);
501           }
502         }
503       }
504     }
505
506     return $classes;
507   }
508
509   protected function fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in = null)
510   {
511     $sql = sprintf('SELECT * FROM %s WHERE %s %s',
512                    constant(constant($tableName.'::PEER').'::TABLE_NAME'),
513                    strtolower($column->getName()),
514                    is_null($in) ? 'IS NULL' : 'IN ('.$in.')');
515     $stmt = $this->con->prepare($sql);
516
517     $stmt->execute();
518
519     $in = array();
520     while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
521     {
522       $in[] = "'".$row[strtolower($column->getRelatedColumnName())]."'";
523       $resultsSets[] = $row;
524     }
525
526     if ($in = implode(', ', $in))
527     {
528       $resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in);
529     }
530
531     return $resultsSets;
532   }
533 }
534
Note: See TracBrowser for help on using the browser.