--- /dev/null
+<?php
+
+namespace RedUNIT\Base;
+
+use RedUNIT\Base as Base;
+use RedBeanPHP\Facade as R;
+use RedBeanPHP\Observer as Observer;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+
+/**
+ * Foreignkeys
+ *
+ * Tests whether foreign keys are correctly generated and whether
+ * depending beans are correctly removed. Also tests auto resolving
+ * types inferred by inspecting foreign keys.
+ *
+ * @file RedUNIT/Base/Foreignkeys.php
+ * @desc Tests foreign key handling and dynamic foreign keys with
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license New BSD/GPLv2
+ *
+ * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the New BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Foreignkeys extends Base implements Observer
+{
+ /**
+ * To log the queries
+ *
+ * @var array
+ */
+ private $queries = array();
+
+ /**
+ * Test whether aliases are not used if not necessary:
+ * when using fetchAs() or R::aliases().
+ *
+ * @return void
+ */
+ public function testAutoResolvAvoid()
+ {
+ R::setAutoResolve( TRUE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->cover = $page;
+ R::store( $book );
+ $book = $book->fresh();
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ $book->cover;
+ asrt( $book->getMeta('sys.autoresolved.cover'), 'page' );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->cover = $page;
+ R::store( $book );
+ $book = $book->fresh();
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ $book->fetchAs('page')->cover;
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ R::nuke();
+ R::aliases( array( 'cover' => 'page' ) );
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->cover = $page;
+ R::store( $book );
+ $book = $book->fresh();
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ $cover = $book->cover;
+ asrt( ( $cover instanceof OODBBean ), TRUE );
+ asrt( $cover->getMeta( 'type' ), 'page' );
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ R::aliases( array() );
+ R::nuke();
+ R::setAutoResolve( FALSE );
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->cover = $page;
+ R::store( $book );
+ $book = $book->fresh();
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ $book->cover;
+ asrt( $book->getMeta('sys.autoresolved.cover'), NULL );
+ R::setAutoResolve( TRUE );
+ }
+
+ /**
+ * Test whether unique constraints are properly created using
+ * reflection.
+ *
+ * @return void
+ */
+ public function testUniqueInspect()
+ {
+ $writer = R::getWriter();
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $category = R::dispense( 'category' );
+ $book->sharedCategory[] = $category;
+ R::store( $book );
+ asrt( count( get_uniques_for_type('book_category') ), 1 );
+ asrt( are_cols_in_unique( 'book_category', array( 'book_id', 'category_id' ) ), TRUE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $category = R::dispense( 'category' );
+ $book->via( 'library' )->sharedCategory[] = $category;
+ R::store( $book );
+ asrt( count( get_uniques_for_type('book_category') ), 0 );
+ asrt( are_cols_in_unique( 'book_category', array( 'book_id', 'category_id' ) ), FALSE );
+ asrt( count( get_uniques_for_type('library') ), 1 );
+ asrt( are_cols_in_unique( 'library', array( 'book_id', 'category_id' ) ), TRUE );
+ AQueryWriter::clearRenames();
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $category = R::dispense( 'category' );
+ $book->sharedCategory[] = $category;
+ R::store( $book );
+ asrt( count( get_uniques_for_type('book_category') ), 1 );
+ asrt( are_cols_in_unique( 'book_category', array( 'book_id', 'category_id' ) ), TRUE );
+ asrt( count( get_uniques_for_type('library') ), 0 );
+ asrt( are_cols_in_unique( 'library', array( 'book_id', 'category_id' ) ), FALSE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $book2 = R::dispense( 'book' );
+ $book->sharedBook[] = $book2;
+ R::store( $book );
+ asrt( count( get_uniques_for_type('book_book') ), 1 );
+ asrt( are_cols_in_unique( 'book_book', array( 'book_id', 'book2_id' ) ), TRUE );
+ try {
+ $result = R::getWriter()->addUniqueConstraint( 'nonexistant', array( 'a', 'b' ) );
+ } catch( \Exception $e ) {
+ print_r( $e ); exit;
+ }
+ pass(); //dont crash!
+ asrt( $result, FALSE );
+ }
+
+ /**
+ * Tests foreign keys but checks using ProxyWriter.
+ *
+ * @return void
+ */
+ public function testFKInspect()
+ {
+ $faultyWriter = new \FaultyWriter( R::getDatabaseAdapter() );
+ try {
+ $null = \ProxyWriter::callMethod( $faultyWriter, 'getForeignKeyForTypeProperty', 'test', 'test' );
+ pass();
+ } catch( \Exception $e ) {
+ fail();
+ }
+ asrt( is_null( $null ), TRUE );
+ $writer = R::getWriter();
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->xownPage[] = $page;
+ R::store( $book );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'book_id' );
+ asrt( is_array( $keys ), TRUE );
+ asrt( $keys['on_delete'], 'CASCADE' );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'id' );
+ asrt( is_null( $keys ), TRUE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->ownPage[] = $page;
+ R::store( $book );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'book_id' );
+ asrt( is_array( $keys ), TRUE );
+ asrt( $keys['on_delete'], 'SET NULL' );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'id' );
+ asrt( is_null( $keys ), TRUE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->alias('magazine')->xownPage[] = $page;
+ R::store( $book );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'magazine_id' );
+ asrt( is_array( $keys ), TRUE );
+ asrt( $keys['on_delete'], 'CASCADE' );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'book_id' );
+ asrt( is_null( $keys ), TRUE );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'page', 'id' );
+ asrt( is_null( $keys ), TRUE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $page = R::dispense( 'page' );
+ $book->cover= $page;
+ R::store( $book );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book', 'cover_id' );
+ asrt( is_array( $keys ), TRUE );
+ asrt( $keys['on_delete'], 'SET NULL' );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book', 'page_id' );
+ asrt( is_null( $keys ), TRUE );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book', 'id' );
+ asrt( is_null( $keys ), TRUE );
+ R::nuke();
+ $book = R::dispense( 'book' );
+ $category = R::dispense( 'category' );
+ $book->sharedTag[] = $category;
+ R::store( $book );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book_category', 'book_id' );
+ asrt( is_array( $keys ), TRUE );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book_category', 'category_id' );
+ asrt( is_array( $keys ), TRUE );
+ $keys = \ProxyWriter::callMethod( $writer, 'getForeignKeyForTypeProperty', 'book_category', 'id' );
+ asrt( is_null( $keys ), TRUE );
+ }
+
+ /**
+ * Test dependencies.
+ *
+ * @return void
+ */
+ public function testDependency()
+ {
+ $can = $this->createBeanInCan( FALSE );
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ // Bean stays
+ asrt( R::count( 'bean' ), 1 );
+ }
+
+ /**
+ * Test dependencies (variation).
+ *
+ * @return void
+ */
+ public function testDependency2()
+ {
+ $can = $this->createBeanInCan( TRUE );
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ // Bean gone
+ asrt( R::count( 'bean' ), 0 );
+
+ $can = $this->createBeanInCan( FALSE );
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ // Bean stays, constraint removed
+ asrt( R::count( 'bean' ), 0 );
+
+ //need to recreate table to get rid of constraint!
+ R::nuke();
+
+ $can = $this->createBeanInCan( FALSE );
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ // Bean stays, constraint removed
+ asrt( R::count( 'bean' ), 1 );
+
+ }
+
+ /**
+ * Tests dependencies (variation).
+ *
+ * @return void
+ */
+ public function testDependency3()
+ {
+ R::nuke();
+
+ $can = $this->createCanForBean();
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ asrt( R::count( 'bean' ), 1 );
+ }
+
+ /**
+ * Tests dependencies (variation).
+ *
+ * @return void
+ */
+ public function testDependency4()
+ {
+ R::nuke();
+
+ $can = $this->createBeanInCan( TRUE );
+
+ R::store( $can );
+
+ R::trash( $can );
+
+ $can = $this->createCanForBean();
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ asrt( R::count( 'bean' ), 0 );
+
+ $can = $this->createBeanInCan( TRUE );
+
+ R::store( $can );
+
+ R::trash( $can );
+
+ $can = $this->createCanForBean();
+
+ asrt( R::count( 'bean' ), 1 );
+
+ R::trash( $can );
+
+ asrt( R::count( 'bean' ), 0 );
+ }
+
+ /**
+ * Issue #171
+ * The index name argument is not unique in processEmbeddedBean etc.
+ *
+ * @return void
+ */
+ public function testIssue171()
+ {
+ R::getDatabaseAdapter()->addEventListener( 'sql_exec', $this );
+
+ $account = R::dispense( 'account' );
+ $user = R::dispense( 'user' );
+ $player = R::dispense( 'player' );
+
+ $account->ownUser[] = $user;
+
+ R::store( $account );
+
+ asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_user_account' ) !== FALSE, TRUE );
+
+ $this->queries = array();
+
+ $account->ownPlayer[] = $player;
+
+ R::store( $account );
+
+ asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_player_accou' ) !== FALSE, TRUE );
+ }
+
+ /**
+ * Tests whether foreign keys are created correctly for certain
+ * relations.
+ *
+ * @return void
+ */
+ public function testCreationOfForeignKeys()
+ {
+ $this->queries = array();
+
+ $account = R::dispense( 'account' );
+ $user = R::dispense( 'user' );
+ $player = R::dispense( 'player' );
+
+ $user->account = $account;
+
+ R::store( $user );
+
+ asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_user_account' ) !== FALSE, TRUE );
+
+ $this->queries = array();
+
+ $player->account = $account;
+
+ R::store( $player );
+
+ asrt( strpos( implode( ',', $this->queries ), 'index_foreignkey_player_accou' ) !== FALSE, TRUE );
+ }
+
+ /**
+ * Test helper method.
+ * Creates a bean in a can. The bean will get a reference
+ * to the can and can be made dependent.
+ *
+ * @return OODBBean $can
+ */
+ private function createBeanInCan( $isExcl )
+ {
+ $can = R::dispense( 'can' );
+ $bean = R::dispense( 'bean' );
+
+ $can->name = 'bakedbeans';
+ $bean->taste = 'salty';
+
+ if ($isExcl) {
+ $can->xownBean[] = $bean;
+ } else {
+ $can->ownBean[] = $bean;
+ }
+
+ R::store( $can );
+
+ return $can;
+ }
+
+ /**
+ * Test helper method.
+ * Creates a bean in a can beginning with the bean. The bean will get a reference
+ * to the can and can be made dependent.
+ *
+ * @return OODBBean $can
+ */
+ private function createCanForBean()
+ {
+ $can = R::dispense( 'can' );
+ $bean = R::dispense( 'bean' );
+
+ $bean->can = $can;
+
+ R::store( $bean );
+
+ return $can;
+ }
+
+ /**
+ * Log queries
+ *
+ * @param string $event
+ * @param Adapter $info
+ */
+ public function onEvent( $event, $info )
+ {
+ $this->queries[] = $info->getSQL();
+ }
+}