Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: Import.php 2552 2007-09-19 19:33:00Z Jonathan.Wage $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
20
 */
21
 
22
/**
23
 * Doctrine_Data_Import
24
 *
25
 * @package     Doctrine
26
 * @package     Data
27
 * @author      Jonathan H. Wage <jwage@mac.com>
28
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
29
 * @link        www.doctrine-project.org
30
 * @since       1.0
31
 * @version     $Revision: 2552 $
32
 */
33
class Doctrine_Data_Import extends Doctrine_Data
34
{
35
    /**
36
     * Array of imported objects for processing and saving
37
     *
38
     * @var array
39
     */
40
    protected $_importedObjects = array();
41
 
42
    /**
43
     * Array of the raw data parsed from yaml
44
     *
45
     * @var array
46
     */
47
    protected $_rows = array();
48
 
49
    /**
50
     * Optionally pass the directory/path to the yaml for importing
51
     *
52
     * @param string $directory
53
     * @return void
54
     */
55
    public function __construct($directory = null)
56
    {
57
        if ($directory !== null) {
58
            $this->setDirectory($directory);
59
        }
60
    }
61
 
62
    /**
63
     * Do the parsing of the yaml files and return the final parsed array
64
     *
65
     * @return array $array
66
     */
67
    public function doParsing()
68
    {
69
        $recursiveMerge = Doctrine_Manager::getInstance()->getAttribute(Doctrine_Core::ATTR_RECURSIVE_MERGE_FIXTURES);
70
        $mergeFunction = $recursiveMerge === true ? 'array_merge_recursive':'array_merge';
71
        $directory = $this->getDirectory();
72
 
73
        $array = array();
74
 
75
        if ($directory !== null) {
76
            foreach ((array) $directory as $dir) {
77
                $e = explode('.', $dir);
78
 
79
                // If they specified a specific yml file
80
                if (end($e) == 'yml') {
81
                    $array = $mergeFunction($array, Doctrine_Parser::load($dir, $this->getFormat()));
82
                // If they specified a directory
83
                } else if (is_dir($dir)) {
84
                    $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir),
85
                                                            RecursiveIteratorIterator::LEAVES_ONLY);
86
                    $filesOrdered = array();
87
                    foreach ($it as $file) {
88
                        $filesOrdered[] = $file;
89
                    }
90
                    // force correct order
91
                    natcasesort($filesOrdered);
92
                    foreach ($filesOrdered as $file) {
93
                        $e = explode('.', $file->getFileName());
94
                        if (in_array(end($e), $this->getFormats())) {
95
                            $array = $mergeFunction($array, Doctrine_Parser::load($file->getPathName(), $this->getFormat()));
96
                        }
97
                    }
98
                }
99
            }
100
        }
101
 
102
        return $array;
103
    }
104
 
105
    /**
106
     * Do the importing of the data parsed from the fixtures
107
     *
108
     * @return void
109
     */
110
    public function doImport($append = false)
111
    {
112
        $array = $this->doParsing();
113
 
114
        if ( ! $append) {
115
            $this->purge(array_reverse(array_keys($array)));
116
        }
117
 
118
        $this->_loadData($array);
119
    }
120
 
121
    /**
122
     * Recursively loop over all data fixtures and build the array of className rows
123
     *
124
     * @return void
125
     */
126
    protected function _buildRows($className, $data)
127
    {
128
        $table = Doctrine_Core::getTable($className);
129
 
130
        foreach ($data as $rowKey => $row) {
131
            // do the same for the row information
132
            $this->_rows[$className][$rowKey] = $row;
133
 
134
            foreach ((array) $row as $key => $value) {
135
                if ($table->hasRelation($key) && is_array($value) && ! $table->hasTemplate('Doctrine_Template_I18n')) {
136
                    // Skip associative arrays defining keys to relationships
137
                    if ( ! isset($value[0]) || (isset($value[0]) && is_array($value[0]))) {
138
                        $rel = $table->getRelation($key);
139
                        $relClassName = $rel->getTable()->getOption('name');
140
                        $relRowKey = $rowKey . '_' . $relClassName;
141
 
142
                        if ($rel->getType() == Doctrine_Relation::ONE) {
143
                            $val = array($relRowKey => $value);
144
                            $this->_rows[$className][$rowKey][$key] = $relRowKey;
145
                        } else {
146
                            $val = $value;
147
                            $this->_rows[$className][$rowKey][$key] = array_keys($val);
148
                        }
149
 
150
                        $this->_buildRows($relClassName, $val);
151
                    }
152
                }
153
            }
154
        }
155
    }
156
 
