API Moving tests to use transactions

This commit is contained in:
Daniel Hensby 2018-06-12 22:35:45 +01:00 committed by Damian Mooyman
parent 77a45c0dbc
commit ec956a682d
7 changed files with 393 additions and 36 deletions

View File

@ -8,6 +8,7 @@ SilverStripe\Core\Injector\Injector:
globals: '%$SilverStripe\Dev\State\GlobalsTestState' globals: '%$SilverStripe\Dev\State\GlobalsTestState'
extensions: '%$SilverStripe\Dev\State\ExtensionTestState' extensions: '%$SilverStripe\Dev\State\ExtensionTestState'
flushable: '%$SilverStripe\Dev\State\FlushableTestState' flushable: '%$SilverStripe\Dev\State\FlushableTestState'
fixtures: '%$SilverStripe\Dev\State\FixtureTestState'
requirements: '%$SilverStripe\View\Dev\RequirementsTestState' requirements: '%$SilverStripe\View\Dev\RequirementsTestState'
ssviewer: '%$SilverStripe\View\Dev\SSViewerTestState' ssviewer: '%$SilverStripe\View\Dev\SSViewerTestState'
--- ---

View File

@ -41,7 +41,7 @@ class FixtureFactory
protected $fixtures = array(); protected $fixtures = array();
/** /**
* @var array Callbacks * @var FixtureBlueprint[] Callbacks
*/ */
protected $blueprints = array(); protected $blueprints = array();

View File

