diff options
| author | wei <> | 2007-10-08 03:24:07 +0000 | 
|---|---|---|
| committer | wei <> | 2007-10-08 03:24:07 +0000 | 
| commit | 4ceba82b9863f2c6323cbe00407e4bfbedbfc1cd (patch) | |
| tree | bc93498b5bf77b25c5a2d5a87ffaa4f8a9b70728 | |
| parent | aedbfe04daaaa0baa8e97c4d7d13e8a1f9d867cb (diff) | |
Allow active records to have multiple foreign key references to the same table. Add TXCache.
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 @@ -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.pngBinary files differ index 50ab812d..ac33b88b 100644 --- a/demos/quickstart/protected/pages/Database/ar_objects.png +++ b/demos/quickstart/protected/pages/Database/ar_objects.png diff --git a/demos/quickstart/protected/pages/Database/ar_objects.vsd b/demos/quickstart/protected/pages/Database/ar_objects.vsdBinary files differ index 10346c54..d3b3963d 100644 --- a/demos/quickstart/protected/pages/Database/ar_objects.vsd +++ b/demos/quickstart/protected/pages/Database/ar_objects.vsd 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 © 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.sqliteBinary files differ new file mode 100644 index 00000000..1e056b52 --- /dev/null +++ b/tests/simple_unit/ActiveRecord/test1.sqlite 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() .']');          }          /** | 