157
    /**
158
     * Build the rows for nested set models
159
     *
160
     * @return void
161
     */
162
    protected function _buildNestedSetRows($className, $data)
163
    {
164
        foreach ($data as $rowKey => $row) {
165
            $children = isset($row['children']) ? $row['children']:array();
166
            unset($row['children']);
167
            $this->_rows[$className][$rowKey] = $row;
168
 
169
            $this->_buildNestedSetRows($className, $children);
170
        }
171
    }
172
 
173
    /**
174
     * Get the unsaved object for a specified row key and validate that it is the valid object class
175
     * for the passed record and relation name
176
     *
177
     * @param  string $rowKey
178
     * @param  Doctrine_Record $record
179
     * @param  string $relationName
180
     * @param  string $referringRowKey
181
     * @return Doctrine_Record
182
     * @throws Doctrine_Data_Exception
183
     */
184
    protected function _getImportedObject($rowKey, Doctrine_Record $record, $relationName, $referringRowKey)
185
    {
186
        $relation = $record->getTable()->getRelation($relationName);
187
        $rowKey = $this->_getRowKeyPrefix($relation->getTable()) . $rowKey;
188
 
189
        if ( ! isset($this->_importedObjects[$rowKey])) {
190
            throw new Doctrine_Data_Exception(
191
                sprintf('Invalid row key specified: %s, referred to in %s', $rowKey, $referringRowKey)
192
            );
193
        }
194
 
195
        $relatedRowKeyObject = $this->_importedObjects[$rowKey];
196
 
197
        $relationClass = $relation->getClass();
198
        if ( ! $relatedRowKeyObject instanceof $relationClass) {
199
            throw new Doctrine_Data_Exception(sprintf(
200
                'Class referred to in "%s" is expected to be "%s" and "%s" was given',
201
                $referringRowKey, $relation->getClass(), get_class($relatedRowKeyObject)
202
            ));
203
        }
204
 
205
        return $relatedRowKeyObject;
206
    }
207
 
208
    /**
209
     * Process a row and make all the appropriate relations between the imported data
210
     *
211
     * @param string $rowKey
212
     * @param string $row
213
     * @return void
214
     */
215
    protected function _processRow($rowKey, $row)
216
    {
217
        $obj = $this->_importedObjects[$rowKey];
218
 
219
        foreach ((array) $row as $key => $value) {
220
            if (method_exists($obj, 'set' . Doctrine_Inflector::classify($key))) {
221
                $func = 'set' . Doctrine_Inflector::classify($key);
222
                $obj->$func($value);
223
            } else if ($obj->getTable()->hasField($key)) {
224
                if ($obj->getTable()->getTypeOf($key) == 'object') {
225
                    $value = unserialize($value);
226
                }
227
                $obj->set($key, $value);
228
            } else if ($obj->getTable()->hasRelation($key)) {
229
                if (is_array($value)) {
230
                    if (isset($value[0]) && ! is_array($value[0])) {
231
                        foreach ($value as $link) {
232
                            if ($obj->getTable()->getRelation($key)->getType() === Doctrine_Relation::ONE) {
233
                                $obj->set($key, $this->_getImportedObject($link, $obj, $key, $rowKey));
234
                            } else if ($obj->getTable()->getRelation($key)->getType() === Doctrine_Relation::MANY) {
235
                                $relation = $obj->$key;
236
 
237
                                $relation[] = $this->_getImportedObject($link, $obj, $key, $rowKey);
238
                            }
239
                        }
240
                    } else {
241
                        $obj->$key->fromArray($value);
242
                    }
243
                } else {
244
                    $obj->set($key, $this->_getImportedObject($value, $obj, $key, $rowKey));
245
                }
246
            } else {
247
                try {
248
                    $obj->$key = $value;
249
                } catch (Exception $e) {
250
                    // used for Doctrine plugin methods (Doctrine_Template)
251
                    if (is_callable(array($obj, 'set' . Doctrine_Inflector::classify($key)))) {
252
                        $func = 'set' . Doctrine_Inflector::classify($key);
253
                        $obj->$func($value);
254
                    } else {
255
                        throw new Doctrine_Data_Exception('Invalid fixture element "'. $key . '" under "' . $rowKey . '"');
256
                    }
257
                }
258
            }
259
        }
260
    }
261
 
262
   /**
263
    * NestedSet fixtures may come in a 'natural' format with nested children listed under a 'children'
264
    * key or in a raw, non-nested format with lft/rgt values.
265
    *
266
    * This method returns true if the given $data is a nested set in 'natural' form.
267
    *
268
    * @param $className
269
    * @param $data
270
    * @return boolean
271
    */
