/** * joinAdd - adds another dataobject to this, building a joined query. * * example (requires links.ini to be set up correctly) * // get all the images for product 24 * $i = new DataObject_Image(); * $pi = new DataObjects_Product_image(); * $pi->product_id = 24; // set the product id to 24 * $i->joinAdd($pi); // add the product_image connectoin * $i->find(); * while ($i->fetch()) { * // do stuff * } * // an example with 2 joins * // get all the images linked with products or productgroups * $i = new DataObject_Image(); * $pi = new DataObject_Product_image(); * $pgi = new DataObject_Productgroup_image(); * $i->joinAdd($pi); * $i->joinAdd($pgi); * $i->find(); * while ($i->fetch()) { * // do stuff * } * * * @param optional $obj object |array the joining object (no value resets the join) * If you use an array here it should be in the format: * array('local_column','remotetable:remote_column'); * if remotetable does not have a definition, you should * use @ to hide the include error message.. * * * @param optional $joinType string 'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates * just select ... from a,b,c with no join and * links are added as where items. * * @param optional $joinAs string if you want to select the table as anther name * useful when you want to select multiple columsn * from a secondary table. * @param optional $joinCol string The column on This objects table to match (needed * if this table links to the child object in * multiple places eg. * user->friend (is a link to another user) * user->mother (is a link to another user..) * * @param optional $joinFull bool includes all possible joins, danger!!! * * @param optional $joinMeAs string * * @return none * @access public * @author Stijn de Reede <*****@*****.**> */ function joinAdd($obj = false, $joinType = 'INNER', $joinAs = false, $joinCol = false, $joinFull = false, $joinMeAs = false) { if ($obj === false) { $this->_join = ''; return; } // support for array as first argument // this assumes that you dont have a links.ini for the specified table. // and it doesnt exist as am extended dataobject!! - experimental. if (is_string($obj)) { $this->_join .= $obj; } if (is_string($joinFull)) { $on = $joinFull; $joinFull = false; } else { $on = ''; } $ofield = false; // object field $tfield = false; // this field $toTable = false; if (is_array($obj)) { $tfield = $obj[0]; list($toTable, $ofield) = explode(':', $obj[1]); $obj = DB_DataObject2::factory($toTable, $this->database()); if (!$obj || is_a($obj, 'PEAR_Error')) { $obj = new DB_DataObject(); $obj->__table = $toTable; } $obj->_connect(); // set the table items to nothing.. - eg. do not try and match // things in the child table...??? $items = array(); } if (!is_object($obj) || !is_a($obj, 'DB_DataObject2')) { throw new Exception("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA); } /* make sure $this->_database is set. */ $this->_connect(); $DB =& DB_DataObject2::$CONNECTIONS[$this->_database_dsn_md5]; if ($joinFull) { if ($this->_query['data_select'] == '*') { $this->selectAdd(); $keys = $this->table(); foreach ($keys as $key => $value) { $this->selectAdd($this->tableName() . '.' . $key . ' AS ' . $key); } } $keys = $obj->table(); foreach ($keys as $key => $value) { $this->selectAdd($obj->tableName() . '.' . $key . ' AS ' . $obj->tableName() . '_' . $key); } } /* look up the links for obj table */ //print_r($obj->links()); if (!$ofield && ($olinks = $obj->links())) { foreach ($olinks as $k => $v) { /* link contains {this column} = {linked table}:{linked column} */ $ar = explode(':', $v); if ($ar[0] == $this->__table) { // you have explictly specified the column // and the col is listed here.. // not sure if 1:1 table could cause probs here.. if ($joinCol !== false) { throw new Exception("joinAdd: You cannot target a join column in the " . "'link from' table ({$obj->__table}). " . "Either remove the fourth argument to joinAdd() " . "({$joinCol}), or alter your links.ini file.", DB_DATAOBJECT_ERROR_NODATA); return false; } $ofield = $k; $tfield = $ar[1]; break; } } } /* otherwise see if there are any links from this table to the obj. */ //print_r($this->links()); if ($ofield === false && ($links = $this->links())) { foreach ($links as $k => $v) { /* link contains {this column} = {linked table}:{linked column} */ $ar = explode(':', $v); if ($ar[0] == $obj->__table) { if ($joinCol !== false) { if ($k == $joinCol) { $tfield = $k; $ofield = $ar[1]; break; } else { continue; } } else { $tfield = $k; $ofield = $ar[1]; break; } } } } /* did I find a conneciton between them? */ if ($ofield === false) { throw new Exception("joinAdd: {$obj->__table} has no link with {$this->__table}", DB_DATAOBJECT_ERROR_NODATA); return false; } $joinType = strtoupper($joinType); // we default to joining as the same name (this is remvoed later..) if ($joinAs === false) { $joinAs = $obj->__table; } $quoteIdentifiers = !empty(DB_DataObject2::$CONFIG['quote_identifiers']); // not sure how portable adding database prefixes is.. $objTable = $quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table; // as far as we know only mysql supports database prefixes.. // prefixing the database name is now the default behaviour, // as it enables joining mutiple columns from multiple databases... /* if ( in_array($DB->dsn['phptype'],array('mysql','mysqli')) && strlen($obj->_database) ) { // prefix database (quoted if neccessary..) $objTable = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->_database) : $obj->_database) . '.' . $objTable; } */ // nested (join of joined objects..) $appendJoin = ''; if ($obj->_join) { /* // postgres allows nested queries, with ()'s // not sure what the results are with other databases.. // may be unpredictable.. if (in_array($DB->dsn["phptype"],array('pgsql'))) { $objTable = "($objTable {$obj->_join})"; } else { $appendJoin = $obj->_join; } */ $appendJoin = $obj->_join; } $table = $this->__table; if ($quoteIdentifiers) { $joinAs = $DB->quoteIdentifier($joinAs); $table = $DB->quoteIdentifier($table); $ofield = $DB->quoteIdentifier($ofield); $tfield = $DB->quoteIdentifier($tfield); } // add database prefix if they are different databases $fullJoinAs = ''; $addJoinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs; if ($addJoinAs) { // join table a AS b - is only supported by a few databases and is probably not needed // , however since it makes the whole Statement alot clearer we are leaving it in // for those databases. // $fullJoinAs = in_array($DB->dsn["phptype"],array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" : $joinAs; // EVIL BUGFIX / TODO $fullJoinAs = "AS {$joinAs}"; } else { // if /* if ( in_array($DB->dsn['phptype'],array('mysql','mysqli')) && strlen($obj->_database) ) { $joinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->_database) : $obj->_database) . '.' . $joinAs; } */ $joinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->getDriver()->getDatabaseName()) : $obj->getDriver()->getDatabaseName()) . '.' . $joinAs; } $mytable = $joinMeAs ? $joinMeAs : $table; switch ($joinType) { case 'INNER': case 'LEFT': case 'RIGHT': // others??? .. cross, left outer, right outer, natural..? if (empty($on)) { $on = "{$joinAs}.{$ofield}={$mytable}.{$tfield} {$appendJoin}"; } $this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}" . " ON " . $on; break; case '': // this is just a standard multitable select.. $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}"; $this->whereAdd("{$joinAs}.{$ofield}={$mytable}.{$tfield}"); } // if obj only a dataobject - eg. no extended class has been defined.. // it obvioulsy cant work out what child elements might exist... // untill we get on the fly querying of tables.. if (strtolower(get_class($obj)) == 'db_dataobject2') { return true; } /* now add where conditions for anything that is set in the object */ $items = $obj->table(); // will return an array if no items.. // only fail if we where expecting it to work (eg. not joined on a array) if (!$items) { throw new Exception("joinAdd: No table definition for {$obj->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } foreach ($items as $k => $v) { if (!isset($obj->{$k})) { continue; } $kSql = $quoteIdentifiers ? $DB->quoteIdentifier($k) : $k; if ($v & DB_DATAOBJECT_STR) { $this->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) ($v & DB_DATAOBJECT_BOOL ? $obj->{$k} === 'f' ? 0 : (int) (bool) $obj->{$k} : $obj->{$k}))); continue; } if (is_numeric($obj->{$k})) { $this->whereAdd("{$joinAs}.{$kSql} = {$obj->{$k}}"); continue; } if (is_a($obj->{$k}, 'DB_DataObject2_Cast')) { $value = $obj->{$k}->toString($v, $DB); if (PEAR::isError($value)) { throw new Exception($value->getMessage(), DB_DATAOBJECT_ERROR_INVALIDARG); return false; } if (strtolower($value) === 'null') { $this->whereAdd("{$joinAs}.{$kSql} IS NULL"); continue; } else { $this->whereAdd("{$joinAs}.{$kSql} = {$value}"); continue; } } /* this is probably an error condition! */ $this->whereAdd("{$joinAs}.{$kSql} = 0"); } if (!isset($this->_query)) { throw new Exception("joinAdd can not be run from a object that has had a query run on it,\n clone the object or create a new one and use setFrom()", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } // and finally merge the whereAdd from the child.. if (!$obj->_query['condition']) { return true; } $cond = preg_replace('/^\\sWHERE/i', '', $obj->_query['condition']); $this->whereAdd("({$cond})"); return true; }