summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes3
-rw-r--r--UPGRADE7
-rw-r--r--demos/northwind-db/protected/database/Employee.php2
-rw-r--r--demos/quickstart/protected/pages/Database/ActiveRecord.page52
-rw-r--r--demos/quickstart/protected/pages/Database/ar_objects.pngbin19837 -> 20638 bytes
-rw-r--r--demos/quickstart/protected/pages/Database/ar_objects.vsdbin190976 -> 190976 bytes
-rw-r--r--demos/quickstart/protected/pages/Database/id/ActiveRecord.page8
-rw-r--r--framework/Caching/TXCache.php126
-rw-r--r--framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php13
-rw-r--r--framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php17
-rw-r--r--framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php24
-rw-r--r--framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php14
-rw-r--r--framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php60
-rw-r--r--framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php91
-rw-r--r--framework/Data/ActiveRecord/TActiveRecord.php75
-rw-r--r--tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php9
-rw-r--r--tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php5
-rw-r--r--tests/simple_unit/ActiveRecord/MultipleForeignKeyTestCase.php179
-rw-r--r--tests/simple_unit/ActiveRecord/records/ItemRecord.php2
-rw-r--r--tests/simple_unit/ActiveRecord/test1.sqlitebin0 -> 6144 bytes
-rw-r--r--tests/test_tools/simpletest/test_case.php3
21 files changed, 607 insertions, 83 deletions
diff --git a/.gitattributes b/.gitattributes
index 298a0308..a37df6f5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1969,6 +1969,7 @@ framework/Caching/TCache.php -text
framework/Caching/TDbCache.php -text
framework/Caching/TMemCache.php -text
framework/Caching/TSqliteCache.php -text
+framework/Caching/TXCache.php -text
framework/Collections/TAttributeCollection.php -text
framework/Collections/TDummyDataSource.php -text
framework/Collections/TList.php -text
@@ -3077,6 +3078,7 @@ tests/simple_unit/ActiveRecord/FindByPksTestCase.php -text
tests/simple_unit/ActiveRecord/FindBySqlTestCase.php -text
tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php -text
tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php -text
+tests/simple_unit/ActiveRecord/MultipleForeignKeyTestCase.php -text
tests/simple_unit/ActiveRecord/RecordEventTestCase.php -text
tests/simple_unit/ActiveRecord/SqliteTestCase.php -text
tests/simple_unit/ActiveRecord/UserRecordTestCase.php -text
@@ -3093,6 +3095,7 @@ tests/simple_unit/ActiveRecord/records/SimpleUser.php -text
tests/simple_unit/ActiveRecord/records/SqliteUsers.php -text
tests/simple_unit/ActiveRecord/records/UserRecord.php -text
tests/simple_unit/ActiveRecord/sqlite.sql -text
+tests/simple_unit/ActiveRecord/test1.sqlite -text
tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php -text
tests/simple_unit/DbCommon/CommandBuilderMysqlTest.php -text
tests/simple_unit/DbCommon/CommandBuilderPgsqlTest.php -text
diff --git a/UPGRADE b/UPGRADE
index 17ae3298..334f8231 100644
--- a/UPGRADE
+++ b/UPGRADE
@@ -11,6 +11,13 @@ for both A and B.
Upgrading from v3.1.1
---------------------
+- The RELATIONS type declaration in Active Record classes for Many-to-Many using
+ an association table was change from "self::HAS_MANY" to "self::MANY_TO_MANY".
+ E.g. change
+ 'albums' => array(self::HAS_MANY, 'Artist', 'album_artists')
+ to
+ 'albums' => array(self::MANY_TO_MANY, 'Artist', 'album_artists')
+
Upgrading from v3.1.0
---------------------
diff --git a/demos/northwind-db/protected/database/Employee.php b/demos/northwind-db/protected/database/Employee.php
index 92de3f24..1e0f090e 100644
--- a/demos/northwind-db/protected/database/Employee.php
+++ b/demos/northwind-db/protected/database/Employee.php
@@ -32,7 +32,7 @@ class Employee extends TActiveRecord
public static $RELATIONS = array
(
- 'Territories' => array(self::HAS_MANY, 'Territory', 'EmployeeTerritories'),
+ 'Territories' => array(self::MANY_TO_MANY, 'Territory', 'EmployeeTerritories'),
'Orders' => array(self::HAS_MANY, 'Order'),
//parent children relationship
diff --git a/demos/quickstart/protected/pages/Database/ActiveRecord.page b/demos/quickstart/protected/pages/Database/ActiveRecord.page
index a6e087b9..cb10d184 100644
--- a/demos/quickstart/protected/pages/Database/ActiveRecord.page
+++ b/demos/quickstart/protected/pages/Database/ActiveRecord.page
@@ -657,10 +657,30 @@ in Active Record by inspecting the <tt>Players</tt> and <tt>Teams</tt> table def
</p>
<div class="info"><b class="note">Info:</b>
-Active Record supports multiple table foreign key relationships with the restriction
-that each relationship corresponds to a unique table. For example, the <tt>Players</tt>
-table may only have one set of foreign key relationship with table <tt>Teams</tt>, it may
-have other relationships that corresponds to other tables (including the <tt>Players</tt> table itself).
+Since version <b>3.1.2</b>, Active Record supports multiple foreign key
+references of the same table. Ambiguity between multiple foreign key references to the same table is
+resolved by providing the foreign key column name as the 3rd parameter in the relationship array.
+For example, both the following foreign keys <tt>owner_id</tt> and <tt>reporter_id</tt>
+references the same table defined in <tt>UserRecord</tt>.
+<com:TTextHighlighter Language="php" CssClass="source block-content">
+class TicketRecord extends TActiveRecord
+{
+ public $owner_id;
+ public $reporter_id;
+
+ public $owner;
+ public $reporter;
+
+ public static $RELATION=array
+ (
+ 'owner' => array(self::BELONGS_TO, 'UserRecord', 'owner_id'),
+ 'reporter' => array(self::BELONGS_TO, 'UserRecord', 'reporter_id'),
+ );
+}
+</com:TTextHighlighter>
+This is applicable to relationships including <tt>BELONGS_TO</tt>, <tt>HAS_ONE</tt> and
+<tt>HAS_MANY</tt>. See section <a href="#142021">Self Referenced Association Tables</a> for solving ambiguity of <tt>MANY_TO_MANY</tt>
+relationships.
</div>
<p id="710023" class="block-content">The "has many" relationship is not fetched automatically when you use any of the Active Record finder methods.
@@ -713,7 +733,7 @@ class PlayerRecord extends TActiveRecord
public static $RELATIONS=array
(
'team' => array(self::BELONGS_TO, 'TeamRecord'),
- 'skills' => array(self::HAS_MANY, 'SkillRecord', 'Player_Skills'),
+ 'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'Player_Skills'),
'profile' => array(self::HAS_ONE, 'ProfileRecord'),
);
@@ -850,9 +870,9 @@ in the <tt>Player_Skills</tt> association table using an inner join.
</p>
<p id="710035" class="block-content">The Prado Active Record design implements the two stage approach. For the
-<tt>Players</tt>-<tt>Skills</tt> M-N (many-to-many) entity relationship, we need
-to define a <b>has many</b> relationship in the <tt>PlayerRecord</tt> class and
-in addition define a <b>has many</b> relationship in the <tt>SkillRecord</tt> class as well.
+<tt>Players</tt>-<tt>Skills</tt> M-N (many-to-many) entity relationship, we
+define a <b>many-to-many</b> relationship in the <tt>PlayerRecord</tt> class and
+in addition we may define a <b>many-to-many</b> relationship in the <tt>SkillRecord</tt> class as well.
The following sample code defines the complete <tt>SkillRecord</tt> class with a
many-to-many relationship with the <tt>PlayerRecord</tt> class. (See the <tt>PlayerRecord</tt>
class definition above to the corresponding many-to-many relationship with the <tt>SkillRecord</tt> class.)
@@ -869,7 +889,7 @@ class SkillRecord extends TActiveRecord
public static $RELATIONS=array
(
- 'players' => array(self::HAS_MANY, 'PlayerRecord', 'Player_Skills'),
+ 'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'),
);
public static function finder($className=__CLASS__)
@@ -882,12 +902,20 @@ class SkillRecord extends TActiveRecord
<p id="710036" class="block-content">
The static <tt>$RELATIONS</tt> property of SkillRecord defines that the
property <tt>$players</tt> has many <tt>PlayerRecord</tt>s via an association table '<tt>Player_Skills</tt>'.
-In <tt>array(self::HAS_MANY, 'PlayerRecord', 'Player_Skills')</tt>, the first element defines the
-relationship type, in this case <strong><tt>self::HAS_MANY</tt></strong>,
+In <tt>array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills')</tt>, the first element defines the
+relationship type, in this case <strong><tt>self::MANY_TO_MANY</tt></strong>,
the second element is a string <tt>'PlayerRecord'</tt> that corresponds to the
class name of the <tt>PlayerRecord</tt> class, and the third element is the name
of the association table name.
</p>
+
+<div class="note"><b class="note">Note:</b>
+Prior to version <b>3.1.2</b> (versions up to 3.1.1), the many-to-many relationship was
+defined using <tt>self::HAS_MANY</tt>. For version <b>3.1.2</b> onwards, this must be changed
+to <tt>self::MANY_TO_MANY</tt>. This can be done by searching for the <tt>HAS_MANY</tt> in your
+source code and carfully changing the appropriate definitions.
+</div>
+
<p id="710037" class="block-content">
A list of player objects with the corresponding collection of skill objects may be fetched as follows.
</p>
@@ -951,7 +979,7 @@ class Item extends TActiveRecord
public static $RELATIONS=array
(
- 'related_items' => array(self::HAS_MANY,
+ 'related_items' => array(self::MANY_TO_MANY,
'Item', 'related_items.related_item_id'),
);
}
diff --git a/demos/quickstart/protected/pages/Database/ar_objects.png b/demos/quickstart/protected/pages/Database/ar_objects.png
index 50ab812d..ac33b88b 100644
--- a/demos/quickstart/protected/pages/Database/ar_objects.png
+++ b/demos/quickstart/protected/pages/Database/ar_objects.png
Binary files differ
diff --git a/demos/quickstart/protected/pages/Database/ar_objects.vsd b/demos/quickstart/protected/pages/Database/ar_objects.vsd
index 10346c54..d3b3963d 100644
--- a/demos/quickstart/protected/pages/Database/ar_objects.vsd
+++ b/demos/quickstart/protected/pages/Database/ar_objects.vsd
Binary files differ
diff --git a/demos/quickstart/protected/pages/Database/id/ActiveRecord.page b/demos/quickstart/protected/pages/Database/id/ActiveRecord.page
index b7b9e612..7273640d 100644
--- a/demos/quickstart/protected/pages/Database/id/ActiveRecord.page
+++ b/demos/quickstart/protected/pages/Database/id/ActiveRecord.page
@@ -615,7 +615,7 @@ class PlayerRecord extends TActiveRecord
public static $RELATIONS=array
(
'team' => array(self::BELONGS_TO, 'TeamRecord'),
- 'skills' => array(self::HAS_MANY, 'SkillRecord', 'Player_Skills'),
+ 'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'Player_Skills'),
'profile' => array(self::HAS_ONE, 'ProfileRecord'),
);
@@ -737,7 +737,7 @@ class SkillRecord extends TActiveRecord
public static $RELATIONS=array
(
- 'players' => array(self::HAS_MANY, 'PlayerRecord', 'Player_Skills'),
+ 'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'),
);
public static function finder($className=__CLASS__)
@@ -749,7 +749,7 @@ class SkillRecord extends TActiveRecord
<p id="710036" class="block-content">
Properti statis <tt>$RELATIONS</tt> dari SkillRecord mendefinisikan bahwa properti <tt>$players</tt> memiliki banyak <tt>PlayerRecord</tt>s melalui tabel asosiasi '<tt>Player_Skills</tt>'.
-Dalam <tt>array(self::HAS_MANY, 'PlayerRecord', 'Player_Skills')</tt>, elemen pertama mendefinisikan tipe hubungan, dalam hal ini <strong><tt>self::HAS_MANY</tt></strong>,
+Dalam <tt>array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills')</tt>, elemen pertama mendefinisikan tipe hubungan, dalam hal ini <strong><tt>self::HAS_MANY</tt></strong>,
elemen kedua adalah string <tt>'PlayerRecord'</tt> yang terkait ke nama kelas dari kelas <tt>PlayerRecord</tt>, dan elemen ketiga adalah nama dari nama tabel asosiasi.
</p>
<p id="710037" class="block-content">
@@ -805,7 +805,7 @@ class Item extends TActiveRecord
public static $RELATIONS=array
(
- 'related_items' => array(self::HAS_MANY,
+ 'related_items' => array(self::MANY_TO_MANY,
'Item', 'related_items.related_item_id'),
);
}
diff --git a/framework/Caching/TXCache.php b/framework/Caching/TXCache.php
new file mode 100644
index 00000000..698020af
--- /dev/null
+++ b/framework/Caching/TXCache.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * TXCache class file
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2007 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Id: TXCache.php 1994 2007-06-11 16:02:28Z knut $
+ * @package System.Caching
+ */
+
+/**
+ * TXCache class
+ *
+ * TXCache implements a cache application module based on {@link http://xcache.lighttpd.net/ xcache}.
+ *
+ * By definition, cache does not ensure the existence of a value
+ * even if it never expires. Cache is not meant to be an persistent storage.
+ *
+ * To use this module, the xcache PHP extension must be loaded and configured in the php.ini.
+ *
+ * Some usage examples of TXCache are as follows,
+ * <code>
+ * $cache=new TXCache; // TXCache may also be loaded as a Prado application module
+ * $cache->init(null);
+ * $cache->add('object',$object);
+ * $object2=$cache->get('object');
+ * </code>
+ *
+ * If loaded, TXCache will register itself with {@link TApplication} as the
+ * cache module. It can be accessed via {@link TApplication::getCache()}.
+ *
+ * TXCache may be configured in application configuration file as follows
+ * <code>
+ * <module id="cache" class="System.Caching.TXCache" />
+ * </code>
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Id: TXCache.php 1994 2007-06-11 16:02:28Z knut $
+ * @package System.Caching
+ * @since 3.1.1
+ */
+class TXCache extends TCache
+{
+ /**
+ * Initializes this module.
+ * This method is required by the IModule interface.
+ * @param TXmlElement configuration for this module, can be null
+ * @throws TConfigurationException if xcache extension is not installed or not started, check your php.ini
+ */
+ public function init($config)
+ {
+ if(!function_exists('xcache_isset'))
+ throw new TConfigurationException('xcache_extension_required');
+
+ $enabled = intval(ini_get('xcache.cacher')) !== 0;
+ $var_size = intval(ini_get('xcache.var_size'));
+
+ if(!($enabled && $var_size > 0))
+ throw new TConfigurationException('xcache_extension_not_enabled');
+
+ parent::init($config);
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string a unique key identifying the cached value
+ * @return string the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return xcache_isset($key) ? xcache_get($key) : false;
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string the key identifying the value to be cached
+ * @param string the value to be cached
+ * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key,$value,$expire)
+ {
+ return xcache_set($key,$value,$expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string the key identifying the value to be cached
+ * @param string the value to be cached
+ * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key,$value,$expire)
+ {
+ return !xcache_isset($key) ? $this->setValue($key,$value,$expire) : false;
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return xcache_unset($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared by multiple applications.
+ */
+ public function flush()
+ {
+ return xcache_clear_cache();
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
index d7278d64..d4ff07e5 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
@@ -77,8 +77,7 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation
*/
protected function collectForeignObjects(&$results)
{
- $fkObject = $this->getContext()->getForeignRecordFinder();
- $fkeys = $this->findForeignKeys($this->getSourceRecord(),$fkObject);
+ $fkeys = $this->getRelationForeignKeys();
$properties = array_keys($fkeys);
$fields = array_values($fkeys);
@@ -87,6 +86,16 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation
$fkObjects = $this->findForeignObjects($fields, $indexValues);
$this->populateResult($results,$properties,$fkObjects,$fields);
}
+
+ /**
+ * @return array foreign key field names as key and object properties as value.
+ * @since 3.1.2
+ */
+ public function getRelationForeignKeys()
+ {
+ $fkObject = $this->getContext()->getForeignRecordFinder();
+ return $this->findForeignKeys($this->getSourceRecord(),$fkObject);
+ }
/**
* Sets the foreign objects to the given property on the source object.
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php
index cbba3ebd..f7426862 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php
@@ -1,4 +1,4 @@
-<?php
+<?php
/**
* TActiveRecordHasMany class file.
*
@@ -75,8 +75,7 @@ class TActiveRecordHasMany extends TActiveRecordRelation
*/
protected function collectForeignObjects(&$results)
{
- $fkObject = $this->getContext()->getForeignRecordFinder();
- $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord());
+ $fkeys = $this->getRelationForeignKeys();
$properties = array_values($fkeys);
$fields = array_keys($fkeys);
@@ -87,6 +86,16 @@ class TActiveRecordHasMany extends TActiveRecordRelation
}
/**
+ * @return array foreign key field names as key and object properties as value.
+ * @since 3.1.2
+ */
+ public function getRelationForeignKeys()
+ {
+ $fkObject = $this->getContext()->getForeignRecordFinder();
+ return $this->findForeignKeys($fkObject, $this->getSourceRecord());
+ }
+
+ /**
* Updates the associated foreign objects.
* @return boolean true if all update are success (including if no update was required), false otherwise .
*/
@@ -113,5 +122,5 @@ class TActiveRecordHasMany extends TActiveRecordRelation
return $success;
}
}
-
+
?> \ No newline at end of file
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php
index 0e176607..564d3d22 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php
@@ -37,7 +37,7 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelation');
*
* public static $RELATIONS = array
* (
- * 'Categories' => array(self::HAS_MANY, 'CategoryRecord', 'Article_Category')
+ * 'Categories' => array(self::MANY_TO_MANY, 'CategoryRecord', 'Article_Category')
* );
*
* public static function finder($className=__CLASS__)
@@ -54,7 +54,7 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelation');
*
* public static $RELATIONS = array
* (
- * 'Articles' => array(self::HAS_MANY, 'ArticleRecord', 'Article_Category')
+ * 'Articles' => array(self::MANY_TO_MANY, 'ArticleRecord', 'Article_Category')
* );
*
* public static function finder($className=__CLASS__)
@@ -96,17 +96,22 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation
*/
protected function collectForeignObjects(&$results)
{
- $association = $this->getAssociationTable();
- $sourceKeys = $this->findForeignKeys($association, $this->getSourceRecord());
-
+ list($sourceKeys, $foreignKeys) = $this->getRelationForeignKeys();
$properties = array_values($sourceKeys);
-
$indexValues = $this->getIndexValues($properties, $results);
+ $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys);
+ }
+ /**
+ * @return array 2 arrays of source keys and foreign keys from the association table.
+ */
+ public function getRelationForeignKeys()
+ {
+ $association = $this->getAssociationTable();
+ $sourceKeys = $this->findForeignKeys($association, $this->getSourceRecord(), true);
$fkObject = $this->getContext()->getForeignRecordFinder();
$foreignKeys = $this->findForeignKeys($association, $fkObject);
-
- $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys);
+ return array($sourceKeys, $foreignKeys);
}
/**
@@ -182,7 +187,7 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation
*/
protected function fetchForeignObjects(&$results,$foreignKeys,$indexValues,$sourceKeys)
{
- $criteria = $this->getContext()->getCriteria();
+ $criteria = $this->getCriteria();
$finder = $this->getContext()->getForeignRecordFinder();
$registry = $finder->getRecordManager()->getObjectStateRegistry();
$type = get_class($finder);
@@ -198,7 +203,6 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation
$collections[$hash][] = $obj;
$registry->registerClean($obj);
}
-
$this->setResultCollection($results, $collections, array_values($sourceKeys));
}
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php
index b22428ae..e5a36659 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php
@@ -92,9 +92,7 @@ class TActiveRecordHasOne extends TActiveRecordRelation
*/
protected function collectForeignObjects(&$results)
{
- $fkObject = $this->getContext()->getForeignRecordFinder();
- $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord());
-
+ $fkeys = $this->getRelationForeignKeys();
$properties = array_values($fkeys);
$fields = array_keys($fkeys);
@@ -102,6 +100,16 @@ class TActiveRecordHasOne extends TActiveRecordRelation
$fkObjects = $this->findForeignObjects($fields,$indexValues);
$this->populateResult($results,$properties,$fkObjects,$fields);
}
+
+ /**
+ * @return array foreign key field names as key and object properties as value.
+ * @since 3.1.2
+ */
+ public function getRelationForeignKeys()
+ {
+ $fkObject = $this->getContext()->getForeignRecordFinder();
+ return $this->findForeignKeys($fkObject, $this->getSourceRecord());
+ }
/**
* Sets the foreign objects to the given property on the source object.
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
index 5a3ea50e..8e9cc9b5 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
@@ -26,10 +26,12 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext');
abstract class TActiveRecordRelation
{
private $_context;
+ private $_criteria;
- public function __construct(TActiveRecordRelationContext $context)
+ public function __construct(TActiveRecordRelationContext $context, $criteria)
{
$this->_context = $context;
+ $this->_criteria = $criteria;
}
/**
@@ -39,6 +41,14 @@ abstract class TActiveRecordRelation
{
return $this->_context;
}
+
+ /**
+ * @return TActiveRecordCriteria
+ */
+ protected function getCriteria()
+ {
+ return $this->_criteria;
+ }
/**
* @return TActiveRecord
@@ -75,6 +85,16 @@ abstract class TActiveRecordRelation
array_push($stack,$this); //call it later
return $results;
}
+
+ /**
+ * Fetch results for current relationship.
+ * @return boolean always true.
+ */
+ public function fetchResultsInto($obj)
+ {
+ $this->collectForeignObjects($obj);
+ return true;
+ }
/**
* Returns foreign keys in $fromRecord with source column names as key
@@ -84,7 +104,7 @@ abstract class TActiveRecordRelation
* @param TActiveRecord $matchesRecord
* @return array foreign keys with source column names as key and foreign column names as value.
*/
- protected function findForeignKeys($from, $matchesRecord)
+ protected function findForeignKeys($from, $matchesRecord, $loose=false)
{
$gateway = $matchesRecord->getRecordGateway();
$matchingTableName = $gateway->getRecordTableInfo($matchesRecord)->getTableName();
@@ -93,13 +113,42 @@ abstract class TActiveRecordRelation
$tableInfo = $gateway->getRecordTableInfo($from);
foreach($tableInfo->getForeignKeys() as $fkeys)
{
- if($fkeys['table']===$matchingTableName)
- return $fkeys['keys'];
+ if(strtolower($fkeys['table'])===strtolower($matchingTableName))
+ {
+ if(!$loose && $this->getContext()->hasFkField())
+ return $this->getFkFields($fkeys['keys']);
+ else
+ return $fkeys['keys'];
+ }
}
$matching = $gateway->getRecordTableInfo($matchesRecord)->getTableFullName();
throw new TActiveRecordException('ar_relations_missing_fk',
$tableInfo->getTableFullName(), $matching);
}
+
+ /**
+ * @return array foreign key field names as key and object properties as value.
+ * @since 3.1.2
+ */
+ abstract public function getRelationForeignKeys();
+
+ /**
+ * Find matching foreign key fields from the 3rd element of an entry in TActiveRecord::$RELATION.
+ * Assume field names consist of [\w-] character sets. Prefix to the field names ending with a dot
+ * are ignored.
+ */
+ private function getFkFields($fkeys)
+ {
+ $matching = array();
+ preg_match_all('/\s*(\S+\.)?([\w-]+)\s*/', $this->getContext()->getFkField(), $matching);
+ $fields = array();
+ foreach($fkeys as $fkName => $field)
+ {
+ if(in_array($fkName, $matching[2]))
+ $fields[$fkName] = $field;
+ }
+ return $fields;
+ }
/**
* @param mixed object or array to be hashed
@@ -122,9 +171,8 @@ abstract class TActiveRecordRelation
*/
protected function findForeignObjects($fields, $indexValues)
{
- $criteria = $this->getContext()->getCriteria();
$finder = $this->getContext()->getForeignRecordFinder();
- return $finder->findAllByIndex($criteria, $fields, $indexValues);
+ return $finder->findAllByIndex($this->_criteria, $fields, $indexValues);
}
/**
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
index 189d2c5e..d83aa63a 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
@@ -32,13 +32,12 @@ class TActiveRecordRelationContext
private $_property;
private $_sourceRecord;
- private $_criteria;
- private $_relation;
+ private $_relation; //data from an entry of TActiveRecord::$RELATION
+ private $_fkeys;
- public function __construct($source, $property=null, $criteria=null)
+ public function __construct($source, $property=null)
{
$this->_sourceRecord=$source;
- $this->_criteria=$criteria;
if($property!==null)
list($this->_property, $this->_relation) = $this->getSourceRecordRelation($property);
}
@@ -48,7 +47,6 @@ class TActiveRecordRelationContext
* property from the $RELATIONS static property in TActiveRecord.
* @param string relation property name
* @return array array($propertyName, $relation) relation definition.
- * @throws TActiveRecordException if property is not defined or missing.
*/
protected function getSourceRecordRelation($property)
{
@@ -58,8 +56,6 @@ class TActiveRecordRelationContext
if(strtolower($name)===$property)
return array($name, $relation);
}
- throw new TActiveRecordException('ar_undefined_relation_prop',
- $property, get_class($this->_sourceRecord), self::RELATIONS_CONST);
}
/**
@@ -71,6 +67,15 @@ class TActiveRecordRelationContext
return $class->getStaticPropertyValue(self::RELATIONS_CONST);
}
+ /**
+ * @return boolean true if the relation is defined in TActiveRecord::$RELATIONS
+ * @since 3.1.2
+ */
+ public function hasRecordRelation()
+ {
+ return $this->_relation!==null;
+ }
+
public function getPropertyValue()
{
$obj = $this->getSourceRecord();
@@ -86,14 +91,6 @@ class TActiveRecordRelationContext
}
/**
- * @return TActiveRecordCriteria sql query criteria for fetching the related record.
- */
- public function getCriteria()
- {
- return $this->_criteria;
- }
-
- /**
* @return TActiveRecord the active record instance that queried for its related records.
*/
public function getSourceRecord()
@@ -110,6 +107,18 @@ class TActiveRecordRelationContext
}
/**
+ * @return array foreign key of this relations, the keys is dependent on the
+ * relationship type.
+ * @since 3.1.2
+ */
+ public function getRelationForeignKeys()
+ {
+ if($this->_fkeys===null)
+ $this->_fkeys=$this->getRelationHandler()->getRelationForeignKeys();
+ return $this->_fkeys;
+ }
+
+ /**
* @return string HAS_MANY, HAS_ONE, or BELONGS_TO
*/
public function getRelationType()
@@ -118,6 +127,25 @@ class TActiveRecordRelationContext
}
/**
+ * @return string foreign key field names, comma delimited.
+ * @since 3.1.2
+ */
+ public function getFkField()
+ {
+ return $this->_relation[2];
+ }
+
+ /**
+ * @return boolean true if the 3rd element of an TActiveRecord::$RELATION entry is set.
+ * @since 3.1.2
+ */
+ public function hasFkField()
+ {
+ $notManyToMany = $this->getRelationType() !== TActiveRecord::MANY_TO_MANY;
+ return $notManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]);
+ }
+
+ /**
* @return string the M-N relationship association table name.
*/
public function getAssociationTable()
@@ -130,7 +158,8 @@ class TActiveRecordRelationContext
*/
public function hasAssociationTable()
{
- return isset($this->_relation[2]);
+ $isManyToMany = $this->getRelationType() === TActiveRecord::MANY_TO_MANY;
+ return $isManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]);
}
/**
@@ -145,29 +174,33 @@ class TActiveRecordRelationContext
* Creates and return the TActiveRecordRelation handler for specific relationships.
* An instance of TActiveRecordHasOne, TActiveRecordBelongsTo, TActiveRecordHasMany,
* or TActiveRecordHasManyAssocation will be returned.
+ * @param TActiveRecordCriteria search criteria
* @return TActiveRecordRelation record relationship handler instnace.
+ * @throws TActiveRecordException if property is not defined or missing.
*/
- public function getRelationHandler()
+ public function getRelationHandler($criteria=null)
{
+ if(!$this->hasRecordRelation())
+ {
+ throw new TActiveRecordException('ar_undefined_relation_prop',
+ $property, get_class($this->_sourceRecord), self::RELATIONS_CONST);
+ }
+ if($criteria===null)
+ $criteria = new TActiveRecordCriteria;
switch($this->getRelationType())
{
case TActiveRecord::HAS_MANY:
- if(!$this->hasAssociationTable())
- {
- Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany');
- return new TActiveRecordHasMany($this);
- }
- else
- {
- Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasManyAssociation');
- return new TActiveRecordHasManyAssociation($this);
- }
+ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany');
+ return new TActiveRecordHasMany($this, $criteria);
+ case TActiveRecord::MANY_TO_MANY:
+ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasManyAssociation');
+ return new TActiveRecordHasManyAssociation($this, $criteria);
case TActiveRecord::HAS_ONE:
Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasOne');
- return new TActiveRecordHasOne($this);
+ return new TActiveRecordHasOne($this, $criteria);
case TActiveRecord::BELONGS_TO:
Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordBelongsTo');
- return new TActiveRecordBelongsTo($this);
+ return new TActiveRecordBelongsTo($this, $criteria);
default:
throw new TActiveRecordException('ar_invalid_relationship');
}
diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php
index b53cdf45..446c87e4 100644
--- a/framework/Data/ActiveRecord/TActiveRecord.php
+++ b/framework/Data/ActiveRecord/TActiveRecord.php
@@ -94,9 +94,10 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext');
*/
abstract class TActiveRecord extends TComponent
{
- const HAS_MANY='HAS_MANY';
const BELONGS_TO='BELONGS_TO';
const HAS_ONE='HAS_ONE';
+ const HAS_MANY='HAS_MANY';
+ const MANY_TO_MANY='MANY_TO_MANY';
private static $_columnMapping=array();
@@ -110,6 +111,8 @@ abstract class TActiveRecord extends TComponent
*/
public static $COLUMN_MAPPING=array();
+ private static $_relations=array();
+
/**
* This static variable defines the relationships.
* The keys are public variable/property names defined in the AR class.
@@ -562,15 +565,77 @@ abstract class TActiveRecord extends TComponent
/**
* Returns the active record relationship handler for $RELATION with key
* value equal to the $property value.
- * @param string relationship property name.
+ * @param string relationship/property name corresponding to keys in $RELATION array.
* @param array method call arguments.
* @return TActiveRecordRelation
*/
- protected function getRelationHandler($property,$args)
+ protected function getRelationHandler($property,$args=array())
{
$criteria = $this->getCriteria(count($args)>0 ? $args[0] : null, array_slice($args,1));
- $context = new TActiveRecordRelationContext($this, $property, $criteria);
- return $context->getRelationHandler();
+ return $this->getRelationContext($property)->getRelationHandler($criteria);
+ }
+
+ /**
+ * Gets a static copy of the relationship context for given property (a key
+ * in $RELATION), returns null if invalid relationship. Keeps a null
+ * reference to all invalid relations called.
+ * @param string relationship/property name corresponding to keys in $RELATION array.
+ * @return TActiveRecordRelationContext object containing information on
+ * the active record relationships for given property, null if invalid relationship
+ * @since 3.1.2
+ */
+ protected function getRelationContext($property)
+ {
+ $prop = get_class($this).':'.$property;
+ if(!isset(self::$_relations[$prop]))
+ {
+ $context = new TActiveRecordRelationContext($this, $property);
+ //keep a null reference to all non-existing relations called?
+ self::$_relations[$prop] = $context->hasRecordRelation() ? $context : null;
+ }
+ return self::$_relations[$prop];
+ }
+
+ /**
+ * Tries to load the relationship results for the given property. The $property
+ * value should correspond to an entry key in the $RELATION array.
+ * This method can be used to lazy load relationships.
+ * <code>
+ * class TeamRecord extends TActiveRecord
+ * {
+ * ...
+ *
+ * private $_players;
+ * public static $RELATION=array
+ * (
+ * 'players' => array(self::HAS_MANY, 'PlayerRecord'),
+ * );
+ *
+ * public function setPlayers($array)
+ * {
+ * $this->_players=$array;
+ * }
+ *
+ * public function getPlayers()
+ * {
+ * if($this->_players===null)
+ * $this->fetchResultsFor('players');
+ * return $this->_players;
+ * }
+ * }
+ * Usage example:
+ * $team = TeamRecord::finder()->findByPk(1);
+ * var_dump($team->players); //uses lazy load to fetch 'players' relation
+ * </code>
+ * @param string relationship/property name corresponding to keys in $RELATION array.
+ * @return boolean true if relationship exists, false otherwise.
+ * @since 3.1.2
+ */
+ protected function fetchResultsFor($property)
+ {
+ if( ($context=$this->getRelationContext($property)) !== null)
+ return $context->getRelationHandler()->fetchResultsInto($this);
+ return false;
}
/**
diff --git a/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php b/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php
index cbde7fa0..2e4bee2d 100644
--- a/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php
+++ b/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php
@@ -26,7 +26,7 @@ class Album extends SqliteRecord
public static $RELATIONS = array(
'Tracks' => array(self::HAS_MANY, 'Track'),
- 'Artists' => array(self::HAS_MANY, 'Artist', 'album_artists'),
+ 'Artists' => array(self::MANY_TO_MANY, 'Artist', 'album_artists'),
'cover' => array(self::HAS_ONE, 'Cover')
);
@@ -42,8 +42,9 @@ class Artist extends SqliteRecord
public $Albums = array();
- public static $RELATIONS=array(
- 'Albums' => array(self::HAS_MANY, 'Album', 'album_artists')
+ public static $RELATIONS=array
+ (
+ 'Albums' => array(self::MANY_TO_MANY, 'Album', 'album_artists')
);
public static function finder($class=__CLASS__)
@@ -158,6 +159,7 @@ class ForeignKeyTestCase extends UnitTestCase
function test_self_reference_fk()
{
$item = ItemRecord::finder()->withRelated_Items()->findByPk(1);
+
$this->assertNotNull($item);
$this->assertEqual($item->name, "Professional Work Attire");
@@ -168,6 +170,7 @@ class ForeignKeyTestCase extends UnitTestCase
$this->assertEqual($item->related_items[1]->name, "Grooming and Hygiene");
$this->assertEqual($item->related_items[1]->item_id, 3);
}
+
}
?> \ No newline at end of file
diff --git a/tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php b/tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php
index 36864f77..026efb4d 100644
--- a/tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php
+++ b/tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php
@@ -52,7 +52,7 @@ class PlayerRecord extends BaseFkRecord
public static $RELATIONS=array
(
- 'skills' => array(self::HAS_MANY, 'SkillRecord', 'player_skills'),
+ 'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'player_skills'),
'team' => array(self::BELONGS_TO, 'TeamRecord'),
'profile' => array(self::HAS_ONE, 'ProfileRecord'),
);
@@ -112,7 +112,7 @@ class SkillRecord extends BaseFkRecord
public static $RELATIONS=array
(
- 'players' => array(self::HAS_MANY, 'PlayerRecord', 'player_skills'),
+ 'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'player_skills'),
);
public static function finder($className=__CLASS__)
@@ -236,6 +236,7 @@ class ForeignObjectUpdateTest extends UnitTestCase
$this->assertEqual($player4->skills[1]->name, 'Skip');
$this->assertEqual($player4->skills[2]->name, 'Push');
}
+//*/
}
?> \ No newline at end of file
diff --git a/tests/simple_unit/ActiveRecord/MultipleForeignKeyTestCase.php b/tests/simple_unit/ActiveRecord/MultipleForeignKeyTestCase.php
new file mode 100644
index 00000000..16036b9f
--- /dev/null
+++ b/tests/simple_unit/ActiveRecord/MultipleForeignKeyTestCase.php
@@ -0,0 +1,179 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.TActiveRecord');
+
+abstract class MultipleFKSqliteRecord extends TActiveRecord
+{
+ protected static $conn;
+
+ public function getDbConnection()
+ {
+ if(self::$conn===null)
+ self::$conn = new TDbConnection('sqlite:'.dirname(__FILE__).'/test1.sqlite');
+ return self::$conn;
+ }
+}
+
+/**
+ *
+CREATE TABLE table1 (
+id integer PRIMARY KEY AUTOINCREMENT,
+field1 varchar,
+fk1 integer CONSTRAINT fk_id1 REFERENCES table2(id) ON DELETE CASCADE,
+fk2 integer CONSTRAINT fk_id2 REFERENCES table2(id) ON DELETE CASCADE,
+fk3 integer CONSTRAINT fk_id3 REFERENCES table2(id) ON DELETE CASCADE)
+ */
+class Table1 extends MultipleFKSqliteRecord
+{
+ public $id;
+ public $field1;
+ public $fk1;
+ public $fk2;
+ public $fk3;
+
+ public $object1;
+ public $object2;
+ public $object3;
+
+ public static $RELATIONS = array
+ (
+ 'object1' => array(self::BELONGS_TO, 'Table2', 'fk1'),
+ 'object2' => array(self::BELONGS_TO, 'Table2', 'fk2'),
+ 'object3' => array(self::BELONGS_TO, 'Table2', 'fk3'),
+ );
+
+ public static function finder($class=__CLASS__)
+ {
+ return parent::finder($class);
+ }
+}
+
+/**
+ * CREATE TABLE table2 (id integer PRIMARY KEY AUTOINCREMENT,field1 varchar)
+ */
+class Table2 extends MultipleFKSqliteRecord
+{
+ public $id;
+ public $field1;
+
+ private $_state1;
+ public $state2;
+ public $state3;
+
+ public static $RELATIONS = array
+ (
+ 'state1' => array(self::HAS_MANY, 'Table1', 'fk1'),
+ 'state2' => array(self::HAS_MANY, 'Table1', 'fk2'),
+ 'state3' => array(self::HAS_ONE, 'Table1', 'fk3'),
+ );
+
+ public function setState1($obj)
+ {
+ $this->_state1 = $obj;
+ }
+
+ public function getState1()
+ {
+ if(is_null($this->_state1))
+ $this->fetchResultsFor('state1');
+ return $this->_state1;
+ }
+
+ public static function finder($class=__CLASS__)
+ {
+ return parent::finder($class);
+ }
+}
+
+
+class Category extends MultipleFKSqliteRecord
+{
+ public $cat_id;
+ public $category_name;
+ public $parent_cat;
+
+ public $parent_category;
+ public $child_categories=array();
+
+ public static $RELATIONS=array
+ (
+ 'parent_category' => array(self::BELONGS_TO, 'Category'),
+ 'child_categories' => array(self::HAS_MANY, 'Category'),
+ );
+
+ public static function finder($class=__CLASS__)
+ {
+ return parent::finder($class);
+ }
+}
+
+class MultipleForeignKeyTestCase extends UnitTestCase
+{
+ function testBelongsTo()
+ {
+ $obj = Table1::finder()->withObject1()->findAll();
+ $this->assertEqual(count($obj), 3);
+ $this->assertEqual($obj[0]->id, '1');
+ $this->assertEqual($obj[1]->id, '2');
+ $this->assertEqual($obj[2]->id, '3');
+
+ $this->assertEqual($obj[0]->object1->id, '1');
+ $this->assertEqual($obj[1]->object1->id, '2');
+ $this->assertEqual($obj[2]->object1->id, '2');
+ }
+
+ function testHasMany()
+ {
+ $obj = Table2::finder()->withState1()->findAll();
+ $this->assertEqual(count($obj), 5);
+
+ $this->assertEqual(count($obj[0]->state1), 1);
+ $this->assertEqual($obj[0]->state1[0]->id, '1');
+
+ $this->assertEqual(count($obj[1]->state1), 2);
+ $this->assertEqual($obj[1]->state1[0]->id, '2');
+ $this->assertEqual($obj[1]->state1[1]->id, '3');
+
+ $this->assertEqual(count($obj[2]->state1), 0);
+ $this->assertEqual($obj[2]->id, '3');
+
+ $this->assertEqual(count($obj[3]->state1), 0);
+ $this->assertEqual($obj[3]->id, '4');
+ }
+
+ function testHasOne()
+ {
+ $obj = Table2::finder()->withState3('id = 3')->findAll();
+
+ $this->assertEqual(count($obj), 5);
+
+ $this->assertEqual($obj[0]->id, '1');
+ $this->assertNull($obj[0]->state3);
+
+ $this->assertEqual($obj[1]->id, '2');
+ $this->assertNull($obj[1]->state3);
+
+ $this->assertEqual($obj[2]->id, '3');
+ $this->assertNotNull($obj[2]->state3);
+ $this->assertEqual($obj[2]->state3->id, '3');
+
+ $this->assertEqual($obj[3]->id, '4');
+ $this->assertNull($obj[3]->state3);
+ }
+
+ function testParentChild()
+ {
+ $obj = Category::finder()->withChild_Categories()->withParent_Category()->findByPk(2);
+
+ $this->assertEqual($obj->cat_id, '2');
+ $this->assertEqual(count($obj->child_categories), 2);
+ $this->assertNotNull($obj->parent_category);
+
+ $this->assertEqual($obj->child_categories[0]->cat_id, 3);
+ $this->assertEqual($obj->child_categories[1]->cat_id, 4);
+
+ $this->assertEqual($obj->parent_category->cat_id, 1);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/simple_unit/ActiveRecord/records/ItemRecord.php b/tests/simple_unit/ActiveRecord/records/ItemRecord.php
index 45d15427..52a1a658 100644
--- a/tests/simple_unit/ActiveRecord/records/ItemRecord.php
+++ b/tests/simple_unit/ActiveRecord/records/ItemRecord.php
@@ -21,7 +21,7 @@ class ItemRecord extends TActiveRecord
public static $RELATIONS=array
(
- 'related_items' => array(self::HAS_MANY, 'ItemRecord', 'related_items.(related_item_id)'),
+ 'related_items' => array(self::MANY_TO_MANY, 'ItemRecord', 'related_items.related_item_id'),
);
public function getDbConnection()
diff --git a/tests/simple_unit/ActiveRecord/test1.sqlite b/tests/simple_unit/ActiveRecord/test1.sqlite
new file mode 100644
index 00000000..1e056b52
--- /dev/null
+++ b/tests/simple_unit/ActiveRecord/test1.sqlite
Binary files differ
diff --git a/tests/test_tools/simpletest/test_case.php b/tests/test_tools/simpletest/test_case.php
index bc215640..bc4f0c42 100644
--- a/tests/test_tools/simpletest/test_case.php
+++ b/tests/test_tools/simpletest/test_case.php
@@ -240,7 +240,8 @@
'Unexpected exception of type [' . get_class($exception) .
'] with message ['. $exception->getMessage() .
'] in ['. $exception->getFile() .
- '] line [' . $exception->getLine() . ']');
+ '] line [' . $exception->getLine() .
+ '] stack [' . $exception->getTraceAsString() .']');
}
/**