272
    protected function _hasNaturalNestedSetFormat($className, array &$data)
273
    {
274
        if (Doctrine_Core::getTable($className)->isTree()) {
275
            if (isset($data['NestedSet']) && $data['NestedSet'] == true) {
276
                unset($data['NestedSet']);
277
                return true;
278
            } else {
279
                $first = current($data);
280
                return array_key_exists('children', $first);
281
            }
282
        } else {
283
            return false;
284
        }
285
    }
286
 
287
    /**
288
     * Perform the loading of the data from the passed array
289
     *
290
     * @param string $array
291
     * @return void
292
     */
293
    protected function _loadData(array $array)
294
    {
295
        $nestedSets = array();
296
 
297
        $specifiedModels = $this->getModels();
298
        $rows = array();
299
 
300
        foreach ($array as $className => $data) {
301
            if ( ! empty($specifiedModels) && !in_array($className, $specifiedModels)) {
302
                continue;
303
            }
304
 
305
            // if loaded data is a nested set in natural format, process through _buildNestedSetRows.
306
            // 'raw' nested sets and all other models are processed through _buildRows.
307
            if ($this->_hasNaturalNestedSetFormat($className, $data)) {
308
                $nestedSets[$className][] = $data;
309
                $this->_buildNestedSetRows($className, $data);
310
            } else {
311
                $this->_buildRows($className, $data);
312
            }
313
        }
314
 
315
        $buildRows = array();
316
        foreach ($this->_rows as $className => $classRows) {
317
            $rowKeyPrefix = $this->_getRowKeyPrefix(Doctrine_Core::getTable($className));
318
            foreach ($classRows as $rowKey => $row) {
319
                $rowKey = $rowKeyPrefix . $rowKey;
320
                $buildRows[$rowKey] = $row;
321
                $this->_importedObjects[$rowKey] = new $className();
322
                $this->_importedObjects[$rowKey]->state('TDIRTY');
323
            }
324
        }
325
 
326
        foreach($buildRows as $rowKey => $row) {
327
            $this->_processRow($rowKey, $row);
328
        }
329
 
330
        // save natural nested set fixture data and unset from _importedObjects
331
        foreach ($nestedSets as $className => $sets) {
332
            foreach ($sets as $data) {
333
                $this->_loadNestedSetData($className, $data);
334
            }
335
        }
336
 
337
        $manager = Doctrine_Manager::getInstance();
338
        foreach ($manager as $connection) {
339
            $tree = $connection->unitOfWork->buildFlushTree(array_keys($array));
340
 
341
            foreach ($tree as $model) {
342
                foreach ($this->_importedObjects as $obj) {
343
 
344
                    if ($obj instanceof $model) {
345
                        $obj->save();
346
                    }
347
                }
348
            }
349
        }
350
 
351
    }
352
 
353
    /**
354
     * Load nested set data for models with nested set enabled
355
     *
356
     * @param string $model
357
     * @param string $nestedSetData
358
     * @param string $parent
359
     * @return void
360
     */
361
    protected function _loadNestedSetData($model, $nestedSetData, $parent = null)
362
    {
363
        foreach($nestedSetData AS $rowKey => $nestedSet) {
364
            $children = array();
365
            $data  = array();
366
 
367
            if (array_key_exists('children', $nestedSet)) {
368
                $children = (array) $nestedSet['children'];
369
                $children = array_reverse($children, true);
370
                unset($nestedSet['children']);
371
            }
372
 
373
            $rowKey = $this->_getRowKeyPrefix(Doctrine_Core::getTable($model)) . $rowKey;
374
 
375
            $record = $this->_importedObjects[$rowKey];
376
            // remove this nested set from _importedObjects so it's not processed in the save routine for normal objects
377
            unset($this->_importedObjects[$rowKey]);
378
 
379
            if ( ! $parent) {
380
                $record->save(); // save, so that createRoot can do: root id = id
381
                Doctrine_Core::getTable($model)->getTree()->createRoot($record);
382
            } else {
383
                $parent->getNode()->addChild($record);
384
            }
385
 
386
            if (is_array($children) AND !empty($children)) {
387
                $this->_loadNestedSetData($model, $children, $record);
388
            }
389
        }
390
    }
391
 
392
    /**
393
     * Returns the prefix to use when indexing an object from the supplied table.
394
     *
395
     * @param Doctrine_Table $table
396
     * @return string
397
     */
398
    protected function _getRowKeyPrefix(Doctrine_Table $table)
399
    {
400
        return sprintf('(%s) ', $table->getTableName());
401
    }
402
}