Completed
Push — master ( a05958...d2c6c5 )
by Damian
07:29
created

FixtureFactory   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 0
loc 240
rs 10
c 0
b 0
f 0
wmc 30
lcom 1
cbo 7

12 Methods

Rating   Name   Duplication   Size   Complexity  
A define() 0 16 2
A createObject() 0 16 3
A createRaw() 0 13 2
A getId() 0 8 2
A getIds() 0 8 2
A setId() 0 5 1
A get() 0 19 4
A getFixtures() 0 4 1
B clear() 0 20 5
A getBlueprints() 0 4 1
A getBlueprint() 0 4 2
B parseValue() 0 23 5
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use SilverStripe\ORM\Queries\SQLInsert;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\ORM\Queries\SQLDelete;
9
use SilverStripe\Core\Injector\Injector;
10
use InvalidArgumentException;
11
12
/**
13
 * Manages a set of database fixtures for {@link DataObject} records
14
 * as well as raw database table rows.
15
 *
16
 * Delegates creation of objects to {@link FixtureBlueprint},
17
 * which can implement class- and use-case specific fixture setup.
18
 *
19
 * Supports referencing model relations through a specialized syntax:
20
 * <code>
21
 * $factory = new FixtureFactory();
22
 * $relatedObj = $factory->createObject(
23
 *  'MyRelatedClass',
24
 *  'relation1'
25
 * );
26
 * $obj = $factory->createObject(
27
 *  'MyClass',
28
 *  'object1'
29
 *  array('MyRelationName' => '=>MyRelatedClass.relation1')
30
 * );
31
 * </code>
32
 * Relation loading is order dependant.
33
 */