@ -23,6 +23,7 @@ use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\Constraint\SSListContains; use SilverStripe\Dev\Constraint\SSListContains;
use SilverStripe\Dev\Constraint\SSListContainsOnly; use SilverStripe\Dev\Constraint\SSListContainsOnly;
use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems; use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems;
use SilverStripe\Dev\State\FixtureTestState;
use SilverStripe\Dev\State\SapphireTestState; use SilverStripe\Dev\State\SapphireTestState;
use SilverStripe\Dev\State\TestState; use SilverStripe\Dev\State\TestState;
use SilverStripe\i18n\i18n; use SilverStripe\i18n\i18n;
@ -148,7 +149,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
/** /**
* State management container for SapphireTest * State management container for SapphireTest
* *
* @var TestState * @var SapphireTestState
*/ */
protected static $state = null; protected static $state = null;
@ -159,6 +160,17 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
*/ */
protected static $tempDB = null; protected static $tempDB = null;
/**
* @return TempDatabase
*/
public static function tempDB()
{
if (!static::$tempDB) {
static::$tempDB = TempDatabase::create();
}
return static::$tempDB;
}
/** /**
* Gets illegal extensions for this class * Gets illegal extensions for this class
* *
@ -208,6 +220,22 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
return static::$fixture_file; return static::$fixture_file;
} }
/**
* @return bool
*/
public function getUsesDatabase()
{
return $this->usesDatabase;
}
/**
* @return array
*/
public function getRequireDefaultRecordsFrom()
{
return $this->requireDefaultRecordsFrom;
}
/** /**
* Setup the test. * Setup the test.
* Always sets up in order: * Always sets up in order:
@ -254,31 +282,10 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
$fixtureFiles = $this->getFixturePaths(); $fixtureFiles = $this->getFixturePaths();
// Set up fixture
if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) { if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) {
if (!static::$tempDB->isUsed()) { /** @var FixtureTestState $fixtureState */
static::$tempDB->build(); $fixtureState = static::$state->getStateByName('fixtures');
} $this->setFixtureFactory($fixtureState->getFixtureFactory(static::class));
DataObject::singleton()->flushCache();
static::$tempDB->clearAllData();
foreach ($this->requireDefaultRecordsFrom as $className) {
$instance = singleton($className);
if (method_exists($instance, 'requireDefaultRecords')) {
$instance->requireDefaultRecords();
}
if (method_exists($instance, 'augmentDefaultRecords')) {
$instance->augmentDefaultRecords();
}
}
foreach ($fixtureFiles as $fixtureFilePath) {
$fixture = YamlFixture::create($fixtureFilePath);
$fixture->writeInto($this->getFixtureFactory());
}
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
} }
@ -393,24 +400,29 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
} }
/** /**
* @return FixtureFactory * @deprecated 4.0..5.0
* @return FixtureFactory|false
*/ */
public function getFixtureFactory() public function getFixtureFactory()
{ {
if (!$this->fixtureFactory) { Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
$this->fixtureFactory = Injector::inst()->create(FixtureFactory::class); /** @var FixtureTestState $state */
} $state = static::$state->getStateByName('fixtures');
return $this->fixtureFactory; return $state->getFixtureFactory(static::class);
} }
/** /**
* Sets a new fixture factory * Sets a new fixture factory
* * @deprecated 4.0..5.0
* @param FixtureFactory $factory * @param FixtureFactory $factory
* @return $this * @return $this
*/ */
public function setFixtureFactory(FixtureFactory $factory) public function setFixtureFactory(FixtureFactory $factory)
{ {
Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
/** @var FixtureTestState $state */
$state = static::$state->getStateByName('fixtures');
$state->setFixtureFactory($factory, static::class);
$this->fixtureFactory = $factory; $this->fixtureFactory = $factory;
return $this; return $this;
} }
@ -476,11 +488,13 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
* Load a YAML fixture file into the database. * Load a YAML fixture file into the database.
* Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture. * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
* Doesn't clear existing fixtures. * Doesn't clear existing fixtures.
* @deprecated 4.0...5.0
* *
* @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
*/ */
public function loadFixture($fixtureFile) public function loadFixture($fixtureFile)
{ {
Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
$fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile); $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
$fixture->writeInto($this->getFixtureFactory()); $fixture->writeInto($this->getFixtureFactory());
} }
@ -973,7 +987,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
// Register state // Register state
static::$state = SapphireTestState::singleton(); static::$state = SapphireTestState::singleton();
// Register temp DB holder // Register temp DB holder
static::$tempDB = TempDatabase::create(); static::tempDB();
} }
/** /**

View File

@ -0,0 +1,229 @@
<?php
namespace SilverStripe\Dev\State;
use LogicException;
use SilverStripe\Control\Director;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\FixtureFactory;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\YamlFixture;
use SilverStripe\ORM\DataObject;
class FixtureTestState implements TestState
{
/**
* @var FixtureFactory[]
*/
private $fixtureFactories = [];
/**
* Called on setup
*
* @param SapphireTest $test
*/
public function setUp(SapphireTest $test)
{
if ($this->testNeedsDB($test)) {
$tmpDB = $test::tempDB();
if (!$tmpDB->isUsed()) {
$tmpDB->build();
}
DataObject::singleton()->flushCache();
if (!$tmpDB->hasStarted()) {
foreach ($test->getRequireDefaultRecordsFrom() as $className) {
$instance = singleton($className);
if (method_exists($instance, 'requireDefaultRecords')) {
$instance->requireDefaultRecords();
}
if (method_exists($instance, 'augmentDefaultRecords')) {
$instance->augmentDefaultRecords();
}
}
$this->loadFixtures($test);
}
$tmpDB->startTransaction();
}
}
/**
* Called on tear down
*
* @param SapphireTest $test
*/
public function tearDown(SapphireTest $test)
{
if ($this->testNeedsDB($test)) {
$test::tempDB()->rollbackTransaction();
}
}
/**
* Called once on setup
*
* @param string $class Class being setup
*/
public function setUpOnce($class)
{
$this->fixtureFactories[strtolower($class)] = Injector::inst()->create(FixtureFactory::class);
}
/**
* Called once on tear down
*
* @param string $class Class being torn down
*/
public function tearDownOnce($class)
{
unset($this->fixtureFactories[strtolower($class)]);
$class::tempDB()->clearAllData();
}
/**
* @param string $class
*
* @return bool|FixtureFactory
*/
public function getFixtureFactory($class)
{
$testClass = strtolower($class);
if (array_key_exists($testClass, $this->fixtureFactories)) {
return $this->fixtureFactories[$testClass];
}
return false;
}
/**
* @param FixtureFactory $factory
* @param string $class
*/
public function setFixtureFactory(FixtureFactory $factory, $class)
{
$this->fixtureFactories[strtolower($class)] = $factory;
}
/**
* @param array $fixtures
*
* @param SapphireTest $test
*
* @return array
*/
protected function getFixturePaths($fixtures, SapphireTest $test)
{
return array_map(function ($fixtureFilePath) use ($test) {
return $this->resolveFixturePath($fixtureFilePath, $test);
}, $fixtures);
}
/**
* @param SapphireTest $test
*/
protected function loadFixtures(SapphireTest $test)
{
$fixtures = $test::get_fixture_file();
$fixtures = is_array($fixtures) ? $fixtures : [$fixtures];
$paths = $this->getFixturePaths($fixtures, $test);
foreach ($paths as $fixtureFile) {
$this->loadFixture($fixtureFile, $test);
}
}
/**
* @param string $fixtureFile
* @param SapphireTest $test
*/
protected function loadFixture($fixtureFile, SapphireTest $test)
{
/** @var YamlFixture $fixture */
$fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
$fixture->writeInto($this->getFixtureFactory(get_class($test)));
}
/**
* Map a fixture path to a physical file
*
* @param string $fixtureFilePath
* @param SapphireTest $test
*
* @return string
*/
protected function resolveFixturePath($fixtureFilePath, SapphireTest $test)
{
// Support fixture paths relative to the test class, rather than relative to webroot
// String checking is faster than file_exists() calls.
$isRelativeToFile
= (strpos($fixtureFilePath, '/') === false)
|| preg_match('/^(\.){1,2}/', $fixtureFilePath);
if ($isRelativeToFile) {
$resolvedPath = realpath($this->getTestAbsolutePath($test) . '/' . $fixtureFilePath);
if ($resolvedPath) {
return $resolvedPath;
}
}
// Check if file exists relative to base dir
$resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
if ($resolvedPath) {
return $resolvedPath;
}
return $fixtureFilePath;
}
/**
* Useful for writing unit tests without hardcoding folder structures.
*
* @param SapphireTest $test
*
* @return string Absolute path to current class.
*/
protected function getTestAbsolutePath(SapphireTest $test)
{
$filename = ClassLoader::inst()->getItemPath(get_class($test));
if (!$filename) {
throw new LogicException('getItemPath returned null for ' . static::class
. '. Try adding flush=1 to the test run.');
}
return dirname($filename);
}
/**
* @param SapphireTest $test
*
* @return bool
*/
protected function testNeedsDB(SapphireTest $test)
{
$annotations = $test->getAnnotations();
// annotation explicitly disables the DB
if (array_key_exists('useDatabase', $annotations['method'])
&& $annotations['method']['useDatabase'][0] === 'false') {
return false;
}
// annotation explicitly enables the DB
if (array_key_exists('useDatabase', $annotations['method'])
&& $annotations['method']['useDatabase'][0] !== 'false') {
return true;
}
// test class explicitly enables DB
if ($test->getUsesDatabase()) {
return true;
}
// presence of fixture file implicitly enables DB
$fixtures = $test::get_fixture_file();
if (!empty($fixtures)) {
return true;
}
return false;
}
}

