Development

/branches/1.1/lib/plugins/sfPropelPlugin/lib/propel/sfPropelData.class.php

You must first sign up to be able to contribute.

root/branches/1.1/lib/plugins/sfPropelPlugin/lib/propel/sfPropelData.class.php

Revision 12957, 14.9 kB (checked in by hartym, 6 years ago)

[1.1] r12811 introduced a regression: fixtures order is totally random, as sfFinder returns directories entry in arbitrary order.

  • 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.
13  *
14  * It interacts with the data source and loads data.
15  *
16  * @package    symfony
17  * @subpackage propel
18  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19  * @version    SVN: $Id$
20  */
21 class sfPropelData extends sfData
22 {
23   protected
24     $deletedClasses = array(),
25     $con            = null;
26
27   /**
28    * Loads data from a file or directory into a Propel data source
29    *
30    * @param mixed   $directoryOrFile  A file or directory path
31    * @param string  $connectionName   The Propel connection name, default 'propel'
32    *
33    * @throws Exception If the database throws an error, rollback transaction and rethrows exception
34    */
35   public function loadData($directoryOrFile = null, $connectionName = 'propel')
36   {
37     $fixtureFiles = $this->getFiles($directoryOrFile);
38
39     // load map classes
40     $this->loadMapBuilders();
41     $this->dbMap = Propel::getDatabaseMap($connectionName);
42
43     // wrap all database operations in a single transaction
44     $this->con = Propel::getConnection($connectionName);
45     try
46     {
47       $this->con->begin();
48
49       $this->doDeleteCurrentData($fixtureFiles);
50
51       $this->doLoadData($fixtureFiles);
52
53       $this->con->commit();
54     }
55     catch (Exception $e)
56     {
57       $this->con->rollback();
58       throw $e;
59     }
60   }
61
62   /**
63    * Implements the abstract loadDataFromArray method and loads the data using the generated data model.
64    *
65    * @param array   $data  The data to be loaded into the data source
66    *
67    * @throws Exception If data is unnamed.
68    * @throws sfException If an object defined in the model does not exist in the data
69    * @throws sfException If a column that does not exist is referenced
70    */
71   public function loadDataFromArray($data)
72   {
73     if ($data === null)
74     {
75       // no data
76       return;
77     }
78
79     foreach ($data as $class => $datas)
80     {
81       $class = trim($class);
82
83       $tableMap = $this->dbMap->getTable(constant($class.'Peer::TABLE_NAME'));
84
85       $column_names = call_user_func_array(array($class.'Peer', 'getFieldNames'), array(BasePeer::TYPE_FIELDNAME));
86
87       // iterate through datas for this class
88       // might have been empty just for force a table to be emptied on import
89       if (!is_array($datas))
90       {
91         continue;
92       }
93
94       foreach ($datas as $key => $data)
95       {
96         // create a new entry in the database
97         if (!class_exists($class))
98         {
99           throw new InvalidArgumentException(sprintf('Unknown class "%s".', $class));
100         }
101
102         $obj = new $class();
103
104         if (!$obj instanceof BaseObject)
105         {
106           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));
107         }
108
109         if (!is_array($data))
110         {
111           throw new InvalidArgumentException(sprintf('You must give a name for each fixture data entry (class %s).', $class));
112         }
113
114         foreach ($data as $name => $value)
115         {
116           // will need to be updated for Propel 1.3
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[$class.'_'.$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(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 $fixtureFiles 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($fixtureFiles)
225   {
226     // delete all current datas in database
227     if (!$this->deleteCurrentData)
228     {
229       return;
230     }
231
232     rsort($fixtureFiles);
233     foreach ($fixtureFiles as $fixture_file)
234     {
235       $data = sfYaml::load($fixture_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($class.'Peer'))
254         {
255           throw new InvalidArgumentException(sprintf('Unknown class "%sPeer".', $class));
256         }
257
258         call_user_func(array($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       $mapBuilderClass = basename($file, '.php');
276       $map = new $mapBuilderClass();
277       $map->doBuild();
278     }
279   }
280
281   /**
282    * Dumps data to fixture from one or more tables.
283    *
284    * @param string $directoryOrFile   The directory or file to dump to
285    * @param mixed  $tables            The name or names of tables to dump (or all to dump all tables)
286    * @param string $connectionName    The connection name (default to propel)
287    */
288   public function dumpData($directoryOrFile, $tables = 'all', $connectionName = 'propel')
289   {
290     $dumpData = $this->getData($tables, $connectionName);
291
292     // save to file(s)
293     if (!is_dir($directoryOrFile))
294     {
295       file_put_contents($directoryOrFile, sfYaml::dump($dumpData, 3));
296     }
297     else
298     {
299       $i = 0;
300       foreach ($tables as $tableName)
301       {
302         if (!isset($dumpData[$tableName]))
303         {
304           continue;
305         }
306
307         file_put_contents(sprintf("%s/%03d-%s.yml", $directoryOrFile, ++$i, $tableName), sfYaml::dump(array($tableName => $dumpData[$tableName]), 3));
308       }
309     }
310   }
311
312   /**
313    * Returns data from one or more tables.
314    *
315    * @param  mixed  $tables           name or names of tables to dump (or all to dump all tables)
316    * @param  string $connectionName   connection name
317    *
318    * @return array  An array of database data
319    */
320   public function getData($tables = 'all', $connectionName = 'propel')
321   {
322     $this->loadMapBuilders();
323     $this->con = Propel::getConnection($connectionName);
324     $this->dbMap = Propel::getDatabaseMap($connectionName);
325
326     // get tables
327     if ('all' === $tables || is_null($tables))
328     {
329       $tables = array();
330       foreach ($this->dbMap->getTables() as $table)
331       {
332         $tables[] = $table->getPhpName();
333       }
334     }
335     else if (!is_array($tables))
336     {
337       $tables = array($tables);
338     }
339
340     $dumpData = array();
341
342     $tables = $this->fixOrderingOfForeignKeyData($tables);
343     foreach ($tables as $tableName)
344     {
345       $tableMap = $this->dbMap->getTable(constant($tableName.'Peer::TABLE_NAME'));
346       $hasParent = false;
347       $haveParents = false;
348       $fixColumn = null;
349       foreach ($tableMap->getColumns() as $column)
350       {
351         $col = strtolower($column->getColumnName());
352         if ($column->isForeignKey())
353         {
354           $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
355           if ($tableName === $relatedTable->getPhpName())
356           {
357             if ($hasParent)
358             {
359               $haveParents = true;
360             }
361             else
362             {
363               $fixColumn = $column;
364               $hasParent = true;
365             }
366           }
367         }
368       }
369
370       if ($haveParents)
371       {
372         // unable to dump tables having multi-recursive references
373         continue;
374       }
375
376       // get db info
377       $resultsSets = array();
378       if ($hasParent)
379       {
380         $resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $fixColumn);
381       }
382       else
383       {
384         $resultsSets[] = $this->con->executeQuery('SELECT * FROM '.constant($tableName.'Peer::TABLE_NAME'));
385       }
386
387       foreach ($resultsSets as $rs)
388       {
389         if($rs->getRecordCount() > 0 && !isset($dumpData[$tableName])){
390           $dumpData[$tableName] = array();
391         }
392
393         while ($rs->next())
394         {
395           $pk = $tableName;
396           $values = array();
397           $primaryKeys = array();
398           $foreignKeys = array();
399
400           foreach ($tableMap->getColumns() as $column)
401           {
402             $col = strtolower($column->getColumnName());
403             $isPrimaryKey = $column->isPrimaryKey();
404
405             if (is_null($rs->get($col)))
406             {
407               continue;
408             }
409
410             if ($isPrimaryKey)
411             {
412               $value = $rs->get($col);
413               $pk .= '_'.$value;
414               $primaryKeys[$col] = $value;
415             }
416
417             if ($column->isForeignKey())
418             {
419               $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
420               if ($isPrimaryKey)
421               {
422                 $foreignKeys[$col] = $rs->get($col);
423                 $primaryKeys[$col] = $relatedTable->getPhpName().'_'.$rs->get($col);
424               }
425               else
426               {
427                 $values[$col] = strlen($rs->get($col)) ? $relatedTable->getPhpName().'_'.$rs->get($col) : '';
428               }
429             }
430             elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator()))
431             {
432               // We did not want auto incremented primary keys
433               $values[$col] = $rs->get($col);
434             }
435           }
436
437           if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0))
438           {
439             $values = array_merge($primaryKeys, $values);
440           }
441
442           $dumpData[$tableName][$pk] = $values;
443         }
444       }
445     }
446
447     return $dumpData;
448   }
449
450   /**
451    * Fixes the ordering of foreign key data, by outputting data a foreign key depends on before the table with the foreign key.
452    *
453    * @param array $classes The array with the class names.
454    */
455   public function fixOrderingOfForeignKeyData($classes)
456   {
457     // reordering classes to take foreign keys into account
458     for ($i = 0, $count = count($classes); $i < $count; $i++)
459     {
460       $class = $classes[$i];
461       $tableMap = $this->dbMap->getTable(constant($class.'Peer::TABLE_NAME'));
462       foreach ($tableMap->getColumns() as $column)
463       {
464         if ($column->isForeignKey())
465         {
466           $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
467           $relatedTablePos = array_search($relatedTable->getPhpName(), $classes);
468
469           // check if relatedTable is after the current table
470           if ($relatedTablePos > $i)
471           {
472             // move related table 1 position before current table
473             $classes = array_merge(
474               array_slice($classes, 0, $i),
475               array($classes[$relatedTablePos]),
476               array_slice($classes, $i, $relatedTablePos - $i),
477               array_slice($classes, $relatedTablePos + 1)
478             );
479
480             // we have moved a table, so let's see if we are done
481             return $this->fixOrderingOfForeignKeyData($classes);
482           }
483         }
484       }
485     }
486
487     return $classes;
488   }
489
490   protected function fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in = null)
491   {
492     $rs = $this->con->executeQuery(sprintf('SELECT * FROM %s WHERE %s %s',
493       constant($tableName.'Peer::TABLE_NAME'),
494       strtolower($column->getColumnName()),
495       is_null($in) ? 'IS NULL' : 'IN ('.$in.')'
496     ));
497     $in = array();
498     while ($rs->next())
499     {
500       $in[] = "'".$rs->get(strtolower($column->getRelatedColumnName()))."'";
501     }
502
503     if ($in = implode(', ', $in))
504     {
505       $rs->seek(0);
506       $resultsSets[] = $rs;
507       $resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in);
508     }
509
510     return $resultsSets;
511   }
512 }
513
Note: See TracBrowser for help on using the browser.