34
class FixtureFactory
35
{
36
37
    /**
38
     * @var array Array of fixture items, keyed by class and unique identifier,
39
     * with values being the generated database ID. Does not store object instances.
40
     */
41
    protected $fixtures = array();
42
43
    /**
44
     * @var array Callbacks
45
     */
46
    protected $blueprints = array();
47
48
    /**
49
     * @param string $name Unique name for this blueprint
50
     * @param array|FixtureBlueprint $defaults Array of default values, or a blueprint instance
51
     * @return $this
52
     */
53
    public function define($name, $defaults = array())
54
    {
55
        if ($defaults instanceof FixtureBlueprint) {
56
            $this->blueprints[$name] = $defaults;
57
        } else {
58
            $class = $name;
59
            $this->blueprints[$name] = Injector::inst()->create(
60
                'SilverStripe\\Dev\\FixtureBlueprint',
61
                $name,
62
                $class,
63
                $defaults
64
            );
65
        }
66
67
        return $this;
68
    }
69
70
    /**
71
     * Writes the fixture into the database using DataObjects
72
     *
73
     * @param string $name Name of the {@link FixtureBlueprint} to use,
74
     *                     usually a DataObject subclass.
75
     * @param string $identifier Unique identifier for this fixture type
76
     * @param array $data Map of properties. Overrides default data.
77
     * @return DataObject
78
     */
79
    public function createObject($name, $identifier, $data = null)
80
    {
81
        if (!isset($this->blueprints[$name])) {
82
            $this->blueprints[$name] = new FixtureBlueprint($name);
83
        }
84
        $blueprint = $this->blueprints[$name];
85
        $obj = $blueprint->createObject($identifier, $data, $this->fixtures);
86
        $class = $blueprint->getClass();
87
88
        if (!isset($this->fixtures[$class])) {
89
            $this->fixtures[$class] = array();
90
        }
91
        $this->fixtures[$class][$identifier] = $obj->ID;
92
93
        return $obj;
94
    }
95
96
    /**
97
     * Writes the fixture into the database directly using a database manipulation.
98
     * Does not use blueprints. Only supports tables with a primary key.
99
     *
100
     * @param string $table Existing database table name
101
     * @param string $identifier Unique identifier for this fixture type
102
     * @param array $data Map of properties
103
     * @return int Database identifier
104
     */
105
    public function createRaw($table, $identifier, $data)
106
    {
107
        $fields = array();
108
        foreach ($data as $fieldName => $fieldVal) {
109
            $fields["\"{$fieldName}\""] = $this->parseValue($fieldVal);
110
        }
111
        $insert = new SQLInsert("\"{$table}\"", $fields);
112
        $insert->execute();
113
        $id = DB::get_generated_id($table);
114
        $this->fixtures[$table][$identifier] = $id;
115
116
        return $id;
117
    }
118
119
    /**
120
     * Get the ID of an object from the fixture.
121
     *
122
     * @param string $class The data class, as specified in your fixture file.  Parent classes won't work
123
     * @param string $identifier The identifier string, as provided in your fixture file
124
     * @return int
125
     */
126
    public function getId($class, $identifier)
127
    {
128
        if (isset($this->fixtures[$class][$identifier])) {
129
            return $this->fixtures[$class][$identifier];
130
        } else {
131
            return false;
132
        }
133
    }
134
135
    /**
136
     * Return all of the IDs in the fixture of a particular class name.
137
     *
138
     * @param string $class The data class or table name
139
     * @return array|false A map of fixture-identifier => object-id
140
     */
141
    public function getIds($class)
142
    {
143
        if (isset($this->fixtures[$class])) {
144
            return $this->fixtures[$class];
145
        } else {
146
            return false;
147
        }
148
    }
149
150
    /**
151
     * @param string $class
152
     * @param string $identifier
153
     * @param int $databaseId
154
     * @return $this
155
     */
156
    public function setId($class, $identifier, $databaseId)
157
    {
158
        $this->fixtures[$class][$identifier] = $databaseId;
159
        return $this;
160
    }
161
162
    /**
163
     * Get an object from the fixture.
164
     *
165
     * @param string $class The data class or table name, as specified in your fixture file.  Parent classes won't work
166
     * @param string $identifier The identifier string, as provided in your fixture file
167
     * @return DataObject
168
     */
169
    public function get($class, $identifier)
170
    {
171
        $id = $this->getId($class, $identifier);
172
        if (!$id) {
173
            return null;
174
        }
175
176
        // If the class doesn't exist, look for a table instead
177
        if (!class_exists($class)) {
178
            $tableNames = DataObject::getSchema()->getTableNames();
179
            $potential = array_search($class, $tableNames);
180
            if (!$potential) {
181
                throw new \LogicException("'$class' is neither a class nor a table name");
182
            }
183
            $class = $potential;
184
        }
185
186
        return DataObject::get_by_id($class, $id);
187
    }
188
189
    /**
190
     * @return array Map of class names, containing a map of in-memory identifiers
191
     * mapped to database identifiers.
192
     */
193
    public function getFixtures()
194
    {
195
        return $this->fixtures;
196
    }
197
198
    /**
199
     * Remove all fixtures previously defined through {@link createObject()}
200
     * or {@link createRaw()}, both from the internal fixture mapping and the database.
201
     * If the $class argument is set, limit clearing to items of this class.
202
     *
203
     * @param string $limitToClass
204
     */
205
    public function clear($limitToClass = null)
206
    {
207
        $classes = ($limitToClass) ? array($limitToClass) : array_keys($this->fixtures);
208
        foreach ($classes as $class) {
209
            $ids = $this->fixtures[$class];
210
            foreach ($ids as $id => $dbId) {
211
                if (class_exists($class)) {
212
                    $class::get()->byId($dbId)->delete();
213
                } else {
214
                    $table = $class;
215
                    $delete = new SQLDelete("\"$table\"", array(
216
                        "\"$table\".\"ID\"" => $dbId
217
                    ));
218
                    $delete->execute();
219
                }
220
221
                unset($this->fixtures[$class][$id]);
222
            }
223
        }
224
    }
225
226
    /**
227
     * @return array Of {@link FixtureBlueprint} instances
228
     */
229
    public function getBlueprints()
230
    {
231
        return $this->blueprints;
232
    }
233
234
    /**
235
     * @param String $name
236
     * @return FixtureBlueprint|false
237
     */
238
    public function getBlueprint($name)
239
    {
240
        return (isset($this->blueprints[$name])) ? $this->blueprints[$name] : false;
241
    }
242
243
    /**
244
     * Parse a value from a fixture file.  If it starts with =>
245
     * it will get an ID from the fixture dictionary
246
     *
247
     * @param string $value
248
     * @return string Fixture database ID, or the original value
249
     */
250
    protected function parseValue($value)
251
    {
252
        if (substr($value, 0, 2) == '=>') {
253
            // Parse a dictionary reference - used to set foreign keys
254
            if (strpos($value, '.') !== false) {
255
                list($class, $identifier) = explode('.', substr($value, 2), 2);
256
            } else {
257
                throw new \LogicException("Bad fixture lookup identifier: " . $value);
258
            }
259
260
            if ($this->fixtures && !isset($this->fixtures[$class][$identifier])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fixtures of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
261
                throw new InvalidArgumentException(sprintf(
262
                    'No fixture definitions found for "%s"',
263
                    $value
264
                ));
265
            }
266
267
            return $this->fixtures[$class][$identifier];
268
        } else {
269
            // Regular field value setting
270
            return $value;
271
        }
272
    }
273
}
274