mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #2894 from wilr/3.1-manymanyyaml
Add support for many_many_extraField in YAML
This commit is contained in:
commit
ccb791995e
@ -128,8 +128,28 @@ class FixtureBlueprint {
|
|||||||
// Populate all relations
|
// Populate all relations
|
||||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||||
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) {
|
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) {
|
||||||
|
$obj->write();
|
||||||
|
|
||||||
$parsedItems = array();
|
$parsedItems = array();
|
||||||
|
|
||||||
|
if(is_array($fieldVal)) {
|
||||||
|
// handle lists of many_many relations. Each item can
|
||||||
|
// specify the many_many_extraFields against each
|
||||||
|
// related item.
|
||||||
|
foreach($fieldVal as $relVal) {
|
||||||
|
$item = key($relVal);
|
||||||
|
$id = $this->parseValue($item, $fixtures);
|
||||||
|
$parsedItems[] = $id;
|
||||||
|
|
||||||
|
array_shift($relVal);
|
||||||
|
|
||||||
|
$obj->getManyManyComponents($fieldName)->add(
|
||||||
|
$id, $relVal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
$items = preg_split('/ *, */',trim($fieldVal));
|
$items = preg_split('/ *, */',trim($fieldVal));
|
||||||
|
|
||||||
foreach($items as $item) {
|
foreach($items as $item) {
|
||||||
// Check for correct format: =><relationname>.<identifier>.
|
// Check for correct format: =><relationname>.<identifier>.
|
||||||
// Ignore if the item has already been replaced with a numeric DB identifier
|
// Ignore if the item has already been replaced with a numeric DB identifier
|
||||||
@ -144,12 +164,13 @@ class FixtureBlueprint {
|
|||||||
|
|
||||||
$parsedItems[] = $this->parseValue($item, $fixtures);
|
$parsedItems[] = $this->parseValue($item, $fixtures);
|
||||||
}
|
}
|
||||||
$obj->write();
|
|
||||||
if($obj->has_many($fieldName)) {
|
if($obj->has_many($fieldName)) {
|
||||||
$obj->getComponents($fieldName)->setByIDList($parsedItems);
|
$obj->getComponents($fieldName)->setByIDList($parsedItems);
|
||||||
} elseif($obj->many_many($fieldName)) {
|
} elseif($obj->many_many($fieldName)) {
|
||||||
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
|
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} elseif($obj->has_one($fieldName)) {
|
} elseif($obj->has_one($fieldName)) {
|
||||||
// Sets has_one with relation name
|
// Sets has_one with relation name
|
||||||
$obj->{$fieldName . 'ID'} = $this->parseValue($fieldVal, $fixtures);
|
$obj->{$fieldName . 'ID'} = $this->parseValue($fieldVal, $fixtures);
|
||||||
|
@ -2,38 +2,42 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
You will often find the need to test your functionality with some consistent data.
|
You will often find the need to test your functionality with some consistent
|
||||||
If we are testing our code with the same data each time,
|
data. If we are testing our code with the same data each time, we can trust our
|
||||||
we can trust our tests to yeild reliable results.
|
tests to yield reliable results.
|
||||||
In Silverstripe we define this data via 'fixtures' (so called because of their fixed nature).
|
|
||||||
The `[api:SapphireTest]` class takes care of populating a test database with data from these fixtures -
|
In Silverstripe we define this data via 'fixtures' (so called because of their
|
||||||
all we have to do is define them, and we have a few ways in which we can do this.
|
fixed nature). The `[api:SapphireTest]` class takes care of populating a test
|
||||||
|
database with data from these fixtures - all we have to do is define them, and
|
||||||
|
we have a few ways in which we can do this.
|
||||||
|
|
||||||
## YAML Fixtures
|
## YAML Fixtures
|
||||||
|
|
||||||
YAML is a markup language which is deliberately simple and easy to read,
|
YAML is a markup language which is deliberately simple and easy to read, so it
|
||||||
so it is ideal for fixture generation.
|
is ideal for fixture generation.
|
||||||
|
|
||||||
Say we have the following two DataObjects:
|
Say we have the following two DataObjects:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
static $db = array (
|
|
||||||
|
private static $db = array (
|
||||||
'Name' => 'Varchar(255)'
|
'Name' => 'Varchar(255)'
|
||||||
);
|
);
|
||||||
|
|
||||||
static $has_one = array(
|
private static $has_one = array(
|
||||||
'Team' => 'Team'
|
'Team' => 'Team'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
static $db = array (
|
|
||||||
|
private static $db = array (
|
||||||
'Name' => 'Varchar(255)',
|
'Name' => 'Varchar(255)',
|
||||||
'Origin' => 'Varchar(255)'
|
'Origin' => 'Varchar(255)'
|
||||||
);
|
);
|
||||||
|
|
||||||
static $has_many = array(
|
private static $has_many = array(
|
||||||
'Players' => 'Player'
|
'Players' => 'Player'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -59,31 +63,42 @@ We can represent multiple instances of them in `YAML` as follows:
|
|||||||
Name: The Crusaders
|
Name: The Crusaders
|
||||||
Origin: Bay of Plenty
|
Origin: Bay of Plenty
|
||||||
|
|
||||||
Our `YAML` is broken up into three levels, signified by the indentation of each line.
|
Our `YAML` is broken up into three levels, signified by the indentation of each
|
||||||
In the first level of indentation, `Player` and `Team`,
|
line. In the first level of indentation, `Player` and `Team`, represent the
|
||||||
represent the class names of the objects we want to be created for the test.
|
class names of the objects we want to be created for the test.
|
||||||
|
|
||||||
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are identifiers.
|
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are
|
||||||
These are what you pass as the second argument of `SapphireTest::objFromFixture()`.
|
identifiers. These are what you pass as the second argument of
|
||||||
Each identifier you specify represents a new object.
|
`SapphireTest::objFromFixture()`. Each identifier you specify represents a new
|
||||||
|
object.
|
||||||
|
|
||||||
The third and final level represents each individual object's fields.
|
The third and final level represents each individual object's fields.
|
||||||
A field can either be provided with raw data (such as the Names for our Players),
|
|
||||||
or we can define a relationship, as seen by the fields prefixed with `=>`.
|
|
||||||
|
|
||||||
Each one of our Players has a relationship to a Team,
|
A field can either be provided with raw data (such as the names for our
|
||||||
this is shown with the `Team` field for each `Player` being set to `=>Team.` followed by a team name.
|
Players), or we can define a relationship, as seen by the fields prefixed with
|
||||||
Take the player John for example, his team is the Hurricanes which is represented by `=>Team.hurricanes`.
|
`=>`.
|
||||||
This is tells the system that we want to set up a relationship for the `Player` object `john` with the `Team` object `hurricanes`.
|
|
||||||
|
Each one of our Players has a relationship to a Team, this is shown with the
|
||||||
|
`Team` field for each `Player` being set to `=>Team.` followed by a team name.
|
||||||
|
|
||||||
|
Take the player John for example, his team is the Hurricanes which is
|
||||||
|
represented by `=>Team.hurricanes`.
|
||||||
|
|
||||||
|
This is tells the system that we want to set up a relationship for the `Player`
|
||||||
|
object `john` with the `Team` object `hurricanes`.
|
||||||
|
|
||||||
It will populate the `Player` object's `TeamID` with the ID of `hurricanes`,
|
It will populate the `Player` object's `TeamID` with the ID of `hurricanes`,
|
||||||
just like how a relationship is always set up.
|
just like how a relationship is always set up.
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
<div class="hint" markdown='1'>
|
||||||
Note that we use the name of the relationship (Team), and not the name of the database field (TeamID).
|
Note that we use the name of the relationship (Team), and not the name of the
|
||||||
|
database field (TeamID).
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
This style of relationship declaration can be used for both a `has-one` and a `many-many` relationship.
|
This style of relationship declaration can be used for both a `has-one` and a
|
||||||
For `many-many` relationships, we specify a comma separated list of values.
|
`many-many` relationship. For `many-many` relationships, we specify a comma
|
||||||
|
separated list of values.
|
||||||
|
|
||||||
For example we could just as easily write the above as:
|
For example we could just as easily write the above as:
|
||||||
|
|
||||||
:::yml
|
:::yml
|
||||||
@ -104,27 +119,97 @@ For example we could just as easily write the above as:
|
|||||||
Origin: Bay of Plenty
|
Origin: Bay of Plenty
|
||||||
Players: =>Player.joe,=>Player.jack
|
Players: =>Player.joe,=>Player.jack
|
||||||
|
|
||||||
A crucial thing to note is that **the YAML file specifies DataObjects, not database records**.
|
A crucial thing to note is that **the YAML file specifies DataObjects, not
|
||||||
The database is populated by instantiating DataObject objects and setting the fields declared in the YML,
|
database records**.
|
||||||
then calling write() on those objects.
|
|
||||||
This means that any `onBeforeWrite()` or default value logic will be executed as part of the test.
|
The database is populated by instantiating DataObject objects and setting the
|
||||||
The reasoning behind this is to allow us to test the `onBeforeWrite` functionality of our objects.
|
fields declared in the YML, then calling write() on those objects. This means
|
||||||
You can see this kind of testing in action in the `testURLGeneration()` test from the example in
|
that any `onBeforeWrite()` or default value logic will be executed as part of
|
||||||
[Creating a SilverStripe Test](creating-a-silverstripe-test).
|
the test. The reasoning behind this is to allow us to test the `onBeforeWrite`
|
||||||
|
functionality of our objects.
|
||||||
|
|
||||||
|
You can see this kind of testing in action in the `testURLGeneration()` test
|
||||||
|
from the example in [Creating a SilverStripe Test](creating-a-silverstripe-test).
|
||||||
|
|
||||||
|
### Defining many_many_extraFields
|
||||||
|
|
||||||
|
`many_many` relations can have additional database fields attached to the
|
||||||
|
relationship. For example we may want to declare the role each player has in the
|
||||||
|
team.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
class Player extends DataObject {
|
||||||
|
|
||||||
|
private static $db = array (
|
||||||
|
'Name' => 'Varchar(255)'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $belongs_many_many = array(
|
||||||
|
'Teams' => 'Team'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Team extends DataObject {
|
||||||
|
|
||||||
|
private static $db = array (
|
||||||
|
'Name' => 'Varchar(255)'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $many_many = array(
|
||||||
|
'Players' => 'Player'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $many_many_extraFields = array(
|
||||||
|
"Players" => array(
|
||||||
|
"Role" => "Varchar"
|
||||||
|
);
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
To provide the value for the many_many_extraField use the YAML list syntax.
|
||||||
|
|
||||||
|
:::yml
|
||||||
|
Player:
|
||||||
|
john:
|
||||||
|
Name: John
|
||||||
|
joe:
|
||||||
|
Name: Joe
|
||||||
|
jack:
|
||||||
|
Name: Jack
|
||||||
|
Team:
|
||||||
|
hurricanes:
|
||||||
|
Name: The Hurricanes
|
||||||
|
Players:
|
||||||
|
- =>Player.john:
|
||||||
|
Role: Captain
|
||||||
|
|
||||||
|
crusaders:
|
||||||
|
Name: The Crusaders
|
||||||
|
Players:
|
||||||
|
- =>Player.joe:
|
||||||
|
Role: Captain
|
||||||
|
- =>Player.jack:
|
||||||
|
Role: Winger
|
||||||
|
|
||||||
## Test Class Definition
|
## Test Class Definition
|
||||||
|
|
||||||
### Manual Object Creation
|
### Manual Object Creation
|
||||||
|
|
||||||
Sometimes statically defined fixtures don't suffice. This could be because of the complexity of the tested model,
|
Sometimes statically defined fixtures don't suffice. This could be because of
|
||||||
or because the YAML format doesn't allow you to modify all of a model's state.
|
the complexity of the tested model, or because the YAML format doesn't allow you
|
||||||
One common example here is publishing pages (page fixtures aren't published by default).
|
to modify all of a model's state.
|
||||||
|
|
||||||
|
One common example here is publishing pages (page fixtures aren't published by
|
||||||
|
default).
|
||||||
|
|
||||||
You can always resort to creating objects manually in the test setup phase.
|
You can always resort to creating objects manually in the test setup phase.
|
||||||
Since the test database is cleared on every test method, you'll get a fresh set of test instances every time.
|
|
||||||
|
Since the test database is cleared on every test method, you'll get a fresh set
|
||||||
|
of test instances every time.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class SiteTreeTest extends SapphireTest {
|
class SiteTreeTest extends SapphireTest {
|
||||||
|
|
||||||
function setUp() {
|
function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
@ -140,16 +225,20 @@ Since the test database is cleared on every test method, you'll get a fresh set
|
|||||||
|
|
||||||
### Why Factories?
|
### Why Factories?
|
||||||
|
|
||||||
While manually defined fixtures provide full flexibility, they offer very little in terms of structure and convention.
|
While manually defined fixtures provide full flexibility, they offer very little
|
||||||
Alternatively, you can use the `[api:FixtureFactory]` class, which allows you to set default values,
|
in terms of structure and convention. Alternatively, you can use the
|
||||||
callbacks on object creation, and dynamic/lazy value setting.
|
`[api:FixtureFactory]` class, which allows you to set default values, callbacks
|
||||||
|
on object creation, and dynamic/lazy value setting.
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
<div class="hint" markdown='1'>
|
||||||
SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
|
SapphireTest uses FixtureFactory under the hood when it is provided with YAML
|
||||||
|
based fixtures.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
The idea is that rather than instantiating objects directly, we'll have a factory class for them.
|
The idea is that rather than instantiating objects directly, we'll have a
|
||||||
This factory can have so called "blueprints" defined on it, which tells the factory how to instantiate an object of a specific type. Blueprints need a name, which is usually set to the class it creates.
|
factory class for them. This factory can have so called "blueprints" defined on
|
||||||
|
it, which tells the factory how to instantiate an object of a specific type.
|
||||||
|
Blueprints need a name, which is usually set to the class it creates.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
|
@ -12,6 +12,58 @@ class FixtureBlueprintTest extends SapphireTest {
|
|||||||
'FixtureFactoryTest_DataObjectRelation'
|
'FixtureFactoryTest_DataObjectRelation'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public function testCreateWithRelationshipExtraFields() {
|
||||||
|
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||||
|
|
||||||
|
$relation1 = new FixtureFactoryTest_DataObjectRelation();
|
||||||
|
$relation1->write();
|
||||||
|
$relation2 = new FixtureFactoryTest_DataObjectRelation();
|
||||||
|
$relation2->write();
|
||||||
|
|
||||||
|
// in YAML these look like
|
||||||
|
// RelationName:
|
||||||
|
// - =>Relational.obj:
|
||||||
|
// ExtraFieldName: test
|
||||||
|
// - =>..
|
||||||
|
$obj = $blueprint->createObject(
|
||||||
|
'one',
|
||||||
|
array(
|
||||||
|
'ManyMany' =>
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
"=>FixtureFactoryTest_DataObjectRelation.relation1" => array(),
|
||||||
|
"Label" => 'This is a label for relation 1'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"=>FixtureFactoryTest_DataObjectRelation.relation2" => array(),
|
||||||
|
"Label" => 'This is a label for relation 2'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||||
|
'relation1' => $relation1->ID,
|
||||||
|
'relation2' => $relation2->ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(2, $obj->ManyMany()->Count());
|
||||||
|
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
|
||||||
|
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array('Label' => 'This is a label for relation 1'),
|
||||||
|
$obj->ManyMany()->getExtraData('ManyMany', $relation1->ID)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array('Label' => 'This is a label for relation 2'),
|
||||||
|
$obj->ManyMany()->getExtraData('ManyMany', $relation2->ID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testCreateWithoutData() {
|
public function testCreateWithoutData() {
|
||||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||||
$obj = $blueprint->createObject('one');
|
$obj = $blueprint->createObject('one');
|
||||||
@ -28,6 +80,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
|||||||
$this->assertEquals('My Name', $obj->Name);
|
$this->assertEquals('My Name', $obj->Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testCreateWithRelationship() {
|
public function testCreateWithRelationship() {
|
||||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||||
|
|
||||||
@ -127,7 +180,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
|||||||
$this->assertEquals(99, $obj->ID);
|
$this->assertEquals(99, $obj->ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testCallbackOnBeforeCreate() {
|
public function testCallbackOnBeforeCreate() {
|
||||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||||
$this->_called = 0;
|
$this->_called = 0;
|
||||||
$self = $this;
|
$self = $this;
|
||||||
@ -144,7 +197,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
|||||||
$this->_called = 0;
|
$this->_called = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testCallbackOnAfterCreate() {
|
public function testCallbackOnAfterCreate() {
|
||||||
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
|
||||||
$this->_called = 0;
|
$this->_called = 0;
|
||||||
$self = $this;
|
$self = $this;
|
||||||
@ -161,7 +214,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
|||||||
$this->_called = 0;
|
$this->_called = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testDefineWithDefaultCustomSetters() {
|
public function testDefineWithDefaultCustomSetters() {
|
||||||
$blueprint = new FixtureBlueprint(
|
$blueprint = new FixtureBlueprint(
|
||||||
'FixtureFactoryTest_DataObject',
|
'FixtureFactoryTest_DataObject',
|
||||||
null,
|
null,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage tests
|
* @subpackage tests
|
||||||
@ -151,19 +152,37 @@ class FixtureFactoryTest extends SapphireTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package framework
|
||||||
|
* @subpackage tests
|
||||||
|
*/
|
||||||
class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
|
class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
"Name" => "Varchar"
|
"Name" => "Varchar"
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $many_many = array(
|
private static $many_many = array(
|
||||||
"ManyMany" => "FixtureFactoryTest_DataObjectRelation"
|
"ManyMany" => "FixtureFactoryTest_DataObjectRelation"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static $many_many_extraFields = array(
|
||||||
|
"ManyMany" => array(
|
||||||
|
"Label" => "Varchar"
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package framework
|
||||||
|
* @subpackage tests
|
||||||
|
*/
|
||||||
class FixtureFactoryTest_DataObjectRelation extends DataObject implements TestOnly {
|
class FixtureFactoryTest_DataObjectRelation extends DataObject implements TestOnly {
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
"Name" => "Varchar"
|
"Name" => "Varchar"
|
||||||
);
|
);
|
||||||
|
|
||||||
private static $belongs_many_many = array(
|
private static $belongs_many_many = array(
|
||||||
"TestParent" => "FixtureFactoryTest_DataObject"
|
"TestParent" => "FixtureFactoryTest_DataObject"
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user