View File

@ -22,6 +22,36 @@ class SapphireTestState implements TestState
return $this->states; return $this->states;
} }
/**
* @param string $name
*
* @return bool|TestState
*/
public function getStateByName($name)
{
$states = $this->getStates();
if (array_key_exists($name, $states)) {
return $states[$name];
}
return false;
}
/**
* @param string $class
*
* @return bool|TestState
*/
public function getStateByClass($class)
{
$lClass = strtolower($class);
foreach ($this->getStates() as $state) {
if ($lClass === strtolower(get_class($state))) {
return $state;
}
}
return false;
}
/** /**
* @param TestState[] $states * @param TestState[] $states
* @return $this * @return $this

View File

@ -23,6 +23,11 @@ class TempDatabase
*/ */
protected $name = null; protected $name = null;
/**
* @var bool If a transaction has been started
*/
protected $hasStarted = false;
/** /**
* Create a new temp database * Create a new temp database
* *
@ -68,6 +73,46 @@ class TempDatabase
return $this->isDBTemp($selected); return $this->isDBTemp($selected);
} }
/**
* @return bool
*/
public function hasStarted()
{
return $this->hasStarted;
}
/**
* @return bool
*/
public function supportsTransactions()
{
return static::getConn()->supportsTransactions();
}
/**
* Start a transaction for easy rollback after tests
*/
public function startTransaction()
{
$this->hasStarted = true;
if (static::getConn()->supportsTransactions()) {
static::getConn()->transactionStart();
}
}
/**
* Rollback a transaction (or trash all data if the DB doesn't support databases
*/
public function rollbackTransaction()
{
if (static::getConn()->supportsTransactions()) {
static::getConn()->transactionRollback();
} else {
$this->hasStarted = false;
static::clearAllData();
}
}
/** /**
* Destroy the current temp database * Destroy the current temp database
*/ */
@ -102,6 +147,7 @@ class TempDatabase
*/ */
public function clearAllData() public function clearAllData()
{ {
$this->hasStarted = false;
if (!$this->isUsed()) { if (!$this->isUsed()) {
return; return;
} }
@ -183,6 +229,11 @@ class TempDatabase
*/ */
public function resetDBSchema(array $extraDataObjects = []) public function resetDBSchema(array $extraDataObjects = [])
{ {
// pgsql doesn't allow schema updates inside transactions
// so we need to rollback any transactions before commencing a schema reset
if ($this->hasStarted()) {
$this->rollbackTransaction();
}
if (!$this->isUsed()) { if (!$this->isUsed()) {
return; return;
} }

View File

@ -100,6 +100,7 @@ class DataObjectSchema
* @param string $class Class name (not a table). * @param string $class Class name (not a table).
* @param string $field Name of field that belongs to this class (or a parent class) * @param string $field Name of field that belongs to this class (or a parent class)
* @param string $tablePrefix Optional prefix for table (alias) * @param string $tablePrefix Optional prefix for table (alias)
*
* @return string The SQL identifier string for the corresponding column for this field * @return string The SQL identifier string for the corresponding column for this field
*/ */
public function sqlColumnForField($class, $field, $tablePrefix = null) public function sqlColumnForField($class, $field, $tablePrefix = null)
@ -118,6 +119,7 @@ class DataObjectSchema
* the name that would be used if this table did exist. * the name that would be used if this table did exist.
* *
* @param string $class * @param string $class
*
* @return string Returns the table name, or null if there is no table * @return string Returns the table name, or null if there is no table
*/ */
public function tableName($class) public function tableName($class)
@ -135,6 +137,7 @@ class DataObjectSchema
* passed class. * passed class.
* *
* @param string|object $class * @param string|object $class
*
* @return string * @return string
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
@ -155,6 +158,7 @@ class DataObjectSchema
* Get the base table * Get the base table
* *
* @param string|object $class * @param string|object $class
*
* @return string * @return string
*/ */
public function baseDataTable($class) public function baseDataTable($class)
@ -185,6 +189,7 @@ class DataObjectSchema
* - UNINHERITED Limit to only this table * - UNINHERITED Limit to only this table
* - DB_ONLY Exclude virtual fields (such as composite fields), and only include fields with a db column. * - DB_ONLY Exclude virtual fields (such as composite fields), and only include fields with a db column.
* - INCLUDE_CLASS Prefix the field specification with the class name in RecordClass.Column(spec) format. * - INCLUDE_CLASS Prefix the field specification with the class name in RecordClass.Column(spec) format.
*
* @return array List of fields, where the key is the field name and the value is the field specification. * @return array List of fields, where the key is the field name and the value is the field specification.
*/ */
public function fieldSpecs($classOrInstance, $options = 0) public function fieldSpecs($classOrInstance, $options = 0)
@ -235,6 +240,7 @@ class DataObjectSchema
* - UNINHERITED Limit to only this table * - UNINHERITED Limit to only this table
* - DB_ONLY Exclude virtual fields (such as composite fields), and only include fields with a db column. * - DB_ONLY Exclude virtual fields (such as composite fields), and only include fields with a db column.
* - INCLUDE_CLASS Prefix the field specification with the class name in RecordClass.Column(spec) format. * - INCLUDE_CLASS Prefix the field specification with the class name in RecordClass.Column(spec) format.
*
* @return string|null Field will be a string in FieldClass(args) format, or * @return string|null Field will be a string in FieldClass(args) format, or
* RecordClass.FieldClass(args) format if using INCLUDE_CLASS. Will be null if no field is found. * RecordClass.FieldClass(args) format if using INCLUDE_CLASS. Will be null if no field is found.
*/ */
@ -248,6 +254,7 @@ class DataObjectSchema
* Find the class for the given table * Find the class for the given table
* *
* @param string $table * @param string $table
*
* @return string|null The FQN of the class, or null if not found * @return string|null The FQN of the class, or null if not found
*/ */
public function tableClass($table) public function tableClass($table)
@ -303,6 +310,7 @@ class DataObjectSchema
* See dev/build errors for details in case of table name violation. * See dev/build errors for details in case of table name violation.
* *
* @param string $class * @param string $class
*
* @return string * @return string
*/ */
protected function buildTableName($class) protected function buildTableName($class)
@ -334,6 +342,7 @@ class DataObjectSchema
* *
* @param string $class Class name to query from * @param string $class Class name to query from
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
*
* @return array Map of fieldname to specification, similiar to {@link DataObject::$db}. * @return array Map of fieldname to specification, similiar to {@link DataObject::$db}.
*/ */
public function databaseFields($class, $aggregated = true) public function databaseFields($class, $aggregated = true)
@ -360,6 +369,7 @@ class DataObjectSchema
* @param string $class Class name to query from * @param string $class Class name to query from
* @param string $field Field name * @param string $field Field name
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
*
* @return string|null Field specification, or null if not a field * @return string|null Field specification, or null if not a field
*/ */
public function databaseField($class, $field, $aggregated = true) public function databaseField($class, $field, $aggregated = true)
@ -392,6 +402,7 @@ class DataObjectSchema
* Check if the given class has a table * Check if the given class has a table
* *
* @param string $class * @param string $class
*
* @return bool * @return bool
*/ */
public function classHasTable($class) public function classHasTable($class)
@ -415,6 +426,7 @@ class DataObjectSchema
* *
* @param string $class Name of class to check * @param string $class Name of class to check
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
*
* @return array List of composite fields and their class spec * @return array List of composite fields and their class spec
*/ */
public function compositeFields($class, $aggregated = true) public function compositeFields($class, $aggregated = true)
@ -442,6 +454,7 @@ class DataObjectSchema
* @param string $class Class name to query from * @param string $class Class name to query from
* @param string $field Field name * @param string $field Field name
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table * @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
*
* @return string|null Field specification, or null if not a field * @return string|null Field specification, or null if not a field
*/ */
public function compositeField($class, $field, $aggregated = true) public function compositeField($class, $field, $aggregated = true)
@ -535,6 +548,7 @@ class DataObjectSchema
* Get "default" database indexable field types * Get "default" database indexable field types
* *
* @param string $class * @param string $class
*
* @return array * @return array
*/ */
protected function cacheDefaultDatabaseIndexes($class) protected function cacheDefaultDatabaseIndexes($class)
@ -559,6 +573,7 @@ class DataObjectSchema
* Look for custom indexes declared on the class * Look for custom indexes declared on the class
* *
* @param string $class * @param string $class
*
* @return array * @return array
* @throws InvalidArgumentException If an index already exists on the class * @throws InvalidArgumentException If an index already exists on the class
* @throws InvalidArgumentException If a custom index format is not valid * @throws InvalidArgumentException If a custom index format is not valid
@ -637,6 +652,7 @@ class DataObjectSchema
* Parses a specified column into a sort field and direction * Parses a specified column into a sort field and direction
* *
* @param string $column String to parse containing the column name * @param string $column String to parse containing the column name
*
* @return array Resolved table and column. * @return array Resolved table and column.
*/ */
protected function parseSortColumn($column) protected function parseSortColumn($column)
@ -659,6 +675,7 @@ class DataObjectSchema
* *
* @param string $candidateClass * @param string $candidateClass
* @param string $fieldName * @param string $fieldName
*
* @return string * @return string
*/ */
public function tableForField($candidateClass, $fieldName) public function tableForField($candidateClass, $fieldName)
@ -677,6 +694,7 @@ class DataObjectSchema
* *
* @param string $candidateClass * @param string $candidateClass
* @param string $fieldName * @param string $fieldName
*
* @return string * @return string
*/ */
public function classForField($candidateClass, $fieldName) public function classForField($candidateClass, $fieldName)
@ -720,8 +738,10 @@ class DataObjectSchema
* If the class name is 'ManyManyThroughList' then this is the name of the * If the class name is 'ManyManyThroughList' then this is the name of the
* has_many relation. * has_many relation.
* ) * )
*
* @param string $class Name of class to get component for * @param string $class Name of class to get component for
* @param string $component The component name * @param string $component The component name
*
* @return array|null * @return array|null
*/ */
public function manyManyComponent($class, $component) public function manyManyComponent($class, $component)
@ -768,6 +788,7 @@ class DataObjectSchema
* @param string $parentClass Name of class * @param string $parentClass Name of class
* @param string $component Name of relation on class * @param string $component Name of relation on class
* @param string $specification specification for this belongs_many_many * @param string $specification specification for this belongs_many_many
*
* @return array Array with child class and relation name * @return array Array with child class and relation name
*/ */
protected function parseBelongsManyManyComponent($parentClass, $component, $specification) protected function parseBelongsManyManyComponent($parentClass, $component, $specification)
@ -802,7 +823,7 @@ class DataObjectSchema
// Return relatios // Return relatios
return [ return [
'childClass' => $childClass, 'childClass' => $childClass,
'relationName' => $relationName 'relationName' => $relationName,
]; ];
} }
@ -811,6 +832,7 @@ class DataObjectSchema
* *
* @param string $class * @param string $class
* @param string $component * @param string $component
*
* @return array|null * @return array|null
*/ */
public function manyManyExtraFieldsForComponent($class, $component) public function manyManyExtraFieldsForComponent($class, $component)
@ -845,6 +867,7 @@ class DataObjectSchema
* @param string $component * @param string $component
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form * @param bool $classOnly If this is TRUE, than any has_many relationships in the form
* "ClassName.Field" will have the field data stripped off. It defaults to TRUE. * "ClassName.Field" will have the field data stripped off. It defaults to TRUE.
*
* @return string|null * @return string|null
*/ */
public function hasManyComponent($class, $component, $classOnly = true) public function hasManyComponent($class, $component, $classOnly = true)
@ -868,6 +891,7 @@ class DataObjectSchema
* *
* @param string $class * @param string $class
* @param string $component * @param string $component
*
* @return string|null * @return string|null
*/ */
public function hasOneComponent($class, $component) public function hasOneComponent($class, $component)
@ -890,6 +914,7 @@ class DataObjectSchema
* @param string $component * @param string $component
* @param bool $classOnly If this is TRUE, than any has_many relationships in the * @param bool $classOnly If this is TRUE, than any has_many relationships in the
* form "ClassName.Field" will have the field data stripped off. It defaults to TRUE. * form "ClassName.Field" will have the field data stripped off. It defaults to TRUE.
*
* @return string|null * @return string|null
*/ */
public function belongsToComponent($class, $component, $classOnly = true) public function belongsToComponent($class, $component, $classOnly = true)
@ -912,8 +937,10 @@ class DataObjectSchema
* Check class for any unary component * Check class for any unary component
* *
* Alias for hasOneComponent() ?: belongsToComponent() * Alias for hasOneComponent() ?: belongsToComponent()
*
* @param string $class * @param string $class
* @param string $component * @param string $component
*
* @return string|null * @return string|null
*/ */
public function unaryComponent($class, $component) public function unaryComponent($class, $component)
@ -926,6 +953,7 @@ class DataObjectSchema
* @param string $parentClass Parent class name * @param string $parentClass Parent class name
* @param string $component ManyMany name * @param string $component ManyMany name
* @param string|array $specification Declaration of many_many relation type * @param string|array $specification Declaration of many_many relation type
*
* @return array * @return array
*/ */
protected function parseManyManyComponent($parentClass, $component, $specification) protected function parseManyManyComponent($parentClass, $component, $specification)
@ -975,6 +1003,7 @@ class DataObjectSchema
* *
* @param string $childClass * @param string $childClass
* @param string $parentClass * @param string $parentClass
*
* @return string|null * @return string|null
*/ */
protected function getManyManyInverseRelationship($childClass, $parentClass) protected function getManyManyInverseRelationship($childClass, $parentClass)
@ -992,10 +1021,10 @@ class DataObjectSchema
if (is_array($manyManySpec)) { if (is_array($manyManySpec)) {
$toClass = $this->hasOneComponent($manyManySpec['through'], $manyManySpec['to']); $toClass = $this->hasOneComponent($manyManySpec['through'], $manyManySpec['to']);
if ($toClass === $parentClass) { if ($toClass === $parentClass) {
return $inverseComponentName; return $inverseComponentName;
}
} }
} }
}
return null; return null;
} }
@ -1011,6 +1040,7 @@ class DataObjectSchema
* remote object. * remote object.
* @param string $type the join type - either 'has_many' or 'belongs_to' * @param string $type the join type - either 'has_many' or 'belongs_to'
* @param boolean $polymorphic Flag set to true if the remote join field is polymorphic. * @param boolean $polymorphic Flag set to true if the remote join field is polymorphic.
*
* @return string * @return string
* @throws Exception * @throws Exception
*/ */
@ -1091,6 +1121,7 @@ class DataObjectSchema
* @param string $joinClass Class for the joined table * @param string $joinClass Class for the joined table
* @param array $specification Complete many_many specification * @param array $specification Complete many_many specification
* @param string $key Name of key to check ('from' or 'to') * @param string $key Name of key to check ('from' or 'to')
*
* @return string Class that matches the given relation * @return string Class that matches the given relation
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
@ -1152,6 +1183,7 @@ class DataObjectSchema
* @param string $parentClass Name of parent class * @param string $parentClass Name of parent class
* @param string $component Name of many_many component * @param string $component Name of many_many component
* @param array $specification Complete many_many specification * @param array $specification Complete many_many specification
*
* @return string Name of join class * @return string Name of join class
*/ */
protected function checkManyManyJoinClass($parentClass, $component, $specification) protected function checkManyManyJoinClass($parentClass, $component, $specification)