. */ /** * Doctrine_Data_Import * * @package Doctrine * @package Data * @author Jonathan H. Wage * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 * @version $Revision: 2552 $ */ class Doctrine_Data_Import extends Doctrine_Data { /** * Array of imported objects for processing and saving * * @var array */ protected $_importedObjects = array(); /** * Array of the raw data parsed from yaml * * @var array */ protected $_rows = array(); /** * Optionally pass the directory/path to the yaml for importing * * @param string $directory * @return void */ public function __construct($directory = null) { if ($directory !== null) { $this->setDirectory($directory); } } /** * Do the parsing of the yaml files and return the final parsed array * * @return array $array */ public function doParsing() { $recursiveMerge = Doctrine_Manager::getInstance()->getAttribute('recursive_merge_fixtures'); $mergeFunction = $recursiveMerge === true ? 'array_merge_recursive':'array_merge'; $directory = $this->getDirectory(); $array = array(); if ($directory !== null) { foreach ((array) $directory as $dir) { $e = explode('.', $dir); // If they specified a specific yml file if (end($e) == 'yml') { $array = $mergeFunction($array, Doctrine_Parser::load($dir, $this->getFormat())); // If they specified a directory } else if(is_dir($dir)) { $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY); foreach ($it as $file) { $e = explode('.', $file->getFileName()); if (in_array(end($e), $this->getFormats())) { $array = $mergeFunction($array, Doctrine_Parser::load($file->getPathName(), $this->getFormat())); } } } } } return $array; } /** * Do the importing of the data parsed from the fixtures * * @return void */ public function doImport($append = false) { $array = $this->doParsing(); if ( ! $append) { $this->purge(array_reverse(array_keys($array))); } $this->_loadData($array); } /** * Recursively loop over all data fixtures and build the array of className rows * * @return void */ protected function _buildRows($className, $data) { foreach ($data as $rowKey => $row) { // do the same for the row information $this->_rows[$className][$rowKey] = $row; foreach ((array) $row as $key => $value) { if (Doctrine::getTable($className)->hasRelation($key) && is_array($value)) { $keys = array_keys($value); // Skip associative arrays defining keys to relationships if ( ! isset($keys[0])) { $this->_buildRows(Doctrine::getTable($className)->getRelation($key)->getTable()->getOption('name'), $value); } } } } } /** * Build the rows for nested set models * * @return void */ protected function _buildNestedSetRows($className, $data) { foreach ($data as $rowKey => $row) { $children = isset($row['children']) ? $row['children']:array(); unset($row['children']); $this->_rows[$className][$rowKey] = $row; $this->_buildNestedSetRows($className, $children); } } /** * Get the unsaved object for a specified row key and validate that it is the valid object class * for the passed record and relation name * * @param string $rowKey * @param Doctrine_Record $record * @param string $relationName * @param string $referringRowKey * @return Doctrine_Record * @throws Doctrine_Data_Exception */ protected function _getImportedObject($rowKey, Doctrine_Record $record, $relationName, $referringRowKey) { if ( ! isset($this->_importedObjects[$rowKey])) { throw new Doctrine_Data_Exception( sprintf('Invalid row key specified: %s, referred to in %s', $rowKey, $referringRowKey) ); } $relatedRowKeyObject = $this->_importedObjects[$rowKey]; $relation = $record->getTable()->getRelation($relationName); if ($relation->getClass() !== get_class($relatedRowKeyObject)) { if ( ! is_subclass_of($relatedRowKeyObject, $relation->getClass())) { throw new Doctrine_Data_Exception(sprintf( 'Class referred to in "%s" is expected to be "%s" and "%s" was given', $referringRowKey, $relation->getClass(), get_class($relatedRowKeyObject))); } } return $relatedRowKeyObject; } /** * Process a row and make all the appropriate relations between the imported data * * @param string $rowKey * @param string $row * @return void */ protected function _processRow($rowKey, $row) { $obj = $this->_importedObjects[$rowKey]; foreach ((array) $row as $key => $value) { if (method_exists($obj, 'set' . Doctrine_Inflector::classify($key))) { $func = 'set' . Doctrine_Inflector::classify($key); $obj->$func($value); } else if ($obj->getTable()->hasField($key)) { if ($obj->getTable()->getTypeOf($key) == 'object') { $value = unserialize($value); } $obj->set($key, $value); } else if ($obj->getTable()->hasRelation($key)) { if (is_array($value)) { if (isset($value[0]) && ! is_array($value[0])) { foreach ($value as $link) { if ($obj->getTable()->getRelation($key)->getType() === Doctrine_Relation::ONE) { $obj->set($key, $this->_getImportedObject($link, $obj, $key, $rowKey)); } else if ($obj->getTable()->getRelation($key)->getType() === Doctrine_Relation::MANY) { $relation = $obj->$key; $relation[] = $this->_getImportedObject($link, $obj, $key, $rowKey); } } } else { $obj->$key->fromArray($value); } } else { $obj->set($key, $this->_getImportedObject($value, $obj, $key, $rowKey)); } } else { try { $obj->$key = $value; } catch (Exception $e) { // used for Doctrine plugin methods (Doctrine_Template) if (is_callable(array($obj, 'set' . Doctrine_Inflector::classify($key)))) { $func = 'set' . Doctrine_Inflector::classify($key); $obj->$func($value); } else { throw new Doctrine_Data_Exception('Invalid fixture element "'. $key . '" under "' . $rowKey . '"'); } } } } } /** * NestedSet fixtures may come in a 'natural' format with nested children listed under a 'children' * key or in a raw, non-nested format with lft/rgt values. * * This method returns true if the given $data is a nested set in 'natural' form. * * @param $className * @param $data * @return boolean */ protected function _hasNaturalNestedSetFormat($className, array &$data) { if (Doctrine::getTable($className)->isTree()) { if (isset($data['NestedSet']) && $data['NestedSet'] == true) { unset($data['NestedSet']); return true; } else { $first = current($data); return array_key_exists('children', $first); } } else { return false; } } /** * Perform the loading of the data from the passed array * * @param string $array * @return void */ protected function _loadData(array $array) { $nestedSets = array(); $specifiedModels = $this->getModels(); $rows = array(); foreach ($array as $className => $data) { if ( ! empty($specifiedModels) && !in_array($className, $specifiedModels)) { continue; } // if loaded data is a nested set in natural format, process through _buildNestedSetRows. // 'raw' nested sets and all other models are processed through _buildRows. if ($this->_hasNaturalNestedSetFormat($className, $data)) { $nestedSets[$className][] = $data; $this->_buildNestedSetRows($className, $data); } else { $this->_buildRows($className, $data); } } $buildRows = array(); foreach ($this->_rows as $className => $classRows) { foreach ($classRows as $rowKey => $row) { $buildRows[$rowKey] = $row; $this->_importedObjects[$rowKey] = new $className(); $this->_importedObjects[$rowKey]->state('TDIRTY'); } } foreach($buildRows as $rowKey => $row) { $this->_processRow($rowKey, $row); } // save natural nested set fixture data and unset from _importedObjects foreach ($nestedSets as $className => $sets) { foreach ($sets as $data) { $this->_loadNestedSetData($className, $data); } } $objects = array(); foreach ($this->_importedObjects as $object) { $className = get_class($object); $objects[$className] = $className; } $manager = Doctrine_Manager::getInstance(); foreach ($manager as $connection) { $tree = $connection->unitOfWork->buildFlushTree($objects); foreach ($tree as $model) { foreach ($this->_importedObjects as $obj) { if ($obj instanceof $model) { $obj->save(); } } } } } /** * Load nested set data for models with nested set enabled * * @param string $model * @param string $nestedSetData * @param string $parent * @return void */ protected function _loadNestedSetData($model, $nestedSetData, $parent = null) { foreach($nestedSetData AS $rowKey => $nestedSet) { $children = array(); $data = array(); if (array_key_exists('children', $nestedSet)) { $children = (array) $nestedSet['children']; $children = array_reverse($children, true); unset($nestedSet['children']); } $record = $this->_importedObjects[$rowKey]; // remove this nested set from _importedObjects so it's not processed in the save routine for normal objects unset($this->_importedObjects[$rowKey]); if( ! $parent) { $record->save(); // save, so that createRoot can do: root id = id Doctrine::getTable($model)->getTree()->createRoot($record); } else { $parent->getNode()->addChild($record); } if (is_array($children) AND !empty($children)) { $this->_loadNestedSetData($model, $children, $record); } } } }