Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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: sfPropelData.class.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
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
}