Files
pae.co.at/trunk copy/proactiveenglish/lib/symfony/addon/propel/sfPropelData.class.php
2017-03-02 21:57:21 -06:00

482 lines
14 KiB
PHP

<?php
/*
* This file is part of the symfony package.
* (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* This class is the Propel implementation of sfData. It interacts with the data source
* and loads data.
*
* @package symfony
* @subpackage addon
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @version SVN: $Id: sfPropelData.class.php 8288 2008-04-04 13:55:10Z noel $
*/
class sfPropelData extends sfData
{
protected
$maps = array(),
$deletedClasses = array(),
$con = null;
// symfony load-data (file|dir)
/**
* Loads data from a file or directory into a Propel data source
*
* @param mixed A file or directory path
* @param string The Propel connection name, default 'propel'
*
* @throws Exception If the database throws an error, rollback transaction and rethrows exception
*/
public function loadData($directory_or_file = null, $connectionName = 'propel')
{
$fixture_files = $this->getFiles($directory_or_file);
// wrap all database operations in a single transaction
$this->con = Propel::getConnection($connectionName);
try
{
$this->con->begin();
$this->doDeleteCurrentData($fixture_files);
$this->doLoadData($fixture_files);
$this->con->commit();
}
catch (Exception $e)
{
$this->con->rollback();
throw $e;
}
}
/**
* Implements the abstract loadDataFromArray method and loads the data using the generated data model.
*
* @param array The data to be loaded into the data source
*
* @throws Exception If data is unnamed.
* @throws sfException If an object defined in the model does not exist in the data
* @throws sfException If a column that does not exist is referenced
*/
public function loadDataFromArray($data)
{
if ($data === null)
{
// no data
return;
}
foreach ($data as $class => $datas)
{
$class = trim($class);
$peer_class = $class.'Peer';
// load map class
$this->loadMapBuilder($class);
$tableMap = $this->maps[$class]->getDatabaseMap()->getTable(constant($peer_class.'::TABLE_NAME'));
$column_names = call_user_func_array(array($peer_class, 'getFieldNames'), array(BasePeer::TYPE_FIELDNAME));
// iterate through datas for this class
// might have been empty just for force a table to be emptied on import
if (!is_array($datas))
{
continue;
}
foreach ($datas as $key => $data)
{
// create a new entry in the database
$obj = new $class();
if (!$obj instanceof BaseObject)
{
throw new Exception(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));
}
if (!is_array($data))
{
throw new Exception(sprintf('You must give a name for each fixture data entry (class %s)', $class));
}
foreach ($data as $name => $value)
{
$isARealColumn = true;
try
{
$column = $tableMap->getColumn($name);
}
catch (PropelException $e)
{
$isARealColumn = false;
}
// foreign key?
if ($isARealColumn)
{
if ($column->isForeignKey() && !is_null($value))
{
$relatedTable = $this->maps[$class]->getDatabaseMap()->getTable($column->getRelatedTableName());
if (!isset($this->object_references[$relatedTable->getPhpName().'_'.$value]))
{
throw new sfException(sprintf('The object "%s" from class "%s" is not defined in your data file.', $value, $relatedTable->getPhpName()));
}
$value = $this->object_references[$relatedTable->getPhpName().'_'.$value];
}
}
if (false !== $pos = array_search($name, $column_names))
{
$obj->setByPosition($pos, $value);
}
else if (is_callable(array($obj, $method = 'set'.sfInflector::camelize($name))))
{
$obj->$method($value);
}
else
{
$error = 'Column "%s" does not exist for class "%s"';
$error = sprintf($error, $name, $class);
throw new sfException($error);
}
}
$obj->save($this->con);
// save the id for future reference
if (method_exists($obj, 'getPrimaryKey'))
{
$this->object_references[$class.'_'.$key] = $obj->getPrimaryKey();
}
}
}
}
/**
* Clears existing data from the data source by reading the fixture files
* and deleting the existing data for only those classes that are mentioned
* in the fixtures.
*
* @param array The list of YAML files.
*
* @throws sfException If a class mentioned in a fixture can not be found
*/
protected function doDeleteCurrentData($fixture_files)
{
// delete all current datas in database
if (!$this->deleteCurrentData)
{
return;
}
rsort($fixture_files);
foreach ($fixture_files as $fixture_file)
{
$data = sfYaml::load($fixture_file);
if ($data === null)
{
// no data
continue;
}
$classes = array_keys($data);
krsort($classes);
foreach ($classes as $class)
{
$class = trim($class);
if (in_array($class, $this->deletedClasses))
{
continue;
}
$peer_class = $class.'Peer';
if (!$classPath = sfCore::getClassPath($peer_class))
{
throw new sfException(sprintf('Unable to find path for class "%s".', $peer_class));
}
require_once($classPath);
call_user_func(array($peer_class, 'doDeleteAll'), $this->con);
$this->deletedClasses[] = $class;
}
}
}
/**
* Loads the mappings for the classes
*
* @param string The model class name
*
* @throws sfException If the class cannot be found
*/
protected function loadMapBuilder($class)
{
$mapBuilderClass = $class.'MapBuilder';
if (!isset($this->maps[$class]))
{
if (!$classPath = sfCore::getClassPath($mapBuilderClass))
{
throw new sfException(sprintf('Unable to find path for class "%s".', $mapBuilderClass));
}
require_once($classPath);
$this->maps[$class] = new $mapBuilderClass();
$this->maps[$class]->doBuild();
}
}
/**
* Dumps data to fixture from one or more tables.
*
* @param string directory or file to dump to
* @param mixed name or names of tables to dump (or all to dump all tables)
* @param string connection name
*/
public function dumpData($directory_or_file = null, $tables = 'all', $connectionName = 'propel')
{
$sameFile = true;
if (is_dir($directory_or_file))
{
// multi files
$sameFile = false;
}
else
{
// same file
// delete file
}
$this->con = Propel::getConnection($connectionName);
// get tables
if ('all' === $tables || is_null($tables))
{
// load all map builder classes
$files = sfFinder::type('file')->ignore_version_control()->name('*MapBuilder.php')->in(sfLoader::getModelDirs());
foreach ($files as $file)
{
$mapBuilderClass = basename($file, '.php');
$map = new $mapBuilderClass();
$map->doBuild();
}
$dbMap = Propel::getDatabaseMap($connectionName);
$tables = array();
foreach ($dbMap->getTables() as $table)
{
$tables[] = $table->getPhpName();
}
}
else if (!is_array($tables))
{
$tables = array($tables);
}
$dumpData = array();
// load map classes
array_walk($tables, array($this, 'loadMapBuilder'));
$tables = $this->fixOrderingOfForeignKeyData($tables);
foreach ($tables as $tableName)
{
$tableMap = $this->maps[$tableName]->getDatabaseMap()->getTable(constant($tableName.'Peer::TABLE_NAME'));
$hasParent = false;
$haveParents = false;
$fixColumn = null;
foreach ($tableMap->getColumns() as $column)
{
$col = strtolower($column->getColumnName());
if ($column->isForeignKey())
{
$relatedTable = $this->maps[$tableName]->getDatabaseMap()->getTable($column->getRelatedTableName());
if ($tableName === $relatedTable->getPhpName())
{
if ($hasParent)
{
$haveParents = true;
}
else
{
$fixColumn = $column;
$hasParent = true;
}
}
}
}
if ($haveParents)
{
// unable to dump tables having multi-recursive references
continue;
}
// get db info
$resultsSets = array();
if ($hasParent)
{
$resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $fixColumn);
}
else
{
$resultsSets[] = $this->con->executeQuery('SELECT * FROM '.constant($tableName.'Peer::TABLE_NAME'));
}
foreach ($resultsSets as $rs)
{
if($rs->getRecordCount() > 0 && !isset($dumpData[$tableName])){
$dumpData[$tableName] = array();
}
while ($rs->next())
{
$pk = $tableName;
$values = array();
$primaryKeys = array();
$foreignKeys = array();
foreach ($tableMap->getColumns() as $column)
{
$col = strtolower($column->getColumnName());
$isPrimaryKey = $column->isPrimaryKey();
if (is_null($rs->get($col)))
{
continue;
}
if ($isPrimaryKey)
{
$value = $rs->get($col);
$pk .= '_'.$value;
$primaryKeys[$col] = $value;
}
if ($column->isForeignKey())
{
$relatedTable = $this->maps[$tableName]->getDatabaseMap()->getTable($column->getRelatedTableName());
if ($isPrimaryKey)
{
$foreignKeys[$col] = $rs->get($col);
$primaryKeys[$col] = $relatedTable->getPhpName().'_'.$rs->get($col);
}
else
{
$values[$col] = $relatedTable->getPhpName().'_'.$rs->get($col);
}
}
elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator()))
{
// We did not want auto incremented primary keys
$values[$col] = $rs->get($col);
}
}
if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0))
{
$values = array_merge($primaryKeys, $values);
}
$dumpData[$tableName][$pk] = $values;
}
}
}
// save to file(s)
if ($sameFile)
{
file_put_contents($directory_or_file, Spyc::YAMLDump($dumpData));
}
else
{
$i = 0;
foreach ($tables as $tableName)
{
if (!isset($dumpData[$tableName]))
{
continue;
}
file_put_contents(sprintf("%s/%03d-%s.yml", $directory_or_file, ++$i, $tableName), Spyc::YAMLDump(array($tableName => $dumpData[$tableName])));
}
}
}
/**
* Fixes the ordering of foreign key data, by outputting data a foreign key depends on before the table with the foreign key.
*
* @param array The array with the class names.
*/
public function fixOrderingOfForeignKeyData($classes)
{
// reordering classes to take foreign keys into account
for ($i = 0, $count = count($classes); $i < $count; $i++)
{
$class = $classes[$i];
$tableMap = $this->maps[$class]->getDatabaseMap()->getTable(constant($class.'Peer::TABLE_NAME'));
foreach ($tableMap->getColumns() as $column)
{
if ($column->isForeignKey())
{
$relatedTable = $this->maps[$class]->getDatabaseMap()->getTable($column->getRelatedTableName());
$relatedTablePos = array_search($relatedTable->getPhpName(), $classes);
// check if relatedTable is after the current table
if ($relatedTablePos > $i)
{
// move related table 1 position before current table
$classes = array_merge(
array_slice($classes, 0, $i),
array($classes[$relatedTablePos]),
array_slice($classes, $i, $relatedTablePos - $i),
array_slice($classes, $relatedTablePos + 1)
);
// we have moved a table, so let's see if we are done
return $this->fixOrderingOfForeignKeyData($classes);
}
}
}
}
return $classes;
}
protected function fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in = null)
{
$rs = $this->con->executeQuery(sprintf('SELECT * FROM %s WHERE %s %s',
constant($tableName.'Peer::TABLE_NAME'),
strtolower($column->getColumnName()),
is_null($in) ? 'IS NULL' : 'IN ('.$in.')'
));
$in = array();
while ($rs->next())
{
$in[] = "'".$rs->get(strtolower($column->getRelatedColumnName()))."'";
}
if ($in = implode(', ', $in))
{
$rs->seek(0);
$resultsSets[] = $rs;
$resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in);
}
return $resultsSets;
}
}