* $factory = new FixtureFactory(); * $relatedObj = $factory->createObject( * 'MyRelatedClass', * 'relation1' * ); * $obj = $factory->createObject( * 'MyClass', * 'object1' * array('MyRelationName' => '=>MyRelatedClass.relation1') * ); * * Relation loading is order dependent. */ class FixtureFactory { /** * @var array Array of fixture items, keyed by class and unique identifier, * with values being the generated database ID. Does not store object instances. */ protected $fixtures = []; /** * @var FixtureBlueprint[] Callbacks */ protected $blueprints = []; /** * @param string $name Unique name for this blueprint * @param array|FixtureBlueprint $defaults Array of default values, or a blueprint instance * @return $this */ public function define($name, $defaults = []) { if ($defaults instanceof FixtureBlueprint) { $this->blueprints[$name] = $defaults; } else { $class = $name; $this->blueprints[$name] = Injector::inst()->create( 'SilverStripe\\Dev\\FixtureBlueprint', $name, $class, $defaults ); } return $this; } /** * Writes the fixture into the database using DataObjects * * @param string $name Name of the {@link FixtureBlueprint} to use, * usually a DataObject subclass. * @param string $identifier Unique identifier for this fixture type * @param array $data Map of properties. Overrides default data. * @return DataObject */ public function createObject($name, $identifier, $data = null) { if (!isset($this->blueprints[$name])) { $this->blueprints[$name] = new FixtureBlueprint($name); } $blueprint = $this->blueprints[$name]; $obj = $blueprint->createObject($identifier, $data, $this->fixtures); $class = $blueprint->getClass(); if (!isset($this->fixtures[$class])) { $this->fixtures[$class] = []; } $this->fixtures[$class][$identifier] = $obj->ID; return $obj; } /** * Writes the fixture into the database directly using a database manipulation. * Does not use blueprints. Only supports tables with a primary key. * * @param string $table Existing database table name * @param string $identifier Unique identifier for this fixture type * @param array $data Map of properties * @return int Database identifier */ public function createRaw($table, $identifier, $data) { $fields = []; foreach ($data as $fieldName => $fieldVal) { $fields["\"{$fieldName}\""] = $this->parseValue($fieldVal); } $insert = new SQLInsert("\"{$table}\"", $fields); $insert->execute(); $id = DB::get_generated_id($table); $this->fixtures[$table][$identifier] = $id; return $id; } /** * Get the ID of an object from the fixture. * * @param string $class The data class, as specified in your fixture file. Parent classes won't work * @param string $identifier The identifier string, as provided in your fixture file * @return int */ public function getId($class, $identifier) { if (isset($this->fixtures[$class][$identifier])) { return $this->fixtures[$class][$identifier]; } else { return false; } } /** * Return all of the IDs in the fixture of a particular class name. * * @param string $class The data class or table name * @return array|false A map of fixture-identifier => object-id */ public function getIds($class) { if (isset($this->fixtures[$class])) { return $this->fixtures[$class]; } else { return false; } } /** * @param string $class * @param string $identifier * @param int $databaseId * @return $this */ public function setId($class, $identifier, $databaseId) { $this->fixtures[$class][$identifier] = $databaseId; return $this; } /** * Get an object from the fixture. * * @param string $class The data class or table name, as specified in your fixture file. Parent classes won't work * @param string $identifier The identifier string, as provided in your fixture file * @return DataObject */ public function get($class, $identifier) { $id = $this->getId($class, $identifier); if (!$id) { return null; } // If the class doesn't exist, look for a table instead if (!class_exists($class ?? '')) { $tableNames = DataObject::getSchema()->getTableNames(); $potential = array_search($class, $tableNames ?? []); if (!$potential) { throw new \LogicException("'$class' is neither a class nor a table name"); } $class = $potential; } return DataObject::get_by_id($class, $id); } /** * @return array Map of class names, containing a map of in-memory identifiers * mapped to database identifiers. */ public function getFixtures() { return $this->fixtures; } /** * Remove all fixtures previously defined through {@link createObject()} * or {@link createRaw()}, both from the internal fixture mapping and the database. * If the $class argument is set, limit clearing to items of this class. * * @param string $limitToClass * @param bool $metadata Clear internal mapping as well as data. * Set to false by default since sometimes data is rolled back by translations. */ public function clear($limitToClass = null, $metadata = false) { $classes = ($limitToClass) ? [$limitToClass] : array_keys($this->fixtures ?? []); foreach ($classes as $class) { $ids = $this->fixtures[$class]; foreach ($ids as $id => $dbId) { if (class_exists($class ?? '')) { $instance = DataObject::get($class)->byId($dbId); if ($instance) { $instance->delete(); } } else { $table = $class; $delete = new SQLDelete("\"$table\"", [ "\"$table\".\"ID\"" => $dbId ]); $delete->execute(); } if ($metadata) { unset($this->fixtures[$class][$id]); } } } } /** * @return array Of {@link FixtureBlueprint} instances */ public function getBlueprints() { return $this->blueprints; } /** * @param string $name * @return FixtureBlueprint|false */ public function getBlueprint($name) { return (isset($this->blueprints[$name])) ? $this->blueprints[$name] : false; } /** * Parse a value from a fixture file. If it starts with => * it will get an ID from the fixture dictionary * * @param string $value * @return string Fixture database ID, or the original value */ protected function parseValue($value) { if (substr($value ?? '', 0, 2) == '=>') { // Parse a dictionary reference - used to set foreign keys if (strpos($value ?? '', '.') !== false) { list($class, $identifier) = explode('.', substr($value ?? '', 2), 2); } else { throw new \LogicException("Bad fixture lookup identifier: " . $value); } if ($this->fixtures && !isset($this->fixtures[$class][$identifier])) { throw new InvalidArgumentException(sprintf( 'No fixture definitions found for "%s"', $value )); } return $this->fixtures[$class][$identifier]; } else { // Regular field value setting return $value; } } }