Hi guys,
I'm trying to wrap my head around why this was designed this way, so I'll try to explain.
I'm creating a database layer to communicate with SalesForce. SalesForce is built on a Soap layer, but communicates in much the same way as MySQL using their own languages SOQL and SOSL. SOQL to perform queries, SOSL to perform search queries. Basically, I'm trying to wrap a SOQL layer.
This brings me to my first debacle. I'm instanciating xPDO much like a branch like MODX does, only MODX extends xPDO whereas I simply use xPDO to construct a new class called "$db".
$host = 'like.imgonnatellyou.com';
$database = 'sandbox';
$token = 'youraresofunny';
$username = '[email protected]';
$password = 'stillnobro';
$dsn = 'salesforce:host='.$host.';dbname='.$database.';charset=utf8';
$db = new xPDO($dsn, $username, $password.$token);
$db->setLogLevel(xPDO::LOG_LEVEL_ERROR);
$db->setLogTarget('ECHO');
Next off, as you would expect, I'm declaring the package being used to communicate with SalesForce. SalesForce doesn't know "tables" like MySQL (or SQLite/SQLSrv) does. It simply has objects. Thus, a simple query would go like this:
SELECT Id, Name, (SELECT Id, Name, FullName from Account) FROM Contacts WHERE Contact.Name != ''
Using any kind of double quotes, braces, tildes or anything of that matter breaks the code and SalesForce (SoapFault) spits in your face telling you what a moron you are for even attempting that. SalesForce is a bitch bitchy in that regard). A normal MySQL user would typ the following:
SELECT `Id`, `Name`, (SELECT `Id`, `Name`, `FullName` from `Account`) FROM `Contacts` WHERE `Contact`.`Name` != "";
Okay, let that be your small introduction into the bitchyness that is SFDC (SalesforceDotCom).
Thus, as I said, I would declare the package to make know to xPDO how to communicate with SFDC much like MySQL.
$db->addPackage('salesforce', $this->option('core_path').'components/salesforce/model/');
I'll load the base objects, much like xPDO and MODX do.
$db->loadClass('sfObject');
$db->loadClass('sfSimpleObject');
And I start construcitng a simply xPDOCriteria and xPDOQuery with a xPDOObject in return.
$criteria = $db->newQuery('sfContact', array(
'Name:!=' => ''
));
$criteria->select($db->getSelectColumns('sfContact'));
var_dump($criteria->toSQL());
Now, after I do that, which works sketchy as best at this point in time (when I bumped in the the problem this thread is dedicated to), I'll do a simply MODX/xPDO query:
$criteria = $this->modx->newQuery('modUser', array(
'pagetitle:!=' => ''
));
$criteria->select($this->modx->getSelectColumns('modUser'));
$criteria->prepare();
var_dump($criteria->toSQL());
All of you devs out there have seen this chunk of code before. You would expect a SQL dump which you could slap in to PMA to perform a direct query (or via xPDO/PDO, what ever).
WRONG.
<b>Fatal error</b>: Class 'xPDOQuery_mysql' not found in <b>...\core\xpdo\xpdo.class.php</b> on line <b>2530</b>
I traced it all back to the following line of code in xpdo.class.php:
/**
* Creates an new xPDOQuery for a specified xPDOObject class.
*
* @param string $class The class to create the xPDOQuery for.
* @param mixed $criteria Any valid xPDO criteria expression.
* @param boolean|integer $cacheFlag Indicates if the result should be cached
* and optionally for how many seconds (if passed an integer greater than 0).
* @return xPDOQuery The resulting xPDOQuery instance or false if unsuccessful.
*/
public function newQuery($class, $criteria= null, $cacheFlag= true) {
$query= false;
if ($this->loadClass($this->config['dbtype'] . '.xPDOQuery', '', false, true)) {
$xpdoQueryClass= 'xPDOQuery_' . $this->config['dbtype'];
if ($query= new $xpdoQueryClass($this, $class, $criteria)) {
$query->cacheFlag= $cacheFlag;
}
}
return $query;
}
Notice (this would be around line #2517) where it says $this->loadClass(...). It basically first loads salesforce.xPDOQuery as a driver, and when I execute a normal MODX query, it tries to load mysql.xPDOQuery. Perfectly normal. loadClass is actually the one that messes things up.
/**
* Load a class by fully qualified name.
*
* The $fqn should in the format:
*
* dir_a.dir_b.dir_c.classname
*
* which will translate to:
*
* XPDO_CORE_PATH/om/dir_a/dir_b/dir_c/dbtype/classname.class.php
*
* @param string $fqn The fully-qualified name of the class to load.
* @return string|boolean The actual classname if successful, or false if
* not.
*/
public function loadClass($fqn, $path= '', $ignorePkg= false, $transient= false) {
if (empty($fqn)) {
$this->log(xPDO::LOG_LEVEL_ERROR, "No class specified for loadClass");
return false;
}
if (!$transient) {
$typePos= strrpos($fqn, '_' . $this->config['dbtype']);
if ($typePos !== false) {
$fqn= substr($fqn, 0, $typePos);
}
}
$pos= strrpos($fqn, '.');
if ($pos === false) {
$class= $fqn;
if ($transient) {
$fqn= strtolower($class);
} else {
$fqn= $this->config['dbtype'] . '.' . strtolower($class);
}
} else {
$class= substr($fqn, $pos +1);
if ($transient) {
$fqn= substr($fqn, 0, $pos) . '.' . strtolower($class);
} else {
$fqn= substr($fqn, 0, $pos) . '.' . $this->config['dbtype'] . '.' . strtolower($class);
}
}
// check if class exists
if (!$transient && isset ($this->map[$class])) return $class;
$included= class_exists($class, false);
if (isset($this->_sf) && $this->_sf) var_dump('FQN: '.$fqn.'; class: '.$class.'; in map: '.((isset($this->map[$class])) ? 'yes' : 'no').'; trancience: '.$transient.'; included: '.$included);
if ($included) {
if ($transient || (!$transient && isset ($this->map[$class]))) {
return $class;
}
}
$classname= $class;
if (!empty($path) || $ignorePkg) {
$class= $this->_loadClass($class, $fqn, $included, $path, $transient);
} elseif (isset ($this->packages[$this->package])) {
$pqn= $this->package . '.' . $fqn;
if (!$pkgClass= $this->_loadClass($class, $pqn, $included, $this->packages[$this->package]['path'], $transient)) {
foreach ($this->packages as $pkg => $pkgDef) {
if ($pkg === $this->package) continue;
$pqn= $pkg . '.' . $fqn;
if ($pkgClass= $this->_loadClass($class, $pqn, $included, $pkgDef['path'], $transient)) {
break;
}
}
}
$class= $pkgClass;
} else {
$class= false;
}
if ($class === false) {
$this->log(xPDO::LOG_LEVEL_ERROR, "Could not load class: {$classname} from {$fqn}.");
}
return $class;
}
loadClass as the public function is declared around line #577. Notice the part where it checks the class_exist for existance.
$included= class_exists($class, false);
Yes, naturally xPDOQuery as the bare object is loaded, since I loaded xPDOQuery with extension of salesforce.xPDOQuery (thus xPDOQuery_salesforce which extends xPDOQuery). This is where it goes awry.
xPDOQuery, the "$class" class_exists checks, is already loaded, so it doesn't bother to load the mysql branch, xPDOQuery_mysql. Thus, resulting in the error.
So sum things up and make a point of all this:
Am I doing something wrong, or is xPDO not meant to run 2 seperate database instances with two drivers at once? The point where it checks xPDOQuery and not xPDOQuery_mysql or xPDOQuery_salesforce is where it all goes wrong and when I turn my argument includes around, I get the same exact error for xPDOQuery_salesforce, since xPDOQuery_mysql is already includes, so again, it doesn't bother.
Is there any way to fix this or do I need to extend xPDO like MODX (modX) does?
Using this thread I'm also creating a bug report.
Thanks in advance lads!