2012-12-07 18:44:00 +01:00
|
|
|
<?php
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Dev;
|
|
|
|
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\Queries\SQLInsert;
|
|
|
|
use SilverStripe\ORM\DB;
|
|
|
|
use SilverStripe\ORM\DataObject;
|
|
|
|
use SilverStripe\ORM\Queries\SQLDelete;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Core\Injector\Injector;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
|
2012-12-07 18:44:00 +01:00
|
|
|
/**
|
|
|
|
* Manages a set of database fixtures for {@link DataObject} records
|
|
|
|
* as well as raw database table rows.
|
|
|
|
*
|
|
|
|
* Delegates creation of objects to {@link FixtureBlueprint},
|
|
|
|
* which can implement class- and use-case specific fixture setup.
|
|
|
|
*
|
|
|
|
* Supports referencing model relations through a specialized syntax:
|
|
|
|
* <code>
|
|
|
|
* $factory = new FixtureFactory();
|
|
|
|
* $relatedObj = $factory->createObject(
|
2016-11-29 00:31:16 +01:00
|
|
|
* 'MyRelatedClass',
|
|
|
|
* 'relation1'
|
2012-12-07 18:44:00 +01:00
|
|
|
* );
|
|
|
|
* $obj = $factory->createObject(
|
2016-11-29 00:31:16 +01:00
|
|
|
* 'MyClass',
|
|
|
|
* 'object1'
|
|
|
|
* array('MyRelationName' => '=>MyRelatedClass.relation1')
|
2012-12-07 18:44:00 +01:00
|
|
|
* );
|
|
|
|
* </code>
|
2021-12-13 09:05:33 +01:00
|
|
|
* Relation loading is order dependent.
|
2012-12-07 18:44:00 +01:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class FixtureFactory
|
|
|
|
{
|
2016-01-06 00:34:58 +01:00
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
|
|
|
* @var array Array of fixture items, keyed by class and unique identifier,
|
|
|
|
* with values being the generated database ID. Does not store object instances.
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
protected $fixtures = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
2018-06-12 23:35:45 +02:00
|
|
|
* @var FixtureBlueprint[] Callbacks
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
protected $blueprints = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $name Unique name for this blueprint
|
|
|
|
* @param array|FixtureBlueprint $defaults Array of default values, or a blueprint instance
|
|
|
|
* @return $this
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
public function define($name, $defaults = [])
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
|
|
|
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])) {
|
2020-04-20 19:58:09 +02:00
|
|
|
$this->fixtures[$class] = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
$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)
|
|
|
|
{
|
2020-04-20 19:58:09 +02:00
|
|
|
$fields = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
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.
|
|
|
|
*
|
2017-05-04 07:48:10 +02:00
|
|
|
* @param string $class The data class or table name
|
2016-11-29 00:31:16 +01:00
|
|
|
* @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.
|
|
|
|
*
|
2017-05-04 07:48:10 +02:00
|
|
|
* @param string $class The data class or table name, as specified in your fixture file. Parent classes won't work
|
2016-11-29 00:31:16 +01:00
|
|
|
* @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;
|
|
|
|
}
|
2017-05-04 07:48:10 +02:00
|
|
|
|
|
|
|
// If the class doesn't exist, look for a table instead
|
2022-04-14 03:12:59 +02:00
|
|
|
if (!class_exists($class ?? '')) {
|
2017-05-04 07:48:10 +02:00
|
|
|
$tableNames = DataObject::getSchema()->getTableNames();
|
2022-04-14 03:12:59 +02:00
|
|
|
$potential = array_search($class, $tableNames ?? []);
|
2017-05-04 07:48:10 +02:00
|
|
|
if (!$potential) {
|
|
|
|
throw new \LogicException("'$class' is neither a class nor a table name");
|
|
|
|
}
|
|
|
|
$class = $potential;
|
|
|
|
}
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
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
|
2018-06-14 05:56:46 +02:00
|
|
|
* @param bool $metadata Clear internal mapping as well as data.
|
|
|
|
* Set to false by default since sometimes data is rolled back by translations.
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2018-06-14 05:56:46 +02:00
|
|
|
public function clear($limitToClass = null, $metadata = false)
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
2022-04-14 03:12:59 +02:00
|
|
|
$classes = ($limitToClass) ? [$limitToClass] : array_keys($this->fixtures ?? []);
|
2016-11-29 00:31:16 +01:00
|
|
|
foreach ($classes as $class) {
|
|
|
|
$ids = $this->fixtures[$class];
|
|
|
|
foreach ($ids as $id => $dbId) {
|
2022-04-14 03:12:59 +02:00
|
|
|
if (class_exists($class ?? '')) {
|
2018-06-14 05:56:46 +02:00
|
|
|
$instance = DataObject::get($class)->byId($dbId);
|
|
|
|
if ($instance) {
|
|
|
|
$instance->delete();
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
} else {
|
|
|
|
$table = $class;
|
2020-04-20 19:58:09 +02:00
|
|
|
$delete = new SQLDelete("\"$table\"", [
|
2016-11-29 00:31:16 +01:00
|
|
|
"\"$table\".\"ID\"" => $dbId
|
2020-04-20 19:58:09 +02:00
|
|
|
]);
|
2016-11-29 00:31:16 +01:00
|
|
|
$delete->execute();
|
|
|
|
}
|
|
|
|
|
2018-06-14 05:56:46 +02:00
|
|
|
if ($metadata) {
|
|
|
|
unset($this->fixtures[$class][$id]);
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array Of {@link FixtureBlueprint} instances
|
|
|
|
*/
|
|
|
|
public function getBlueprints()
|
|
|
|
{
|
|
|
|
return $this->blueprints;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-12-21 22:23:23 +01:00
|
|
|
* @param string $name
|
2016-11-29 00:31:16 +01:00
|
|
|
* @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)
|
|
|
|
{
|
2022-04-14 03:12:59 +02:00
|
|
|
if (substr($value ?? '', 0, 2) == '=>') {
|
2016-11-29 00:31:16 +01:00
|
|
|
// Parse a dictionary reference - used to set foreign keys
|
2022-04-14 03:12:59 +02:00
|
|
|
if (strpos($value ?? '', '.') !== false) {
|
|
|
|
list($class, $identifier) = explode('.', substr($value ?? '', 2), 2);
|
2017-05-04 07:48:10 +02:00
|
|
|
} else {
|
|
|
|
throw new \LogicException("Bad fixture lookup identifier: " . $value);
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2014-08-18 05:49:32 +02:00
|
|
|
}
|